Skip to main content
2 of 2
added 14 characters in body; edited title
Jamal
  • 35.2k
  • 13
  • 134
  • 238

Multifunction sprite class for pygame

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)
George Willcox
  • 777
  • 1
  • 4
  • 21