Group all the sprites together with Pygame

In the last tutorial we have created two sprite objects and then render it on the screen with the help of the GameSprite module, in this tutorial we will further modify the GameSprite module so it can be used by the pygame.sprite.Group module later on. With the use of the pygame.sprite.Group module we can easily remove a sprite from the screen once it has overlapped with another sprite. The first thing we need to do in our today tutorial is to modify the GameSprite module so it will contain the image and the rect attributes that will be used later on by the Group module.

import pygame
from pygame.locals import *

class GameSprite(pygame.sprite.Sprite):

    # Constructor. Pass in the image file
    # and it's width and height
    def __init__(self, image, width, height):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        # Create the spritesheet surface from the image file
        self.sprite = pygame.image.load(image).convert_alpha()
        
        #register the width and height of the sprite
        self.width = width
        self.height = height
        
    def setImage(self, x_sprite, y_sprite, flip, player_draw_pos): # this method will return a subsurface which represents a portion of the spritesheet
    
        #the rectangle object uses in clipping
        self.clip_rect = Rect((x_sprite, y_sprite), (self.width, self.height))
        
        self.sprite.set_clip(self.clip_rect) # clip a portion of the spritesheet with the rectangle object
        sub_surface = self.sprite.subsurface(self.sprite.get_clip()) #create sub surface
        self.image = pygame.transform.flip(sub_surface, flip, False) #self.image will be called by group
        self.rect = Rect(player_draw_pos.x, player_draw_pos.y, self.width, self.height) #self.rect will be called by group
    
    def detectCollide(self, right, player_draw_pos, enemy_draw_pos): #return a boolean value indicates whether the player has collided with enemy or not
        # create the rectangle object for both enemy and player that will be used in the collide_rect function
        self.rect = Rect(player_draw_pos.x, player_draw_pos.y, self.width, self.height)
        right.rect = Rect(enemy_draw_pos.x, enemy_draw_pos.y, right.width, right.height)
        return pygame.sprite.collide_rect(self, right)

The only method which we need to change is the getImage method, first of all we will change the name of the method to setImage because this method no longer returned the sprite surface, we have also removed the return sub surface command and include a new player_draw_pos which is the Vector2D object parameter into the method. With that new parameter we can then create the new rect attribute each time the object has moved to a new position. We have also created a new image attribute on each iteration of the pygame while loop. The Group module will need the above two attributes to blit the sprite to the screen surface.

The next thing we need to do is to modify the main module a little, this time we will remove the enemy sprite once it has overlapped with the player sprite with the remove method from the Group module.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D
from game_sprite import GameSprite
from pygame.sprite import Group

robot_sprite_sheet = 'left.png'

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32) #return a screen surface with desire screen size
pygame.display.set_caption("Pygame Demo")

w, h = 64, 64 # width and height of the sprite
sprite_counter = 0 # initialize the sprite_counter
game_frame = 0 # initialize the game_frame counter

#direction control for player
flip = False

#direction control for enemy
face_left = True

clock = pygame.time.Clock() # initialize the clock object
player_pos = Vector2D(30, 240) # initial position of the player sprite
enemy_pos = Vector2D(450, 240) # initial position of the enemy sprite
game_speed = 70. # speed per second

#create the player sprite 
game_sprite = GameSprite(robot_sprite_sheet, w, h)

#the enemy sprite object
game_sprite_two = GameSprite(robot_sprite_sheet, w, h)

