I was recently making a platformer game in pygame (Python version: 3.8.5, Pygame version: 2.0.1).
I implemented a jump functionality where, whenever "a" key or up key is pressed and the player is grounded.
But this was too unresponsive:
Below is the whole code of the project (admittedly a little bit messy):
import os
import sys
import pygame
from pygame.locals import *
# utility functions
# for drawing the collision box
def draw_collision_box(screen, rect):
pygame.draw.rect(screen, (0, 255, 0), rect, 1)
# getting collisions
def get_collisions(rect, tiles):
collisions = []
for tile in tiles:
if rect.colliderect(tile):
collisions.append(tile)
return collisions
# moving the player
def move(rect, movement, tiles):
collision_types = {"top": False, "bottom": False, "right": False, "left": False}
rect.x += movement[0]
collisions = get_collisions(rect, tiles)
for collision in collisions:
if movement[0] > 0:
rect.right = collision.left
collision_types["right"] = True
elif movement[0] < 0:
rect.left = collision.right
collision_types["left"] = True
rect.y += movement[1]
collisions = get_collisions(rect, tiles)
for collision in collisions:
if movement[1] > 0:
rect.bottom = collision.top
collision_types["bottom"] = True
elif movement[1] < 0:
rect.top = collision.bottom
collision_types["top"] = True
return rect, collision_types
# constants
FPS = 60
TILE_SIZE = 32
MAX_GRAVITY_SCL = 3
# initialize pygame
pygame.init()
# initializing the game window
WINDOW_SIZE = (400, 400)
screen = pygame.display.set_mode(WINDOW_SIZE)
pygame.display.set_caption("Platformer")
# clock
clock = pygame.time.Clock()
# player
player_image = pygame.image.load(os.path.join(
"assets", "platformer", "player.png")) # loading the player image
player_image = pygame.transform.scale(player_image, (16, 32)) # resizing the player image
player_image.set_colorkey((255, 255, 255)) # making the bg of the player transparent by setting the color key
moving_right = False # checking if the player is moving right
moving_left = False # checking if the player is moving left
player_xspeed = 4 # player speed on horizontal axis
player_ymomentum = 0 # initial momentum on vertical axis
gravitational_acc = 0.5 # constant gravitation acceleration
jump_force = 10 # jump force of the player
grounded = False # checking if the player is grounded
player_rect = pygame.Rect(
50, 50, player_image.get_width(), player_image.get_height()) # player rect for managing collisions
# tiles
grass_image = pygame.image.load(os.path.join(
"assets", "platformer", "grass.png")) # loading the grass image
dirt_image = pygame.image.load(os.path.join(
"assets", "platformer", "dirt.png")) # loading the dirt image
# resizing the tile images
grass_image = pygame.transform.scale(grass_image, (TILE_SIZE, TILE_SIZE))
dirt_image = pygame.transform.scale(dirt_image, (TILE_SIZE, TILE_SIZE))
# game map
game_map = [["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "2", "2", "2", "0", "0", "2", "2", "2", "0", "0", "0", "0", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "2", "2", "2", "0", "0", "0", "0", "0"],
["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "1", "1", "1", "0", "0", "0", "0", "0"],
["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "1", "1", "1", "2", "2", "2", "2", "2"],
["1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"],
["1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"]]
# game loop
while True:
# frames per second
clock.tick(FPS)
# filling the background with a solid color so that images doesn't make on top of each other
screen.fill((146, 244, 255))
# rendering images
screen.blit(player_image, (player_rect.x, player_rect.y))
# rendering the map
tiles = []
for y in range(len(game_map)):
for x in range(len(game_map[y])):
if game_map[y][x] == "1":
screen.blit(dirt_image, (x * TILE_SIZE, y * TILE_SIZE))
if game_map[y][x] == "2":
screen.blit(grass_image, (x * TILE_SIZE, y * TILE_SIZE))
if game_map[y][x] != "0":
tiles.append(pygame.Rect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE))
# drawing the collision boxes for the tiles
# for tile in tiles:
# draw_collision_box(screen, tile)
# updating the game
movement = [0, 0]
if moving_right == True:
movement[0] += player_xspeed
if moving_left == True:
movement[0] -= player_xspeed
# gravity for the player
player_ymomentum += gravitational_acc
player_ymomentum = min(player_ymomentum, MAX_GRAVITY_SCL)
movement[1] += player_ymomentum
# updating player's position
player_rect, collision_types = move(player_rect, movement, tiles)
if collision_types["top"] or collision_types["bottom"]:
player_ymomentum = 0
grounded = collision_types["bottom"]
# drawing the collision rect of the player
# draw_collision_box(screen, player_rect)
# listening for events
for event in pygame.event.get():
if event.type == QUIT: # quit event
pygame.quit() # stop pygame
sys.exit() # quit the program
if event.type == KEYDOWN: # key input event
if (event.key == K_RIGHT or event.key == K_d):
moving_right = True
if (event.key == K_LEFT or event.key == K_a):
moving_left = True
if (event.key == K_UP or event.key == K_w) and grounded:
player_ymomentum = -jump_force
if event.type == KEYUP: # keyup input event
if (event.key == K_RIGHT or event.key == K_d):
moving_right = False
if (event.key == K_LEFT or event.key == K_a):
moving_left = False
# updating the display
pygame.display.update()
Can someone help me?
From reading your code, this is what happens:
In this line of code
player_ymomentum += gravitational_acc
player_ymomentum is set to 0.5; therefore movement[1] is also set to 0.5.
In the move function, you run this line of code:
rect.y += movement[1]
Since movement[1] is < 1 and Rect.y is/has to be an integer value, rect.y does not actually change.
Since rect.y did not change, no collision is detected in the call to get_collisions that follows next.
The condition if movement[1] > 0 is True, but since no collision got detected, collision_types["bottom"] will never be set to True; hence grounded will not be True and you can't jump.
In the next frame, the line
player_ymomentum += gravitational_acc
is run again. This time, player_ymomentum is set to 1; therefore movement[1] is also set to 1.
In the move function, the line
rect.y += movement[1]
will actually move the rect this time. A collision will be detected, collision_types["bottom"] will be set to True, grounded will be set to True, allowing you to jump.
player_ymomentum will be reset to 0, and this cycle begins again.
So whenever you're trying to jump while standing, you have a 50/50 chance of grounded being actually False, so pressing the jump key will not work.
To solve this, either remove this part:
if collision_types["top"] or collision_types["bottom"]:
player_ymomentum = 0
player_ymomentum will grow towards MAX_GRAVITY_SCL while still standing on the ground, but that shouldn't be a problem.
Or use a bigger Rect when trying to detect collisions between the player and the ground, like this:
...
rect.y += movement[1]
collisions = get_collisions(rect.inflate(1, 1), tiles)
for collision in collisions:
...
I solved the problem by making another function named check_grounded.
The problem was that in each frame the grounded variable was assigned the value True and then False. See the excerpt from within the game-loop:
# updating player's position
player_rect, collision_types = move(player_rect, movement, tiles)
if collision_types["top"] or collision_types["bottom"]:
player_ymomentum = 0
grounded = collision_types["bottom"] # here we assign True or False
So the player has to make the jump at right moment - when grounded == True.
To fix this I made another function, just for the purpose of ground detection and it worked fine:
# check if grounded
def check_grounded(rect, tiles):
rect.y += 1
collisions = get_collisions(rect, tiles)
rect.y -= 1
return len(collisions) != 0
grounded = check_grounded(player_rect, tiles)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With