Difference between revisions of "PyGame Platform"
(→Load in Player image) |
|||
(13 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | This will show you how to create a platform game using a tiled map, and rectangular based collision detection. | + | This will show you how to create a platform game using a tiled map, and rectangular based collision detection. '''If you wanted to create this for your project you should make it Object Oriented.''' |
=Create Map= | =Create Map= | ||
Line 18: | Line 18: | ||
[http://www.gamefromscratch.com/post/2014/04/15/A-quick-look-at-Tiled-An-open-source-2D-level-editor.aspx Written Version of Above Tutorials] | [http://www.gamefromscratch.com/post/2014/04/15/A-quick-look-at-Tiled-An-open-source-2D-level-editor.aspx Written Version of Above Tutorials] | ||
− | |||
===New Tiled Map=== | ===New Tiled Map=== | ||
Line 103: | Line 102: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
for layer in tiled_map.layers: | for layer in tiled_map.layers: | ||
− | if isinstance(layer, pytmx.TiledTileLayer | + | if isinstance(layer, pytmx.TiledTileLayer): |
for x, y, tile in layer.tiles(): | for x, y, tile in layer.tiles(): | ||
if (tile): | if (tile): | ||
Line 129: | Line 128: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
for layer in tiled_map.layers: | for layer in tiled_map.layers: | ||
− | if isinstance(layer, pytmx.TiledTileLayer | + | if isinstance(layer, pytmx.TiledTileLayer): |
for x, y, tile in layer.tiles(): | for x, y, tile in layer.tiles(): | ||
if (tile): | if (tile): | ||
Line 140: | Line 139: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | = | + | =Falling= |
− | + | Falling is the key part of a platform game, the player is essentially always falling but colliding with the tile below. Firstly we need to create some additional variables: | |
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
− | + | fallspeed = 1 | |
− | for | + | </syntaxhighlight> |
− | + | ||
− | + | When you fall, you accelerate and speed up as you fall further. You could spend a lot of time adding values for the mass of the player/object, air resistance etc but i will simply increment the fallspeed every run through the game loop. When you land on a tile the fall speed will be set back to 1. So find your game loop, and add: | |
− | |||
− | + | <syntaxhighlight lang=python> | |
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
+ | #From this point we need to check for hitting the ground | ||
+ | #If we have set pos[1] to 0 and fallspeed to 1 | ||
+ | #Get player movement | ||
+ | #check for the player hitting tiles on the left & right | ||
+ | #if we have set pos[0] to 0 | ||
− | + | #Set new player position | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
tiled_map.get_object_by_name("Player").x += pos[0] | tiled_map.get_object_by_name("Player").x += pos[0] | ||
− | tiled_map.get_object_by_name("Player").y += pos[1] | + | tiled_map.get_object_by_name("Player").y += pos[1] |
</syntaxhighlight> | </syntaxhighlight> | ||
− | =Checking | + | At this point your player should fall through the floor. |
+ | |||
+ | =Checking Ground= | ||
Earlier you added a line to read the collision layer from the map: | Earlier you added a line to read the collision layer from the map: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
− | collision = tiled_map.get_layer_by_name(' | + | collision = tiled_map.get_layer_by_name('Tiles') |
</syntaxhighlight> | </syntaxhighlight> | ||
Line 175: | Line 176: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
− | collision = tiled_map.get_layer_by_name(' | + | collision = tiled_map.get_layer_by_name('Tiles') |
tiles = [] | tiles = [] | ||
for x, y, tile in collision.tiles(): | for x, y, tile in collision.tiles(): | ||
Line 186: | Line 187: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
#tiles is a list of all the tiles in the collision layer | #tiles is a list of all the tiles in the collision layer | ||
− | def | + | def checktiles(playerrec): |
check = False | check = False | ||
if (playerrec.collidelistall(tiles)): #this tests every tile with the player rectangle | if (playerrec.collidelistall(tiles)): #this tests every tile with the player rectangle | ||
Line 196: | Line 197: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
+ | #From this point we need to check for hitting the ground | ||
+ | #If we have set pos[1] to 0 and fallspeed to 1 | ||
+ | #Get player movement | ||
+ | #check for the player hitting tiles on the left & right | ||
+ | #if we have set pos[0] to 0 | ||
+ | |||
+ | #Set new player position | ||
+ | tiled_map.get_object_by_name("Player").x += pos[0] | ||
+ | tiled_map.get_object_by_name("Player").y += pos[1] | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Add these additional lines, these create a rectangle for where the player will move to, and then passes this into the checkbounds method: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
+ | #Create rectangle for the player | ||
+ | x = tiled_map.get_object_by_name("Player").x | ||
+ | y = tiled_map.get_object_by_name("Player").y+pos[1] #checks where it will be not where it is | ||
+ | w = tiled_map.get_object_by_name("Player").width | ||
+ | h = tiled_map.get_object_by_name("Player").height | ||
+ | playerrec = pygame.Rect([x,y,w,h]) | ||
+ | |||
+ | #Check player rectangle with tiles | ||
+ | #If collision cancel movement | ||
+ | if(checktiles(playerrec)): | ||
+ | pos[1] = 0 | ||
+ | |||
+ | #Get player movement | ||
+ | #check for the player hitting tiles on the left & right | ||
+ | #if we have set pos[0] to 0 | ||
+ | |||
+ | #Set new player position | ||
+ | tiled_map.get_object_by_name("Player").x += pos[0] | ||
+ | tiled_map.get_object_by_name("Player").y += pos[1] | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | If a collision happens we can ignore the movement by resetting pos for the height back to 0. | ||
+ | |||
+ | =Jumping= | ||
+ | |||
+ | Jumping is relatively straight forward, we just need to minus a value from the current y position when a key is pressed. So inside your game loop, find this code: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | and add these lines: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
+ | PRESSED = pygame.key.get_pressed() | ||
+ | |||
+ | if PRESSED[pygame.K_UP]: | ||
+ | pos[1]-=64 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | =Move Player in Map= | ||
+ | In the game loop we need to check for left and right movement, so find this code: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
PRESSED = pygame.key.get_pressed() | PRESSED = pygame.key.get_pressed() | ||
− | |||
− | |||
− | |||
− | |||
if PRESSED[pygame.K_UP]: | if PRESSED[pygame.K_UP]: | ||
− | pos[1]-= | + | pos[1]-=64 |
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | And add these extra keys: | |
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
+ | #set pos for falling | ||
+ | pos = [0,fallspeed] | ||
+ | fallspeed+=1 | ||
+ | |||
PRESSED = pygame.key.get_pressed() | PRESSED = pygame.key.get_pressed() | ||
Line 218: | Line 293: | ||
pos[0]+=10 | pos[0]+=10 | ||
if PRESSED[pygame.K_UP]: | if PRESSED[pygame.K_UP]: | ||
− | pos[1]-= | + | pos[1]-=64 |
− | + | ||
− | + | </syntaxhighlight> | |
+ | Now your character can move left and right, but they can walk through walls. We need to create another rectangle for the position the player will move to, we can then check for collisions and reset the pos[0] value back to zero. So find this code: | ||
+ | <syntaxhighlight lang=python> | ||
#Create rectangle for the player | #Create rectangle for the player | ||
− | x = tiled_map.get_object_by_name("Player").x | + | x = tiled_map.get_object_by_name("Player").x |
y = tiled_map.get_object_by_name("Player").y+pos[1] | y = tiled_map.get_object_by_name("Player").y+pos[1] | ||
w = tiled_map.get_object_by_name("Player").width | w = tiled_map.get_object_by_name("Player").width | ||
Line 229: | Line 306: | ||
playerrec = pygame.Rect([x,y,w,h]) | playerrec = pygame.Rect([x,y,w,h]) | ||
− | + | #Check player rectangle with ground | |
− | + | #If collision cancel movement | |
− | + | if(checktiles(playerrec)): | |
− | + | pos[1] = 0 | |
+ | fallspeed = 0 | ||
+ | jumping = False | ||
+ | jumpspeed = 8 | ||
+ | |||
+ | #Get player movement | ||
+ | #check for the player hitting tiles on the left & right | ||
+ | #if we have set pos[0] to 0 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Straight after this add the code below to create the new rectangle and perform the checks: | |
− | = | + | <syntaxhighlight lang=python> |
− | + | #check player movement with the sides | |
+ | x = tiled_map.get_object_by_name("Player").x+pos[0] | ||
+ | y = tiled_map.get_object_by_name("Player").y+5 #5 added so very top isn't touching a tile | ||
+ | w = tiled_map.get_object_by_name("Player").width | ||
+ | h = tiled_map.get_object_by_name("Player").height -10 #10 added, 5 for the top and 5 so the very bottom isn't checked | ||
+ | playerrec = pygame.Rect([x,y,w,h]) | ||
+ | if(checktiles(playerrec)): | ||
+ | pos[0] = 0 | ||
+ | </syntaxhighlight> | ||
− | + | You should now have a player that can jump, move, and collide. | |
− | + | =Collectables= | |
− | + | Earlier, you have added coins to your map we will now load in an image to use and then create a list of objects: | |
− | |||
==Read Coin Image== | ==Read Coin Image== | ||
Line 283: | Line 374: | ||
<syntaxhighlight lang=python> | <syntaxhighlight lang=python> | ||
#Create a list of collectables | #Create a list of collectables | ||
− | objects = tiled_map.get_layer_by_name(' | + | objects = tiled_map.get_layer_by_name('Objects') |
items = [] | items = [] | ||
for object in objects: | for object in objects: | ||
Line 297: | Line 388: | ||
def checkcoins(playerrec): | def checkcoins(playerrec): | ||
for item in items: | for item in items: | ||
− | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | + | if(item.type=='Coin'): |
− | + | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | |
− | + | if (coinrec.colliderect(playerrec) and (item.visible!=0)): | |
+ | item.visible = 0 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 317: | Line 409: | ||
[[File:Plat key door.gif]] | [[File:Plat key door.gif]] | ||
+ | |||
+ | ==Check Key code== | ||
+ | You could either add code to check the key into the current checkcoins method or just create a separate method. We need to create a list of the objects, this will be for keys and doors. This might seem overkill when there is just one key and one door, however you could expand you map to have many keys and doors. So find your checkcoins method: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | def checkcoins(playerrec): | ||
+ | for item in items: | ||
+ | if(item.type=='Coin'): | ||
+ | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (coinrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | We will extend this to also check for keys: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | def checkcoins(playerrec): | ||
+ | for item in items: | ||
+ | if(item.type=='Coin'): | ||
+ | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (coinrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 | ||
+ | elif(item.type=='Key'): | ||
+ | keyrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (keyrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ==Extra Logic== | ||
+ | At the moment we need to count how many coins have been collected, this will be used to then make the key visible. So declare a new variable, before the game loop, called coincount and set it to be 0: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | coincount = 0 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Now in he checkcoin method we need to increment the coincount, and check when enough have been collected. Find this code: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | if(item.type=='Coin'): | ||
+ | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (coinrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | We need to add the following bits after item.visible = 0: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | if(item.type=='Coin'): | ||
+ | coinrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (coinrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible=0 | ||
+ | global coincount | ||
+ | coincount = coincount+1 | ||
+ | if (coincount==3): | ||
+ | coincount = 0 | ||
+ | print("show key") | ||
+ | for item in keydoor: | ||
+ | if (item.name=="Key"): | ||
+ | item.visible = 1; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | the code global coincount will allow access to the coincount variable, without this line we couldn't access coincount. The code increments coincount and then check to see if the coincount is equal to 3. If it is, get the key from keyDoor and change the visibility. This will now display the key, and currently when you collect the key it just sets the key to be invisible. | ||
+ | |||
+ | ==Key Unlocks Door== | ||
+ | We need to add a variable to the program to say if the key is collected or not: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | KeyCollect = False | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | At the moment the main thing preventing the player from leaving the map are the bounds we have in the collision layer. So we will get the the position of the door, find the tiles it is over and remove them from the collision layer, and finally we need to make the door invisible. Firstly find the checkcoin method, and find the key checker and add the KeyCollect = True line: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | elif(item.type=='Key'): | ||
+ | keyrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (keyrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 | ||
+ | global KeyCollect | ||
+ | KeyCollect = True | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Add a new elif to check for the door: | ||
+ | |||
+ | <syntaxhighlight lang=python> | ||
+ | elif(item.type=='Door') and (KeyCollect): | ||
+ | doorrec = pygame.Rect([item.x, item.y, item.width, item.height]) | ||
+ | if (dorrec.colliderect(playerrec) and (item.visible!=0)): | ||
+ | item.visible = 0 #or move elsewhere on the map, or load another map | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Notice it also checks to see if the key is collected. The item.visible line could be any code to move the player or move to another map etc. |
Latest revision as of 09:12, 9 July 2018
This will show you how to create a platform game using a tiled map, and rectangular based collision detection. If you wanted to create this for your project you should make it Object Oriented.
Contents
Create Map
Tiled
You will firstly need to install the Tiled program from the website and link below. In college the Tiled executeables are on moodle, under project, technical skill, monogame, and tiled. I have also added links to other tutorials for using Tiled.
Tiled Website and Download
Tutorials for using Tiled
Written Version of Above Tutorials
New Tiled Map
You will need a new tiled map, the tile size in the screen shot is 128 pixels, in the end i actually changed this to 32 x 32 and resized the tiles accordingly:
Add Tile Set
Now your map is created we need to add a tile set:
I would always recommend you make sure to choose embed and based on tileset image:
Draw Your Level
Now you have a tile set build a simple set of platforms. You should rename the layer to something like Tiles:
Add Player Object
Now you have a section of platforms, we can now set the position of the player. This will also be the object moved by the code and player input. So insert an object layer, and then use the rectangle tool to create the object. My final player was 60 pixels high and 40 pixels wide:
Collectables
Now within the objects layer create 3 new objects for the coins. I have created 3 new objects called Coin1 , Coin2 , and Coin 3. I have also set the type to Coin and the height & width of each object to 16 pixels:
Now, click on the Object layer and in the properties panel add a custom property (+ symbol in bottom left corner). Make an integer called Coin_Count:
PyGame Project
Create a new pygame project, you could use THIS template. You will need to copy the TMX map you have created and the tilesheet image into the same folder as the python program.
Install PyTMX
The current version of writing this is 3.21.5, you need to select Tools, Python, Python Environments.
Select Packages and search for PyTMX, this page will show you how to use PyTMX. Other TMX solutions exist and i will overtime have a look to see which are the best.
Import PyTMX
We need to import PyTMX, you need to add the following lines to the start of your code:
import pytmx
from pytmx.util_pygame import load_pygame
load_pygame is a method used to load in the map and tileset.
Read in Map
Make sure your map is within the same folder as your code, and that your tileset image is also in the same folder. Obviously make sure you change the name to your tmx file:
tiled_map = load_pygame('SimplePlatform.tmx')
tilewidth = tiled_map.tilewidth
tileheight = tiled_map.tileheight
If you are using tiles with transparent sections, you will need to change the code to load the map to this:
tiled_map = load_pygame('SimplePlatform.tmx', pixelalpha=True)
We will also need to read in the collision layer, this will be used to restrict player movement and shouldn't get drawn:
collision = tiled_map.get_layer_by_name('Tiles')
Load in Player image
We will also need to load in an image to use for the player object:
#Player image
player = pygame.image.load(os.path.join("hero.png")).convert_alpha(); # load in coin image, convert_alpha will keep transparent background
player = pygame.transform.scale(player, (40, 60)) # resize player image, this should be the same size as the map object
Draw the Map
Now, within your game loop we need to draw the map. We can do this by cycling through the layers within the map. A for loop is created, and it iterates for each layer. It is important to test if the layer is a Tile layer first. If it is a different layer we will need to draw it differently. Once we have a Tile layer, we can create a for loop to cycle through every tile. If the tile has a value we can then blit it to the screen. The location is calculated using the x & y values:
for layer in tiled_map.layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, tile in layer.tiles():
if (tile):
SCREEN.blit(tile, [x*tilewidth,y*tileheight])
elif isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:
if (object.type=='Player'):
SCREEN.blit(player, (object.x, object.y))
pygame.display.update()
The elif will be accessed if the layer is an object layer. We can then cycle through each object, and if the object has an image we can blit it to the screen at the x & y of the object.
Center Map on Object
You can read an object from your map, this could then be used to center the map to this object. The object has an x & y coordinate and these can be used to essentially set the camera position over your map:
CAMERA = tiled_map.get_object_by_name("Player")
We can now use the object to change the drawing code. If you minus the camera x & y coordinates from the drawing coordinates, you will have your centred position in the top corner. You therefore need to add half of the screen width and height to have it in the centre of the screen. You will also need to change the code to draw each object:
for layer in tiled_map.layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, tile in layer.tiles():
if (tile):
SCREEN.blit(tile, [(x*tilewidth) - CAMERA.x +(SCREENWIDTH/2) , (y*tileheight) - CAMERA.y + (SCREENHEIGHT/2)])
elif isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:
if (object.type=='Player'):
SCREEN.blit(player, [object.x - CAMERA.x +(SCREENWIDTH/2), object.y - CAMERA.y + (SCREENHEIGHT/2)])
Falling
Falling is the key part of a platform game, the player is essentially always falling but colliding with the tile below. Firstly we need to create some additional variables:
fallspeed = 1
When you fall, you accelerate and speed up as you fall further. You could spend a lot of time adding values for the mass of the player/object, air resistance etc but i will simply increment the fallspeed every run through the game loop. When you land on a tile the fall speed will be set back to 1. So find your game loop, and add:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
#From this point we need to check for hitting the ground
#If we have set pos[1] to 0 and fallspeed to 1
#Get player movement
#check for the player hitting tiles on the left & right
#if we have set pos[0] to 0
#Set new player position
tiled_map.get_object_by_name("Player").x += pos[0]
tiled_map.get_object_by_name("Player").y += pos[1]
At this point your player should fall through the floor.
Checking Ground
Earlier you added a line to read the collision layer from the map:
collision = tiled_map.get_layer_by_name('Tiles')
Add the following code to extend this, it creates an empty list and the cycles through each tile in the collision layer. If there is a tile it will add it to the tiles list. This will be used to check the player position:
collision = tiled_map.get_layer_by_name('Tiles')
tiles = []
for x, y, tile in collision.tiles():
if (tile):
tiles.append(pygame.Rect([(x*tilewidth), (y*tileheight), tilewidth, tileheight]));
Now create this method, it accepts the player rectangle and then checks this with the list of tiles:
#tiles is a list of all the tiles in the collision layer
def checktiles(playerrec):
check = False
if (playerrec.collidelistall(tiles)): #this tests every tile with the player rectangle
check = True
return check
Finally we need to edit the game loop, so find the following:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
#From this point we need to check for hitting the ground
#If we have set pos[1] to 0 and fallspeed to 1
#Get player movement
#check for the player hitting tiles on the left & right
#if we have set pos[0] to 0
#Set new player position
tiled_map.get_object_by_name("Player").x += pos[0]
tiled_map.get_object_by_name("Player").y += pos[1]
Add these additional lines, these create a rectangle for where the player will move to, and then passes this into the checkbounds method:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
#Create rectangle for the player
x = tiled_map.get_object_by_name("Player").x
y = tiled_map.get_object_by_name("Player").y+pos[1] #checks where it will be not where it is
w = tiled_map.get_object_by_name("Player").width
h = tiled_map.get_object_by_name("Player").height
playerrec = pygame.Rect([x,y,w,h])
#Check player rectangle with tiles
#If collision cancel movement
if(checktiles(playerrec)):
pos[1] = 0
#Get player movement
#check for the player hitting tiles on the left & right
#if we have set pos[0] to 0
#Set new player position
tiled_map.get_object_by_name("Player").x += pos[0]
tiled_map.get_object_by_name("Player").y += pos[1]
If a collision happens we can ignore the movement by resetting pos for the height back to 0.
Jumping
Jumping is relatively straight forward, we just need to minus a value from the current y position when a key is pressed. So inside your game loop, find this code:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
and add these lines:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
PRESSED = pygame.key.get_pressed()
if PRESSED[pygame.K_UP]:
pos[1]-=64
Move Player in Map
In the game loop we need to check for left and right movement, so find this code:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
PRESSED = pygame.key.get_pressed()
if PRESSED[pygame.K_UP]:
pos[1]-=64
And add these extra keys:
#set pos for falling
pos = [0,fallspeed]
fallspeed+=1
PRESSED = pygame.key.get_pressed()
if PRESSED[pygame.K_LEFT]:
pos[0]-=10
elif PRESSED[pygame.K_RIGHT]:
pos[0]+=10
if PRESSED[pygame.K_UP]:
pos[1]-=64
Now your character can move left and right, but they can walk through walls. We need to create another rectangle for the position the player will move to, we can then check for collisions and reset the pos[0] value back to zero. So find this code:
#Create rectangle for the player
x = tiled_map.get_object_by_name("Player").x
y = tiled_map.get_object_by_name("Player").y+pos[1]
w = tiled_map.get_object_by_name("Player").width
h = tiled_map.get_object_by_name("Player").height
playerrec = pygame.Rect([x,y,w,h])
#Check player rectangle with ground
#If collision cancel movement
if(checktiles(playerrec)):
pos[1] = 0
fallspeed = 0
jumping = False
jumpspeed = 8
#Get player movement
#check for the player hitting tiles on the left & right
#if we have set pos[0] to 0
Straight after this add the code below to create the new rectangle and perform the checks:
#check player movement with the sides
x = tiled_map.get_object_by_name("Player").x+pos[0]
y = tiled_map.get_object_by_name("Player").y+5 #5 added so very top isn't touching a tile
w = tiled_map.get_object_by_name("Player").width
h = tiled_map.get_object_by_name("Player").height -10 #10 added, 5 for the top and 5 so the very bottom isn't checked
playerrec = pygame.Rect([x,y,w,h])
if(checktiles(playerrec)):
pos[0] = 0
You should now have a player that can jump, move, and collide.
Collectables
Earlier, you have added coins to your map we will now load in an image to use and then create a list of objects:
Read Coin Image
you have already added the code to load in the player image, so under this you could also load in the coin image:
#Coin image
coin = pygame.image.load(os.path.join("Coin.png")).convert_alpha(); # load in coin image, convert_alpha will keep transparent background
coin = pygame.transform.scale(coin, (16, 16)) # resize coin image
Draw Coins
In the code to draw the map, we are drawing player objects:
elif isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:#for each object, if it is a coin or player then draw it
if(object.type=="Player"):
SCREEN.blit(player, [object.x - CAMERA.x +(SCREENWIDTH/2), object.y - CAMERA.y + (SCREENHEIGHT/2)])
We need to extend the if statement to also check if the object.type is a Coin:
elif isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:#for each object, if it is a coin or player then draw it
if (object.type=='Coin'):
SCREEN.blit(coin, [object.x - CAMERA.x +(SCREENWIDTH/2), object.y - CAMERA.y + (SCREENHEIGHT/2)])
elif(object.type=="Player"):
SCREEN.blit(player, [object.x - CAMERA.x +(SCREENWIDTH/2), object.y - CAMERA.y + (SCREENHEIGHT/2)])
Now coins will be drawn in position on your map.
List of Collectables
You now need to read in each of the objects into a list, this code must go after you read in the map and shouldn't be in the game loop or method:
#Create a list of collectables
objects = tiled_map.get_layer_by_name('Objects')
items = []
for object in objects:
items.append(object)
This code creates an empty list for the items, you read in the object layer into objects. Then you cycle through each object and add it to the list.
Check Coins
Now we have a list of items we can check the position of each one and the player:
def checkcoins(playerrec):
for item in items:
if(item.type=='Coin'):
coinrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (coinrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
this accepts a rectangle for the player, and then for each item it creates a rectangle and then checks for a collision. If a collision has occurred this sets the item.visible to 0. This will stop it from been drawn. Notice we also check that the item.visibile is not equal to 0, this is so we can't collect something invisible.
Adding to Game Loop
Now in the game loop add this to check for collectables:
#check coins with the current position of the player
checkcoins(pygame.Rect([tiled_map.get_object_by_name("Player").x,tiled_map.get_object_by_name("Player").y,
tiled_map.get_object_by_name("Player").width,tiled_map.get_object_by_name("Player").height]))
Key & Door
Edit your tiled map by adding a key object into the objects layer, and also place a door object in your main layer which represents your door:
Check Key code
You could either add code to check the key into the current checkcoins method or just create a separate method. We need to create a list of the objects, this will be for keys and doors. This might seem overkill when there is just one key and one door, however you could expand you map to have many keys and doors. So find your checkcoins method:
def checkcoins(playerrec):
for item in items:
if(item.type=='Coin'):
coinrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (coinrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
We will extend this to also check for keys:
def checkcoins(playerrec):
for item in items:
if(item.type=='Coin'):
coinrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (coinrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
elif(item.type=='Key'):
keyrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (keyrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
Extra Logic
At the moment we need to count how many coins have been collected, this will be used to then make the key visible. So declare a new variable, before the game loop, called coincount and set it to be 0:
coincount = 0
Now in he checkcoin method we need to increment the coincount, and check when enough have been collected. Find this code:
if(item.type=='Coin'):
coinrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (coinrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
We need to add the following bits after item.visible = 0:
if(item.type=='Coin'):
coinrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (coinrec.colliderect(playerrec) and (item.visible!=0)):
item.visible=0
global coincount
coincount = coincount+1
if (coincount==3):
coincount = 0
print("show key")
for item in keydoor:
if (item.name=="Key"):
item.visible = 1;
the code global coincount will allow access to the coincount variable, without this line we couldn't access coincount. The code increments coincount and then check to see if the coincount is equal to 3. If it is, get the key from keyDoor and change the visibility. This will now display the key, and currently when you collect the key it just sets the key to be invisible.
Key Unlocks Door
We need to add a variable to the program to say if the key is collected or not:
KeyCollect = False
At the moment the main thing preventing the player from leaving the map are the bounds we have in the collision layer. So we will get the the position of the door, find the tiles it is over and remove them from the collision layer, and finally we need to make the door invisible. Firstly find the checkcoin method, and find the key checker and add the KeyCollect = True line:
elif(item.type=='Key'):
keyrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (keyrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0
global KeyCollect
KeyCollect = True
Add a new elif to check for the door:
elif(item.type=='Door') and (KeyCollect):
doorrec = pygame.Rect([item.x, item.y, item.width, item.height])
if (dorrec.colliderect(playerrec) and (item.visible!=0)):
item.visible = 0 #or move elsewhere on the map, or load another map
Notice it also checks to see if the key is collected. The item.visible line could be any code to move the player or move to another map etc.