#create new sprite group
sprite_group = Group(game_sprite)
sprite_group.add(game_sprite_two)

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    v = Vector2D(0., 0.) # reset the speed to zero on each pass
    
    pressed_keys = pygame.key.get_pressed() # get all the keys from key-press events 
        
    if pressed_keys[K_LEFT]: 
        flip = True # if the user pressed the left arrow key then makes the sprite object facing left
        v = Vector2D(-1., 0.) # move the object one unit to the left
        
    elif pressed_keys[K_RIGHT]:
        flip = False # if the user pressed the right arrow key then makes the sprite object facing right
        v = Vector2D(1., 0.) # move the object one unit to the right
            
    screen.fill([255,255,255])
    
    player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)#set the new player position
    game_sprite.setImage(sprite_counter * 64, 0, flip, player_draw_pos) # set the new player sprite surface
    #screen.blit(game_sprite_surface, player_draw_pos)
    
    enemy_draw_pos = Vector2D(enemy_pos.x-w/2, enemy_pos.y-h/2)#set the new enemy position
    if(sprite_group.has(game_sprite_two)):
        game_sprite_two.setImage(sprite_counter * 64, 0, face_left, enemy_draw_pos) # set the new enemy sprite surface
    #screen.blit(game_sprite_surface_two, enemy_draw_pos)
    
    #blit the sprites to the pygame screen within group
    sprite_group.draw(screen)
    
    #increase the sprite counter after x numbers of frame
    if(game_frame % 50 == 0):
        
        sprite_counter += 1
        
        if(sprite_counter > 5):
            sprite_counter = 0
            
    game_frame += 1
    
    time_passed = clock.tick() #delta time for each frame
    time_passed_seconds = time_passed / 1000.0
       
    #set the player's new position after each frame
    player_pos+= v * game_speed * time_passed_seconds   
    
    #if the enemy collides with the player, remove the enemy, else continue to move left
    if(sprite_group.has(game_sprite_two)):
        if(game_sprite.detectCollide(game_sprite_two, player_draw_pos, enemy_draw_pos)):
            sprite_group.remove(game_sprite_two)
        
        elif(face_left == True):
            enemy_pos += Vector2D(-1., 0.) * game_speed * time_passed_seconds 
    
    pygame.display.flip()

With the introduction of the Group module into our game we can now easily add or remove sprite into or from one group and blit those sprites to the screen surface with just one line of code.

Detect the overlapping between two game objects with Pygame

Pygame has a a few great interfaces that we can use to detect whether game character has overlapped each other or not and today we are going to look at one of them which is the pygame.sprite.collide_rect method. This method will take in two sprite objects that will then be used to find out the condition of the overlap. If no overlap happens then the above method will return False or else it will return True.

The script below is not perfect because it will only detect the overlapping once in the entire game and moves the enemy sprite in the opposite direction after the first overlapping occurs. This program is uses just to demonstrate to you how the above mentioned method works.

First thing we need to do before going into the main module is to modify the previous GameSprite module by making it a subclass of the pygame.sprite.Sprite class and then change it’s getImage method so we do not need to create a new GameSprite object on every loop pass just like before. Finally we will include a new detectCollide method which will return a boolean value indicates whether two sprite have overlapped each other or not.

import pygame
from pygame.locals import *

class GameSprite(pygame.sprite.Sprite):

    # Constructor. Pass in the image file
    # and it's width and height
    def __init__(self, image, width, height):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        # Create the spritesheet surface from the image file
        self.sprite = pygame.image.load(image).convert_alpha()
        
        #register the width and height of the sprite
        self.width = width
        self.height = height
        
    def getImage(self, x_sprite, y_sprite, flip): # this method will return a subsurface which represents a portion of the spritesheet
    
        #the rectangle object uses in clipping
        self.clip_rect = Rect((x_sprite, y_sprite), (self.width, self.height))
        
        self.sprite.set_clip(self.clip_rect) # clip a portion of the spritesheet with the rectangle object
        sub_surface = self.sprite.subsurface(self.sprite.get_clip()) #create sub surface
        return pygame.transform.flip(sub_surface, flip, False) #return a new flip surface
    
    def detectCollide(self, right, player_draw_pos, enemy_draw_pos): #return a boolean value indicates whether the player has collided with enemy or not
        # create the rectangle object for both enemy and player that will be used in the collide_rect function
        self.rect = Rect(player_draw_pos.x, player_draw_pos.y, self.width, self.height)
        right.rect = Rect(enemy_draw_pos.x, enemy_draw_pos.y, right.width, right.height)
        return pygame.sprite.collide_rect(self, right)

The next thing we need to do is to include the detectCollide method in our main game loop to detect the overlapping of two objects.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D
from game_sprite import GameSprite

robot_sprite_sheet = 'left.png'

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32) #return a screen surface with desire screen size
pygame.display.set_caption("Pygame Demo")

w, h = 64, 64 # width and height of the sprite
sprite_counter = 0 # initialize the sprite_counter
game_frame = 0 # initialize the game_frame counter

