Pygame4
In this article we will organize the code we have so far and add walls to our game. By the end of this article you will have all the tools you need to make a maze game!
Download the entire Pygame4 folder for everything discussed in this article.
House Cleaning.
Only four articles in and we have already implemented movement, items, and an inventory. We also have accumulated quite a lot of code in our MainGame.py file. Before we continue, I think it is important to organize our classes and script to make further improvements easier.
First, create a new Python file titled 'PlayerClasses.py' in the same directory as 'MainGame.py'. On the first two lines of this script import pygame, sys, and pygame.locals like this:
(2 lines) codebit 1
import pygame, sys from pygame.locals import *
Skip a line, then copy and paste the entire Player class from 'MainGame.py' to 'PlayerClasses.py'.
Now create another file called 'ItemsClasses.py' and add the import statements from codebit 1 on the first two lines of the script. Copy and paste the Item class into 'ItemsClasses.py'.
Back in the 'MainGame.py' file we need to import these two modules like this:
(2 lines) codebit 2
from ItemsClasses import * from PlayerClasses import *
I also changed the method names of the Player and Item classes in an attempt to better correspond with Python naming conventions.
For the Player class I changed the following method names:
loadImages ---> load_images
moveSprite ---> move_sprite
movementCheck ---> move_check
chooseImageNum ---> choose_imageNum
itemsCollision ---> items_coll
updateInventory ---> update_inventory
For the Item class I changed the following method names:
loadSound ---> load_sound
playSound ---> play_sound
pickUp ---> pick_up
Make sure you not only changed the method names, but also any calls to these methods in the update, __init__, render, pick_up, and items_coll methods.
Run the game (MainGame.py) and make sure everything is working as before.
Lastly, we need to fix a bug where the player continues moving after the inventory menu is closed. To do this, all we need to do is adjust the event loop so the player doesn't move when 'e' is pressed.
(4 lines) codebit 3
if event.key==pygame.K_e: moveX = 0 moveY = 0 inventoryMenu()
Invisible Brick Walls.

