Search code examples
pythonclasspygamespriteclone

How do I make clones of a Sprite in Pygame?


I am adding grass into a Scrolling Platform in Pygame & Python 2.7. The problem is, so far I've only got one strand of grass and I want there to be more. Basically I want there to be clones.

I've heard that Classes can make clones of Sprites, but I"m not exactly sure how classes work and how to even make one. All the tutorials I've found online have only made me more confused.

This part calls the grass_rotate function and also makes the grass_rect:

grass_rect = grass.get_rect(topleft = (grass_x, 480 - player_y + 460))
grass_rotate(screen, grass_rect, (grass_x, 480 - player_y + 460), grass_angle)

This is the grass_rotate function:

def grass_rotate(screen, grass_rect, topleft, grass_angle):
    rotated_grass = pygame.transform.rotate(grass, grass_angle)
    rotated_grass_rect = rotated_grass.get_rect(center = grass_rect.center)
    screen.blit(rotated_grass, rotated_grass_rect)

Solution

  • To answer your question, to make rotated "clones" of a sprite, I think it would be best to make a sprite object based on pygame.sprite.Sprite, with options for bitmap rotation.

    First lets make a really simple sprite:

    class Grass( pygame.sprite.Sprite ):
        """ Creates a tuft of grass at (x,y) """
        def __init__( self, image, x, y ):
            pygame.sprite.Sprite.__init__(self)
            self.image    = image
            self.rect     = self.image.get_rect()
            self.rect.center = ( x, y )
    

    Because this is based on the sprite library, it automatically gets (inherits) the function Grass.draw() from pygame.sprite.Sprite.

    EDIT: The above paragraph is false. Sprites do not inherit draw(), this is only part of Sprite Groups. it is necessary to implement your own draw(), iff not using Sprite Groups:

        def draw( self, surface ):
            surface.blit( self.image, self.rect )
    

    So to put it on the screen, we can just do:

    grass_image = pygame.image.load( 'grass_clump.png' ).convert_alpha()
    my_grass1 = Grass( grass_image, 10, 10 )
    my_grass2 = Grass( grass_image, 50, 40 )
    
    ...
    
    my_grass1.draw( window )
    my_grass2.draw( window )
    
    ...
    

    But we want to randomise the angle a bit, so each clump of grass is not so flat & regular. One way to do this is to create a rotated copy of the image when the Grass sprite is initialised (Grass.__init__()). PyGame already has a rotozoom function that does this.

    So we basically have the same thing, except now we add an extra optional parameter for rotation. I've defaulted it to None, and without the parameter, the class is free to choose its own random angle.

    ### Creates a sprite that is rotated slightly to the left or right
    class Grass( pygame.sprite.Sprite ):
        """ Creates a tuft of grass at a jaunty angle """
        def __init__( self, image, x, y, rotation=None ):
            pygame.sprite.Sprite.__init__(self)
            if ( rotation == None ):                                       # choose random angle if none
                angle = random.randrange( -15, 16 )                        # choose random angle if none
            self.image    = pygame.transform.rotozoom( image, angle, 1 )   # rotate the image
            self.rect     = self.image.get_rect()
            self.rect.center = ( x, y )
    

    Now we need to put all this together. One of the very useful features of PyGame and Pygame's Sprite library is Sprite Groups. I don't want to go into huge detail here, but essentially it allows you act on a whole bunch of sprites at the same time, making the code much easier.

    So now we want lots of clones of grass, so we just make 50 of them, and add to a new group:

    ### create lots of grass sprites, adding them to a group
    grass_image = pygame.image.load( 'grass_clump_48.png' ).convert_alpha()
    all_grass_sprites = pygame.sprite.Group()
    for i in range( 50 ):
        new_x     = random.randrange( WINDOW_WIDTH )
        new_y     = random.randrange( WINDOW_HEIGHT )
        new_grass = Grass( grass_image, new_x, new_y )
        all_grass_sprites.add( new_grass )
    

    To draw these to the window, you could loop through all sprites in the group with a for() statement. But that's not necessary, because the sprite group can do this for us already:

    all_grass_sprites.draw( window )   # draws all the grass sprites
    

    grass_sprites_clones

    Too easy right?!

    The clump of grass is from Open Clip Art (Public Domain Licensed.)

    Here's the reference code:

    import pygame
    import random
    
    # Window size
    WINDOW_WIDTH    = 400
    WINDOW_HEIGHT   = 400
    WINDOW_SURFACE  = pygame.HWSURFACE|pygame.DOUBLEBUF
    
    GREEN = ( 130, 220,   0 )  # background colour
    
    ### Creates a sprite that is rotated slightly to the left or right
    class Grass( pygame.sprite.Sprite ):
        """ Creates a tuft of grass at a jaunty angle """
        def __init__( self, image, x, y, rotation=None ):
            pygame.sprite.Sprite.__init__(self)
            if ( rotation == None ):                                       # choose random angle if none
                angle = random.randrange( -15, 14 )                        # choose random angle if none
            self.image    = pygame.transform.rotozoom( image, angle, 1 )   # rotate the image
            self.rect     = self.image.get_rect()
            self.rect.center = ( x, y )
    
    
    ### Initialisation
    pygame.init()
    window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
    pygame.display.set_caption("Grass Sprite Clones")
    
    ### create lots of grass sprites, adding them to a group
    grass_image = pygame.image.load( 'grass_clump_48.png' ).convert_alpha()
    all_grass_sprites = pygame.sprite.Group()
    for i in range( 50 ):
        new_x     = random.randrange( WINDOW_WIDTH )
        new_y     = random.randrange( WINDOW_HEIGHT )
        new_grass = Grass( grass_image, new_x, new_y )
        all_grass_sprites.add( new_grass )
    
    ### Main Loop
    clock = pygame.time.Clock()
    done = False
    while not done:
    
        # Handle user-input
        for event in pygame.event.get():
            if ( event.type == pygame.QUIT ):
                done = True
            elif ( event.type == pygame.MOUSEBUTTONUP ):
                # On mouse-click
                pass
    
        # Update the window, but not more than 60fps
        window.fill( GREEN )
        all_grass_sprites.draw( window )   # draws all the grass sprites
        pygame.display.flip()
    
        # Clamp FPS
        clock.tick(60)
    
    pygame.quit()