#direction control for player
flip = False

#direction control for enemy
face_left = True

clock = pygame.time.Clock() # initialize the clock object
player_pos = Vector2D(30, 240) # initial position of the player sprite
enemy_pos = Vector2D(450, 240) # initial position of the enemy sprite
game_speed = 70. # speed per second

#create the player sprite 
game_sprite = GameSprite(robot_sprite_sheet, w, h)

#the enemy sprite object
game_sprite_two = GameSprite(robot_sprite_sheet, w, h)

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    v = Vector2D(0., 0.) # reset the speed to zero on each pass
    
    pressed_keys = pygame.key.get_pressed() # get all the keys from key-press events 
        
    if pressed_keys[K_LEFT]: 
        flip = True # if the user pressed the left arrow key then makes the sprite object facing left
        v = Vector2D(-1., 0.) # move the object one unit to the left
        
    elif pressed_keys[K_RIGHT]:
        flip = False # if the user pressed the right arrow key then makes the sprite object facing right
        v = Vector2D(1., 0.) # move the object one unit to the right
            
    screen.fill((205, 200, 215))
    
    #blit the player with new position
    player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)
    game_sprite_surface = game_sprite.getImage(sprite_counter * 64, 0, flip) # get the new sprite surface
    screen.blit(game_sprite_surface, player_draw_pos)
    
    #blit the enemy with new position
    enemy_draw_pos = Vector2D(enemy_pos.x-w/2, enemy_pos.y-h/2)
    game_sprite_surface_two = game_sprite_two.getImage(sprite_counter * 64, 0, face_left) # get the new sprite surface
    screen.blit(game_sprite_surface_two, enemy_draw_pos)
    
    #increase the sprite counter after x numbers of frame
    if(game_frame % 50 == 0):
        
        sprite_counter += 1
        
        if(sprite_counter > 5):
            sprite_counter = 0
            
    game_frame += 1
    
    time_passed = clock.tick() #delta time for each frame
    time_passed_seconds = time_passed / 1000.0
       
    #set the player's new position after each frame
    player_pos+= v * game_speed * time_passed_seconds   
    
    #if player collides with enemy, enemy flips and changes it's direction
    if(game_sprite.detectCollide(game_sprite_two, player_draw_pos, enemy_draw_pos)):
        face_left = False
    
    if(face_left == True):
        enemy_pos += Vector2D(-1., 0.) * game_speed * time_passed_seconds 
    else:
        enemy_pos += Vector2D(1., 0.) * game_speed * time_passed_seconds
    
    pygame.display.flip()

By running the above module you will see that after a contact has occurred the enemy sprite will move in the opposite direction.


pygame.sprite.collide_rect method

How to move and flip the gaming character with Pygame

This tutorial will add the flip mechanism to the GameSprite module to flip the game character along the y axis as well as includes the script to move the game character along the x axis in our main game module.

In order to flip the gaming character we need to use the below method

pygame.transform.flip(sub_surface, self.flip, False)

where sub_surface is the surface return by the

self.sprite.subsurface(self.sprite.get_clip())

method which we have mentioned in our previous tutorial. Besides the sub_surface, the pygame.transform.flip method also takes in two other parameters, the first one is the xboolean uses to flip the surface along the y axis and the second one is the yboolean uses to flip the surface along the x axis, we will only use the xboolean in this example so the yboolean value is always set to False. By setting the xboolean to True or False we can flip the surface along the y axis which will make the game character facing left or right.

There is only one minor change to your original GameSprite module which is to add in another boolean parameter to it so you can use that boolean parameter to control the flipping x-direction.

import pygame

class GameSprite(object):
    
    def __init__(self, image, rect, flip):
        self.image = image
        self.rect = rect
        self.flip = flip
        self.sprite = pygame.image.load(image).convert_alpha()
        
    def getImage(self): # this method will return a subsurface which represents a portion of the spritesheet
        self.sprite.set_clip(self.rect) # clip a portion of the sprite with the rectangle object
        sub_surface = self.sprite.subsurface(self.sprite.get_clip())
        return pygame.transform.flip(sub_surface, self.flip, False)

In order to move our game character we need to get all the keys a user has pressed with this statement.

