I've written a class that allows me to easily create sprites on the screen, and then do things like: setting an image or an animation; move the sprite as a platformer or as a top down game; and test for many things like collision and orientation. So far I've found this to be very useful, and it has made it really easy to make games. Now I've finished getting everything to work, and writing a text file of the documentation I thought that I'd post it here for review (I'll post the documentation if requested).
The idea is that it would be imported into a file using import classes.sprite.Sprite
. With classes being the folder that it's in, sprite.py the name of the file, and Sprite the name of the class.
The folder structure would be:
Projects/Pygame/Game/main.py
Projects/Pygame/Game/classes/sprite.py
Projects/Pygame/Game/data/
The data folder is used to store any images or text files that the game will need.
import os, pygame
from pygame.locals import *
from math import *
pygame.init()
WIDTH = None
HEIGHT = None
SCREEN = None
FPS = None
path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'data'))
def load_rotated_image(sprite):
'''Loads an image for the rotate functions in Sprite class'''
sprite.surface = pygame.transform.rotate(sprite.image, sprite.angle)
sprite.rect = sprite.surface.get_rect(center = (sprite.rect.x + sprite.rect.width / 2, sprite.rect.y + sprite.rect.height / 2))
def init(surface, fps):
'''Initialises the sprite class'''
global WIDTH, HEIGHT, SCREEN, FPS #I know globals are bad but I don't know how to avoid them here
SCREEN = surface
WIDTH, HEIGHT = SCREEN.get_size()
FPS = fps
class Sprite(object):
'''Creates a sprite object'''
def __init__ (self, x = 0, y = 0, width = 50, height = 50, colour = (255, 255, 255), boundry = None, Clamp = False, flip = False):
'''Initialises the sprite object'''
self.x = x
self.y = y
self.width = width
self.height = height
if boundry == None: self.boundry = (0, 0, WIDTH, HEIGHT)
else: self.boundry = boundry
self.clamp = Clamp
self.colour = colour
self.do_flip = flip
self.angle = 0
self.animation = False
self.appear = True
self.flip = False
self.moving = False
self.surface = pygame.Surface((self.width, self.height))
self.image = self.surface
self.rect = self.surface.get_rect()
self.rect.x = x
self.rect.y = y
self.surface.fill(colour)
self.xvel = 0
self.yvel = 0
def set_image(self, image, scale = 1, size = (0, 0), colourkey = (255, 0, 255)):
'''Initilizes a sprite to display an image'''
self.surface = pygame.image.load(os.path.join(path, image)).convert()
#self.surface = pygame.image.load(image).convert()
if not colourkey == None: self.surface.set_colorkey(colourkey)
if size != (0, 0):
self.width = round(size[0])
self.height = round(size[1])
else:
self.width = round(scale * self.surface.get_width())
self.height = round(scale * self.surface.get_height())
self.image = pygame.transform.scale(self.surface, (self.width, self.height))
self.surface = self.image
self.rect = self.surface.get_rect()
self.rect.x = self.x
self.rect.y = self.y
def set_animation(self, frames = [], scale = 1, fps = 1, animate_on_move = False, idle_frame = None, colourkey = (255, 0, 255)):
'''Initilizes a sprite to display an animation'''
self.animation = True
self.frames = []
self.animate_on_move = animate_on_move
self.idle_frame = idle_frame
self.change = max(round(FPS / abs(fps)), 1)
self.tick = 0
for image in frames:
self.frame = pygame.image.load(os.path.join(path, image)).convert()
if not colourkey == None: self.frame.set_colorkey(colourkey)
self.width = round(scale * self.frame.get_width())
self.height = round(scale * self.frame.get_height())
self.frames.append(pygame.transform.scale(self.frame, (self.width, self.height)))
self.rect = self.frames[0].get_rect()
self.rect.x = self.x
self.rect.y = self.y
self.frame = 0
def show(self):
'''Show this sprite'''
self.appear = True
def hide(self):
'''Hide this sprite'''
self.appear = False
def move(self, speed = 5, colliders = [], platformer = False, jump_height = 1):
'''Allows the arrow keys to control the sprite'''
keys = pygame.key.get_pressed()
if keys[K_a] or keys[K_LEFT]:
self.rect.x -= int(abs(speed))
self.flip = False
self.moving = True
if self.rect_collide(colliders):
self.rect.x += int(abs(speed))
if keys[K_d] or keys[K_RIGHT]:
self.rect.x += int(abs(speed))
self.flip = True
self.moving = True
if self.rect_collide(colliders):
self.rect.x -= int(abs(speed))
if (keys[K_a] or keys[K_LEFT]) and (keys[K_d] or keys[K_RIGHT]) : self.moving = False
if platformer == True:
if not (keys[K_a] or keys[K_LEFT] or keys[K_d] or keys[K_RIGHT]): self.moving = False
self.rect.y += 10
if (keys[K_w] or keys[K_UP] or keys[K_SPACE]) and self.rect_collide(colliders) and self.yvel < 1:
self.yvel = int(abs(speed)) * (2 + ((jump_height - 1) / 2))
self.rect.y -= 15
if self.rect_collide(colliders): self.yvel = 0
self.rect.y += 15
self.rect.y -= 10
self.rect.y -= self.yvel
if self.rect_collide(colliders):
if self.yvel > 0: self.rect.y += self.yvel + 1
else: self.rect.y += self.yvel
self.yvel = 0
else:
if not (keys[K_a] or keys[K_LEFT] or keys[K_d] or keys[K_RIGHT] or keys[K_w] or keys[K_UP] or keys[K_s] or keys[K_DOWN]): self.moving = False
if (keys[K_w] or keys[K_UP]) and (keys[K_s] or keys[K_DOWN]) : self.moving = False
if keys[K_w] or keys[K_UP]:
self.rect.y -= int(abs(speed))
self.moving = True
if self.rect_collide(colliders):
self.rect.y += int(abs(speed))
if keys[K_s] or keys[K_DOWN]:
self.rect.y += int(abs(speed))
self.moving = True
if self.rect_collide(colliders):
self.rect.y -= int(abs(speed))
def render(self, colour = None, frame = None):
'''Renders the sprite'''
if not colour == None:
self.colour = colour
self.surface.fill(self.colour)
if self.clamp:
self.rect.x = min(max(self.rect.x, self.boundry[0]), self.boundry[0] + self.boundry[2] - self.rect.width )
self.rect.y = min(max(self.rect.y, self.boundry[1]), self.boundry[1] + self.boundry[3] - self.rect.height)
if self.appear:
if self.animation:
self.tick += 1
if self.tick == self.change:
if self.frame + 1 == len(self.frames):
self.frame = 0
else:
self.frame += 1
self.tick = 0
if not frame == None: self.frame = frame % len(self.frames)
if self.animate_on_move:
if not self.moving: self.frame = self.idle_frame
self.rect.width = self.frames[self.frame].get_width()
self.rect.height = self.frames[self.frame].get_height()
if self.do_flip:
if self.flip: SCREEN.blit(pygame.transform.flip(self.frames[self.frame], True, False), (self.rect.x, self.rect.y))
else: SCREEN.blit(self.frames[self.frame], (self.rect.x, self.rect.y))
else: SCREEN.blit(self.frames[self.frame], (self.rect.x, self.rect.y))
else:
if self.do_flip:
if self.flip: SCREEN.blit(pygame.transform.flip(self.surface, True, False), (self.rect.x, self.rect.y))
else: SCREEN.blit(self.surface, (self.rect.x, self.rect.y))
else: SCREEN.blit(self.surface, (self.rect.x, self.rect.y))
def wrap(self):
'''Wraps the sprite around the edge of the screen'''
self.wrap_around = False
if self.rect.x < 0:
self.rect.x += WIDTH
self.wrap_around = True
if self.rect.x + self.width > WIDTH:
self.rect.x -= WIDTH
self.wrap_around = True
if self.rect.y < 0:
self.rect.y += HEIGHT
self.wrap_around = True
if self.rect.y + self.height > HEIGHT:
self.rect.y -= HEIGHT
self.wrap_around = True
if self.wrap_around:
if self.animation:
if self.do_flip:
if self.flip: SCREEN.blit(pygame.transform.flip(self.frames[self.frame], True, False), (self.rect.x, self.rect.y))
else: SCREEN.blit(self.frames[self.frame], (self.rect.x, self.rect.y))
else: SCREEN.blit(self.frames[self.frame], (self.rect.x, self.rect.y))
else:
if self.do_flip:
if self.flip: SCREEN.blit(pygame.transform.flip(self.surface, True, False), (self.rect.x, self.rect.y))
else: SCREEN.blit(self.surface, (self.rect.x, self.rect.y))
else: SCREEN.blit(self.surface, (self.rect.x, self.rect.y))
self.rect.x %= WIDTH
self.rect.y %= HEIGHT
def rect_collide(self, sprites):
'''Returns True if two sprites are colliding'''
self.rect = pygame.Rect(self.rect.x, self.rect.y, self.rect.width, self.rect.height)
if self.appear:
for sprite in sprites:
if self.rect.colliderect(sprite) and sprite.appear:
return True
return False
def clamp(self, boundry = None, Clamp = None):
'''Clamps a sprite to a boundry'''
if not Clamp == None: self.clamp = Clamp
else:
if self.clamp == True: self.clamp = False
if self.clamp == False: self.clamp = True
if not boundry == None:
self.boundry = boundry
def Collect(self, player):
'''Makes an object collectable'''
if player.rect_collide([self]):
self.hide()
return True
def mouse_hover(self):
'''Returns True if the mouse is over the sprite'''
mouse_pos = pygame.mouse.get_pos()
if self.rect.collidepoint(mouse_pos): return True
else: return False
def mouse_click(self, button = 1):
'''Returns True if the sprite is clicked'''
mouse_pos = pygame.mouse.get_pos()
mouse_pressed = pygame.mouse.get_pressed()
if self.rect.collidepoint(mouse_pos) and mouse_pressed[button - 1]: return True
else: return False
def move_in_direction(self, magnitude, direction = None):
'''Moves the sprite a certain distance in the direction it's facing'''
if direction != None:
self.rect.x += magnitude * cos(radians(direction))
self.rect.y -= magnitude * sin(radians(direction))
else:
self.rect.x += magnitude * cos(radians(self.angle))
self.rect.y -= magnitude * sin(radians(self.angle))
def point_in_direction(self, direction):
'''Points a sprite in a specific direction'''
self.angle = direction
load_rotated_image(self)
def point_towards(self, pos = (0,0), sprite = None, anchor = 'center'):
'''Points sprite towards another sprite'''
if sprite == None: self.angle = 360 - atan2(pos[1] - self.rect.centery, pos[0] - self.rect.centerx) * 180 / pi
else: exec('self.angle = 360 - atan2(sprite.rect.' + anchor + '[1] - self.rect.centery, sprite.rect.' + anchor + '[0] - self.rect.centerx) * 180 / pi')
load_rotated_image(self)
def distance_to(self, pos = (0, 0), sprite = None, anchor = 'center'):
'''Returns the distance to a sprite or point'''
exec('self.pos = (self.rect.' + anchor + '[0], self.rect.' + anchor + '[1])')
if sprite == None: return sqrt(abs(self.pos[0] - pos[0])**2 + abs(self.pos[1] - pos[1])**2)
else: return sqrt(abs(self.pos[0] - sprite.rect.x)**2 + abs(self.pos[1] - sprite.rect.y)**2)
def turn(self, angle):
'''Turns the sprite a certain amout of degrees'''
self.angle += angle
load_rotated_image(self)