Right now the player can just walk all over the screen, and out of it, freely. This might be okay for some kinds of games, but for this game it's not exactly ascetically pleasing to see the character trampling over the tops of the trees. To block the player from exiting, or walking to undesired parts of, the screen, we will add invisible walls that can be easily placed anywhere the player is not meant to access.
We will make these walls sprites. They will be invisible and they will detect player collision to prevent movement. This is the Wall class and its __init__ method:
(7 lines) codebit 4
class Wall(pygame.sprite.Sprite): def __init__(self,x,y,width,height): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface([width, height]) self.rect = self.image.get_rect() self.rect.y = y self.rect.x = x
Add this codebit after the Items class definition in 'ItemsClasses.py'.
One difference between the Wall sprite and the other sprites is that self.image is not actually an image as the surface. We create a regular Surface object using pygame.Surface([width, height]) insted.
The Pygame Surface class is very complex and has many features. You can copy, fill, and scroll(good for platforming games) surfaces, among many other things. Check out the documentation for more on the Surface class.
Now add the following get methods to the class:
(26 lines) codebit 5
def get_x(self): return self.rect.x def get_y(self): return self.rect.y def get_rect(self): return self.rect def get_bottom(self): return self.rect.bottom def get_top(self): return self.rect.top def get_left(self): return self.rect.left def get_right(self): return self.rect.right def get_width(self): return self.rect.width def get_height(self): return self.rect.height
These are get methods used by other classes and functions to get specific variables of a wall object. They all return information about the Surface object's rect.
That's actually all there is to the Wall class! Its quite simple since its only job is to be a place holder for areas the player cannot reach. This class doesn't need a draw or update method since all it will do is take up space.
Now we need to edit the Player class a bit so it recognizes wall collisions. Add these two methods after the items_coll method:
(15 lines) codebit 6
def walls_coll_x(self, walls): collisionList = pygame.sprite.spritecollide(self, walls, False) for collision in collisionList: if self.left == False: self.rect.right = collision.get_left() else: self.rect.left = collision.get_right() def walls_coll_y(self, walls): collisionList = pygame.sprite.spritecollide(self, walls, False) for collision in collisionList: if self.down == True: self.rect.bottom = collision.get_top() else: self.rect.top = collision.get_bottom()
walls_coll_x takes one argument, a sprite group of walls, and checks for collisions. The list of collisions is saved to the collisionList variable and then for each collision we run a test. If the player is moving left at the time of the collision with the wall, we move the right side of the character to the left side of the wall. If the player is moving right at the time of the collision, we move the left side of the character to the right side of the wall.
walls_coll_y is very similar except it tests to see if the player is moving down or up at the time of the collision. self.down is a boolean of whether or not the character is moving down or up, we will adjust the move_check method to handle this variable. If the player is moving down at the time of the collision, we move the bottom of the character to the top of the wall. If the player is moving up at the time of the collision, we move the top of the character to the bottom of the wall.
Now we need to add the self.down variable and its test to our player class. In the Player class's __init__ method, after self.left = False, add this:
(1 line) codebit 7
self.down = False
Then adjust the move_check method to look like this:
(13 lines) codebit 8
def move_check(self, oldx, oldy): if self.rect.x == oldx and self.rect.y == oldy: self.moving = False else: self.moving = True if self.rect.x > oldx: self.left = False elif self.rect.x < oldx: self.left = True if self.rect.y > oldy: self.down = True elif self.rect.y < oldy: self.down = False
All we did was add four more lines to check if the current y is greater than the old y or if the current y is less than the old y. Depending on which of these is true, if either, we set self.down equal to True or equal to False.
We need to call the wall collision methods every time the player has moved. Adjust the move_sprite method like this:
(8 lines) codebit 9
def move_sprite(self, movex, movey, walls): oldX = self.rect.x oldY = self.rect.y self.rect.x += movex self.walls_coll_x(walls) self.rect.y += movey self.walls_coll_y(walls) self.move_check(oldX, oldY)
First, we added a new argument to the move_sprite method. walls is a group of sprite walls that will be tested for collisions in the walls_coll methods.
Second, after the player moves on the x axis, walls_coll_x is called to test for collisions with walls.
Third, after the player moves on the y axis, walls_coll_y is called to test for collisions with walls.
You can probably guess what method will need adjusting next. The only method that calls move_sprite is the update method, adjust it as follows:
(4 lines) codebit 10
def update(self, movex, movey, items, walls): self.move_sprite(movex, movey, walls) self.items_coll(items) self.render()
Here we added walls to the required parameters for the function, and then passed the wall parameter on to move_sprite. move_sprite then passes walls to the new collision testing methods.
Be sure to save the 'PlayerClasses.py' and 'ItemClasses.py' files.
Putting the walls up!
Now we have the schematics for the walls. We just need to build them in our game!
We need to create a new sprite group for walls, create wall sprites, and put the sprites in the group. Right after the line that adds item sprites to the item group, add the following:
(6 lines) codebit 11
walls = pygame.sprite.Group() wall = Wall(0,450,1000,10) wall1 = Wall(0,683,1000,10) wall2 = Wall(0,450,5,300) wall3 = Wall(1020,450,5,300) walls.add(wall, wall1, wall2, wall3)
These walls make an invisible box around the player that prevents the player from crossing the red lines in the picture below.
Before you run the game to see (well not really see) the walls in action, adjust the player.update call in the game loop to pass in the walls sprite group. It should look like this:
(1 line) codebit 12
player.update(moveX,moveY,items,walls)
Now, after you have saved all three game files, run the 'MainGame.py' file. The player is trapped in an invisible box. Try as much as you want, but the player is trapped for now. Soon we will add ways to make walls disappear under certain conditions, and we will allow the player to travel to different levels.
If the walls did not work, make sure you saved all the game files and that you placed the walls in the correct locations.
You may have noticed I moved the items around in the picture above! Move the items in your game so the player can reach them.
Now that you know how to make walls in Pygame, I challenge you to make a small maze game that lets the player move around the maze and pick up items!
If you are wondering how to make the walls not invisible in your maze game, that can be done by simply using the draw method on the group of walls(walls.draw(DISPLAYSURF)). You can also change the color of the walls by using the fill method, similar to how we filled the display surface in the first article.
Where can I find the PYTHON4 folder?
ReplyDeleteDo you mean the Pygame4 folder? It can be found at https://drive.google.com/open?id=0B3L3qR8KIa8dWHZwYlpJMDdPUHc
Delete