pressed_keys = pygame.key.get_pressed() # get all the keys from key-press events 

Then we will see whether the left arrow key is within the pressed_keys list or not.

if pressed_keys[K_LEFT]: 
        flip = True # if the user pressed the left arrow key then makes the object facing left
        v = Vector2D(-1., 0.) # move the object one unit to the left

Or else whether the right arrow key is within the pressed_keys list or not.

elif pressed_keys[K_RIGHT]:
        flip = False # if the user pressed the right arrow key then makes the object facing right
        v = Vector2D(1., 0.) # move the object one unit to the right

The rest of the script in our main game module is the same as the 1) animate the stand still sprite and the 2) moving the game sprite tutorial.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D
from game_sorite import GameSprite

robot_sprite_sheet = 'left.png'

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption("Pygame Demo")

w, h = 64, 64 # width and height of the sprite
sprite_counter = 0 # initialize the sprite_counter
game_frame = 0 # initialize the game_frame counter
flip = False

clock = pygame.time.Clock() # initialize the clock object
player_pos = Vector2D(320, 240) # initial position of the sprite
player_speed = 70. # speed per second

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    v = Vector2D(0., 0.) # reset the speed to zero on each pass
    
    pressed_keys = pygame.key.get_pressed() # get all the keys from key-press events 
        
    if pressed_keys[K_LEFT]: 
        flip = True # if the user pressed the left arrow key then makes the object facing left
        v = Vector2D(-1., 0.) # move the object one unit to the left
        
    elif pressed_keys[K_RIGHT]:
        flip = False # if the user pressed the right arrow key then makes the object facing right
        v = Vector2D(1., 0.) # move the object one unit to the right
            
    screen.fill((205, 200, 115))
    
    rect = Rect((sprite_counter * 64, 0), (64, 64)) # the rectangle object used to clip the sprite area 
    game_sprite = GameSprite(robot_sprite_sheet, rect, flip)
    game_sprite_surface = game_sprite.getImage() # get the sprite surface
    
    player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)
    screen.blit(game_sprite_surface, player_draw_pos)
    
    #increase the sprite counter after x numbers of frame
    if(game_frame % 30 == 0):
        sprite_counter += 1
        
        if(sprite_counter > 5):
            sprite_counter = 0
            
    game_frame += 1
    
    time_passed = clock.tick()
    time_passed_seconds = time_passed / 1000.0
            
    player_pos+= v * player_speed * time_passed_seconds   
    
    pygame.display.flip()

Now run the above module and press the left or right arrow key to see how the game character flip and move around.

How to create a stand still sprite animation with Pygame

In order to create a stand still sprite animation with Pygame we need to

  1. Create a GameSprite object which will take in the spritesheet and processes it then return a surface of the sprite which can then be used in the screen.blit method.
  2. Create a rectangle object which will be used to clip the area of the spritesheet.
  3. Create a mechanism which will change the sprite image as well as only change to the new sprite image after x numbers of frame so the sprite will not change that fast on the screen!

Before we start make sure you have created your own spritesheet with each sprite consists of 64px width and 64px height. Below is the spritesheet I have used in this tutorial, I am only using six out of the seven sprite from below spritesheet because the last sprite which is the jumping sprite is not required in this tutorial.

Animated Pygame Spritesheet
Pygame Spritesheet

Below is the GameSprite module which will be used in our next pygame module.

import pygame

class GameSprite(object):
    
    def __init__(self, image, rect):
        self.image = image
        self.rect = rect
        self.sprite = pygame.image.load(image).convert_alpha()
        
    def getImage(self): # this method will return a subsurface which is a portion of the spritesheet
        self.sprite.set_clip(self.rect) # clip a portion of the sprite with the rectangle object
        return self.sprite.subsurface(self.sprite.get_clip())

We can use the above module in this next module to create a stand still animation sprite.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D
from game_sorite import GameSprite

robot_sprite_sheet = 'left.png'

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption("Pygame Demo")

w, h = 64, 64 # width and height of the sprite
sprite_counter = 0 # initialize the sprite_counter
game_frame = 0 # initialize the game_frame counter

player_pos = Vector2D(320, 240) # initialize the position of the sprite

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    
    screen.fill((205, 200, 115))
    
    rect = Rect((sprite_counter * 64, 0), (64, 64)) # the rectangle object uses to clip the sprite area 
    game_sprite = GameSprite(robot_sprite_sheet, rect)
    game_sprite_surface = game_sprite.getImage() # get the sprite surface
    
    player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)
    screen.blit(game_sprite_surface, player_draw_pos)
    
    #increase the sprite counter after x numbers of frame
    if(game_frame % 30 == 0):
        sprite_counter += 1
        if(sprite_counter > 5):
            sprite_counter = 0
            
    game_frame += 1
    
    pygame.display.flip()

If you run the above script then you will get the below outcome…

Reference:

1. http://stackoverflow.com/questions/10560446/how-do-you-select-a-sprite-image-from-a-sprite-sheet-in-python

2. The Vector2D module has already been mentioned in the previous post

Create a base class and a subclass

If we want to create the same type of game entity over and over again (for example we need to create lots of enemy robot in our game) then it is better for us to create a base class first and then create the subclass from that base class. The subclass will have all the methods and properties from it’s base class plus it’s own method and property.

The base class on the other hand will derive from the python object class.

class GameEntity(object):
    
    def __init__(self, entity_id):
        self.id = entity_id
        
    def __str__(self):
        return "Entity id is %d" % (self.id)

Below subclass will derive from the base class.

from game_entity import GameEntity

class RobotEntity(GameEntity): 
     def __init__(self, entity_id):
        GameEntity.__init__(self, entity_id)
        self.strength = 50

Please take note that we need to call the __init__ method of the base class with below command.

GameEntity.__init__(self, entity_id)

If you create another module and run it with below script then we will get

from robot_entity import RobotEntity
if __name__ == "__main__":
    robot_entity = RobotEntity(1)
    print(robot_entity)
    print("Robot strength is %s" % robot_entity.strength)

the below outcome.

Entity id is 1
Robot strength is 50

Notice that the first print command above will trigger the __str__ method of the base class (GameEntity class) to return the “Entity id is 1” string which will later get printed out on the screen!

Playing background music with Pygame

Playing background music with Pygame is relatively simple with just a few lines of code. Before you can start playing a background music you will need to initialize the pygame.mixer interface first.

pygame.init()
pygame.mixer.pre_init(frequency, size, stereo, buffer)

As you can see from above the init method takes in a few parameters.

1) frequency is the sample rate of the audio playback and has the same meaning as the sample rate of music files.
2) size is the size, in bits, of the audio samples for the playback.
3) stereo only has two options : 1 for mono sound or 2 for stereo sound.
4) buffer is the number of samples that are buffered for playback.

Below is an example of how to initialize the mixer.

pygame.mixer.pre_init(44100, 16, 2, 5000)

In the above example we set the mixer to 16-bit, 44100Hz stereo music.

Now to play the background music all you need is these two lines.

sound1 = pygame.mixer.Sound("music.ogg")
channel1 = sound1.play(-1)

The first line takes the music file and create the sound object. The next line play the background music with an infinite number of time.

The channel object returns by the play method can then be used in other part of the code, for example we can

channel1.queue(sound1)

set up a queue by passing in a sound object which will be played after the main music has stopped.

How to use the mouse input in Pygame

Pygame allows us to get the mouse input with the pygame.mouse module. Below are three pygame.mouse module methods that we will often use to find the mouse input.

1) pygame.mouse.get_pressed will returns a tuple contains three 0 or 1 values for the left, middle, and right mouse buttons. If the button is pressed then the value will become 1 or else it will become 0.

2) pygame.mouse.get_pos will return the mouse coordinates as a tuple of the x and y values.

3) pygame.mouse.get_rel will return the relative mouse movement (or mickeys) as a tuple with the x and y relative movement.

Below script will print out the x and y coordinate of the mouse on the screen with the pygame.mouse.get_pos method.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)
font = pygame.font.SysFont("arial", 17);
font_height = font.get_linesize()

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()
    screen.fill((255, 255, 255))
    
    x, y = pygame.mouse.get_pos()
    
    text_surface = font.render("x coordinate : " + str(x) + " y coordinate : " + str(y), True, (0,0,0))
    screen.blit(text_surface, (10, font_height))
    
    pygame.display.update()

Mouse is more wonderful as compared to keyboard so make sure you have included the mouse factor in your game design. This article will conclude the keyboard and mouse chapter and now we are ready to move on into other chapter in Pygame.

Moving the sprite with Vector in Pygame

In this chapter we are going to use our newly created Vector2D class to move the game sprite within a pygame screen. What we are doing here is to add up a vector with another vector which will then create a new vector that will be use as the sprite’s new position. If the user presses any of these keys on the keyboard : left arrow, right arrow, up arrow or the down arrow key then a new unit vector will be created and by multiplying that unit vector with the speed and time we will get a new vector which can then be added up to the old one and becomes the new position of the sprite object.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D

pygame.init()

boat = 'boat.png'

screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption("Pygame Demo")

player = pygame.image.load(boat).convert_alpha()

clock = pygame.time.Clock()
player_pos = Vector2D(350, 350)
player_speed = 300.
v = Vector2D(0., 0.)

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    pressed_keys = pygame.key.get_pressed()
        
    if pressed_keys[K_UP]:
        v = Vector2D(0., -1.) 
    elif pressed_keys[K_DOWN]:
        v = Vector2D(0., 1.) 
    elif pressed_keys[K_LEFT]:
        v = Vector2D(-1., 0.) 
    elif pressed_keys[K_RIGHT]:
        v = Vector2D(1., 0.)
            
    screen.fill((255, 255, 255))
    
    player_draw_pos = Vector2D(player_pos.x, player_pos.y)
    screen.blit(player, player_draw_pos)
    
    time_passed = clock.tick()
    time_passed_seconds = time_passed / 1000.0
            
    player_pos+= v * player_speed * time_passed_seconds   
    pygame.display.update()

With that we are almost done with the vector part of the pygame tutorial and ready for the next chapter in pygame so make sure you visit this blog everyday to learn the complete pygame tutorial!

Rotate an object in Pygame

Rotating an object in python Pygame is relatively simple as compared to html5 or Java, all we need to do is to rotate an object with this pygame method.

rotated_player = pygame.transform.rotate(player, player_rotation)

The player_rotation is the rotation angle of the sprite and player is the surface of that sprite. We will use the Vector2D class which we have previously created in the below script

player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)
screen.blit(rotated_player, player_draw_pos)

to draw out the image and keep it in the memory but before that we need to turn the Vector2D class into a sub-class of the tuple class and overwrite the

__new__(cls, x=0.0, y=0.0)

method of the sub-class so the Vector2D class will return a tuple which can then be used inside the screen.blit method above.

Below is the revised edition of the Vector2D class.

import math

class Vector2D(tuple):
    
    def __new__(cls, x=0.0, y=0.0):
        return tuple.__new__(cls, (x, y))
    
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s, %s)"%(self.x, self.y)
    
    @classmethod
    def next_vector(cls, args):
        return cls(args[2]-args[0], args[3]-args[1])
    
    def get_magnitude(self):
        return math.sqrt( self.x**2 + self.y**2 )
    
    def normalize(self):
        magnitude = self.get_magnitude()
        self.x /= magnitude
        self.y /= magnitude
        
    # rhs stands for Right Hand Side
    def __add__(self, rhs):
        return Vector2D(self.x + rhs.x, self.y + rhs.y)
    
    def __sub__(self, rhs):
        return Vector2D(self.x - rhs.x, self.y - rhs.y)
    
    def __neg__(self):
        return Vector2D(-self.x, -self.y)
    
    def __mul__(self, scalar):
        return Vector2D(self.x * scalar, self.y * scalar)
    
    def __div__(self, scalar):
        return Vector2D(self.x / scalar, self.y / scalar)

Now let’s start to rotate an object with the below script.

#!/usr/bin/env python

import pygame
from pygame.locals import *
from sys import exit
from vector2d import Vector2D
from math import *

boat = 'boat.png'

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption("Pygame Rotation Demo")

player = pygame.image.load(boat).convert_alpha()

clock = pygame.time.Clock()
player_pos = Vector2D(300, 250)

player_rotation = 0.
player_rotation_speed = 360. # 360 degrees per second

while True:
   
    for event in pygame.event.get():
        
        if event.type == QUIT:
            exit()
            
    pressed_keys = pygame.key.get_pressed()
        
    rotation_direction = 0.
        
    if pressed_keys[K_LEFT]:
        rotation_direction = -1.0
    if pressed_keys[K_RIGHT]:
        rotation_direction = +1.0
            
    screen.fill((255, 255, 255))
    
    rotated_player = pygame.transform.rotate(player, player_rotation) # Return the rotated_player surface object
    w, h = rotated_player.get_size()
    player_draw_pos = Vector2D(player_pos.x-w/2, player_pos.y-h/2)
    screen.blit(rotated_player, player_draw_pos)
    
    time_passed = clock.tick()
    time_passed_seconds = time_passed / 1000.0
    
    player_rotation += rotation_direction * player_rotation_speed * time_passed_seconds
            
    pygame.display.update()

Basically what the above script does is to rotate the sprite in the clockwise or anti-clockwise direction when we press the right or the left arrow key. We will also move the top-left corner of the sprite back by half before we draw our sprite in the memory, this move is required to make sure the sprite object will be drawn correctly.

Below is the outcome of the above program.

Create a Vector class in Pygame

What is Vector?

Suppose we want to move from point A to point B where point A is situated at (33.0, 35.0) and point B is at (55.0, 45.0) then Vector AB will be the different between these two points, or the x and the y distance between this two points is (x2-x1, y2-y1) or (55.0-33.0, 45.0-35.0).

Why do we need to create a vector class?

Vector module helps game developer to perform various operations, for example moves an object from point A to point B as well as find out the vector magnitude of that object, therefore it is always better if we can create a Vector module before we create our game.

Create a Vector2D class in python

Vector class is just like any other module class with methods that we can use to move an object or modify the property of that object.

import math

class Vector2D(object):
    
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s, %s)"%(self.x, self.y)
    
    @classmethod
    def next_vector(cls, args):
        return cls(args[2]-args[0], args[3]-args[1])
    
    def get_magnitude(self):
        return math.sqrt( self.x**2 + self.y**2 )
    
    def normalize(self): # find the unit vector
        magnitude = self.get_magnitude()
        self.x /= magnitude
        self.y /= magnitude
        
    # rhs stands for Right Hand Side
    def __add__(self, rhs):
        return Vector2D(self.x + rhs.x, self.y + rhs.y)
    
    def __sub__(self, rhs):
        return Vector2D(self.x - rhs.x, self.y - rhs.y)
    
    def __neg__(self):
        return Vector2D(–self.x, –self.y)
    
    def __mul__(self, scalar):
        return Vector2D(self.x * scalar, self.y * scalar)
    
    def __div__(self, scalar):
        return Vector2D(self.x / scalar, self.y / scalar)

A few methods above are the overload methods where they will be called when the Vector class instance performs certain operation, for example the __div__, __mul__, __sub__ and __add__ method will be called when we divide, multiply, subtract and add two vectors together. The __neg__ method will be called if we want to point a Vector in the opposite direction.

The __init__ method will be called at the moment we initialized the Vector2D’s instance and __str__ will be called when we print that object with the python print function.

The get_magnitude method will return the magnitude of the Vector and the normalize method will divide the x and the y length of the Vector with it’s magnitude.

Finally next_vector will take in the combine value of two tuples and return a new Vector2D object.

Create a separate python module with below script.

from vector2d import Vector2D

if __name__ == "__main__":
    A = (10.0, 20.0)
    B = (30.0, 35.0)
    C = (15.0, 45.0)
    AB = Vector2D.next_vector(A+B)
    BC = Vector2D.next_vector(B+C)
    AC = AB+BC
    print(AC)
    AC = Vector2D.next_vector(A+C)
    print(AC)

If you run the above module then you can see that when you add up two vectors AC = AB + BC the overload __add__ method of vector AB will be called which will then return a new Vector2D object. AC = Vector2D.next_vector(A+C) will create the same outcome as AC = AB + BC when we print the vector out with the print function. In this example the result is (5.0, 25.0).

The above Vector2D function will get you started where you can now include more methods into the Vector2D module for the future expansion purposes.