Reputation: 21
I need help figuring out tile collisions for a platformer I'm currently making in Pygame. I have movement, with gravity working, as well as a tile map, but I don't really understand how to get collisions with the sprites working. I did make a list that appends all of the tiles that aren't 0 (so 1 or 2) into a list called tile_collisions (see line 113) but I don't really know what to do with that. Also, you can ignore the commented-out code, that was just a failed attempt. I'm trying to make this as simple as possible, without classes, because those aren't allowed for this assignment.
https://github.com/Night-28/Treble-Quest.git ^ The "main.py" file won't run without all of the pngs so if you do want to run it, you might have to download all of them, sorry!
import pygame as py
from tile_map import map
# Pygame setup
py.init()
clock = py.time.Clock()
# COLOURS
bg_colour = (110, 121, 228)
sky_colour = (190, 220, 255)
start_colour = (225, 225, 225)
screen_width = 1280
screen_height = 720
screen = py.display.set_mode((screen_width, screen_height))
p_sprite = py.image.load("plant_drone.png")
p_rect = p_sprite.get_rect()
p_rect.centery = screen_height - 32
grass_block = py.image.load("grass_block.png")
dirt_block = py.image.load("dirt_block.png")
# def collisions(rect, tiles):
# collider_list = []
# for tile in tiles:
# if rect.colliderect(tile):
# collider_list.append(tile)
# return collider_list
#
# def move(rect, movement, tiles):
# collision_types = {"top": False, "bottom": False, "right": False, "left": False}
#
# rect.x += movement[0]
# collider_list = collisions(rect, tile)
# for tile in collider_list:
# if movement[0] > 0:
# rect.right = tile.left
# collision_types["right"] = True
# elif movement[0] < 0:
# rect.left = tile.right
# collision_types["left"] = True
#
# rect.y += movement[1]
# collider_list = collisions(rect, tile)
# for tile in collider_list:
# if movement[1] > 0:
# rect.bottom = tile.top
# collision_types["bottom"] = True
# elif movement[1] < 0:
# rect.top = tile.bottom
# collision_types["top"] = True
#
# return rect, collision_types
# MAIN MENU
def menu():
py.display.set_caption("Game Menu")
while True:
start()
py.display.update()
clock.tick(60)
# START OPTION (BLINKING TEXT)
def start():
start_font = py.font.Font('Halogen.otf', 50)
bg = py.image.load("GM Treble Quest V2.png")
bg_rect = py.Rect((0, 0), bg.get_size())
screen.blit(bg, bg_rect)
n = 0
while True:
start = start_font.render(("Press Enter To Play"), True, start_colour)
if n % 2 == 0:
screen.blit(start, (450, 625))
clock.tick(50000)
else:
screen.blit(bg, bg_rect)
n += 0.5
py.display.update()
clock.tick(3)
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if event.key == py.K_RETURN:
play()
# GAME
def play():
py.display.set_caption("Treble Quest")
player_y, player_x = p_rect.bottom, 32
velocity_x, velocity_y = 5, 0
ground = 480
gravity_factor = 0.35
acl_factor = -12
while True:
clock.tick(100)
vertical_acl = gravity_factor
screen.fill(sky_colour)
screen.blit(p_sprite, p_rect)
# TILE MAP
tile_collisions = []
y = 0
for row in map:
x = 0
for tile in row:
if tile == 1:
screen.blit(dirt_block, (x * 32, y * 32))
if tile == 2:
screen.blit(grass_block, (x * 32, y * 32))
if tile != 0:
tile_collisions.append(py.Rect(x * 32, y * 32, 32, 32))
x += 1
y += 1
screen.blit(p_sprite, p_rect)
# player_movement = [0, 0]
# if moving_right == True:
# player_movement[0] += 2
# if moving_left == True:
# player_movement[0] -= 2
# player_movement[1] += player
# MOVEMENT
for event in py.event.get():
if event.type == py.QUIT:
exit()
if event.type == py.KEYDOWN:
if velocity_y == 0 and event.key == py.K_w:
vertical_acl = acl_factor
velocity_y += vertical_acl
player_y += velocity_y
if player_y > ground:
player_y = ground
velocity_y = 0
vertical_acl = 0
p_rect.bottom = round(player_y)
keys = py.key.get_pressed()
player_x += (keys[py.K_d] - keys[py.K_a]) * velocity_x
p_rect.centerx = player_x
py.display.update()
menu()
Upvotes: 1
Views: 378
Reputation: 14924
Your code is almost there.
A simple way to do collisions is to iterate through the bunch of rectangles created when you process the map tiles. Check each rectangle to see if it would intersect the player's position.
When your player needs to move, calculate where the move intends to go (don't actually move the player yet). Then check if the target location will overlap with any of the blocker-tiles. If there is no overlap, then move the player. Otherwise the player stops.
In the example code below, I've implemented a illustrative version of this. While this method is simple, and works (mostly), it suffers from a few issues:
Really you should test to cover these failures. The second issue is easy to fix by calculating how far the player can move in the desired direction, and only move this lesser amount.
Note that I didn't really understand your movement algorithm. There seems to be a velocity, but no stopping. Given I was just illustrating collisions, I hacked it into some kind of working state that suited what I needed to show.
import pygame as py
#from tile_map import map
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,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0 ],
[ 0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 ],
[ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 ] ]
# Pygame setup
py.init()
clock = py.time.Clock()
# COLOURS
bg_colour = (110, 121, 228)
sky_colour = (190, 220, 255)
start_colour = (225, 225, 225)
screen_width = 1280
screen_height = 720
screen = py.display.set_mode((screen_width, screen_height))
def fakeBitmap( width, height, back_colour, text=None, text_colour=(100,100,100) ):
""" Quick function to replace missing bitmaps """
bitmap = py.Surface( ( width, height ) )
bitmap.fill( back_colour )
if ( text != None and len( text.strip() ) > 0 ):
font = py.font.SysFont( None, 10 )
text_bitmap = font.render( text, True, text_colour, back_colour )
bitmap.blit( text_bitmap, text_bitmap.get_rect(center = bitmap.get_rect().center ) )
return bitmap
#p_sprite = py.image.load("plant_drone.png")
p_sprite = fakeBitmap( 32, 32, ( 128,128,0), "PLAYER" )
p_rect = p_sprite.get_rect()
p_rect.centery = screen_height - 32
#grass_block = py.image.load("grass_block.png")
#dirt_block = py.image.load("dirt_block.png")
grass_block = fakeBitmap( 32, 32, ( 0,200,0 ), "grass_block.png" )
dirt_block = fakeBitmap( 32, 32, ( 102,49, 0 ), "dirt_block.png")
# MAIN MENU
def menu():
py.display.set_caption("Game Menu")
while True:
start()
py.display.update()
clock.tick(60)
# START OPTION (BLINKING TEXT)
def start():
#start_font = py.font.Font('Halogen.otf', 50)
start_font = py.font.SysFont( None, 16 )
#bg = py.image.load("GM Treble Quest V2.png")
bg = fakeBitmap( 1280, 1024, ( 0,0,128 ), "GM Treble Quest V2.png" )
bg_rect = py.Rect((0, 0), bg.get_size())
screen.blit(bg, bg_rect)
n = 0
while True:
start = start_font.render(("Press Enter To Play"), True, start_colour)
if n % 2 == 0:
screen.blit(start, (450, 625))
clock.tick(50000)
else:
screen.blit(bg, bg_rect)
n += 0.5
py.display.update()
clock.tick(3)
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if event.key == py.K_RETURN:
play()
def playerCanMoveTo( player_next, blockers ):
""" Is a player allowed to move to player_next, or will that mean
colliding with any of the rectangles defined in blockers """
can_move = True
# Simple check, is the destination blocked
for block_rect in blockers:
print( "is Player [%d,%d %d %d] inside Block [%d,%d %d %d]" % ( player_next.x, player_next.y, player_next.width, player_next.height, block_rect.x, block_rect.y, block_rect.width, block_rect.height ) )
if ( block_rect.colliderect( player_next ) ):
can_move = False
break # don't need to check further
return can_move
# GAME
def play():
py.display.set_caption("Treble Quest")
player_y, player_x = p_rect.bottom, 32
velocity_x, velocity_y = 5, 0
ground = 480
gravity_factor = 0.35
acl_factor = -12
# Only need to do this once, moved away from main loop
tile_collisions = []
y = 0
for row in map:
x = 0
for tile in row:
if tile != 0:
tile_collisions.append(py.Rect(x * 32, y * 32, 32, 32))
x += 1
y += 1
while True:
clock.tick(100)
vertical_acl = gravity_factor
screen.fill(sky_colour)
screen.blit(p_sprite, p_rect)
# TILE MAP
y = 0
for row in map:
x = 0
for tile in row:
if tile == 1:
screen.blit(dirt_block, (x * 32, y * 32))
if tile == 2:
screen.blit(grass_block, (x * 32, y * 32))
x += 1
y += 1
screen.blit(p_sprite, p_rect)
# MOVEMENT
for event in py.event.get():
if event.type == py.QUIT:
exit()
elif event.type == py.KEYDOWN:
if velocity_y == 0 and event.key == py.K_w:
vertical_acl = acl_factor
velocity_y += vertical_acl
player_y += velocity_y
if player_y > ground:
player_y = ground
velocity_y = 0
vertical_acl = 0
p_rect.bottom = round(player_y)
keys = py.key.get_pressed()
# Accelerate left/right [A] <-> [D]
if ( keys[py.K_a] ):
velocity_x -= 1
velocity_x = max( -5, velocity_x )
elif ( keys[py.K_d] ):
velocity_x += 1
velocity_x = min( 5, velocity_x )
# Is the player allowed to move?
target_rect = p_rect.copy()
target_rect.centerx += velocity_x
# Check where the player would move to, is allowed
if ( playerCanMoveTo( target_rect, tile_collisions ) ):
player_x += velocity_x
p_rect.centerx = player_x
print( "moving %d" % ( velocity_x ) )
else:
velocity_x = 0
print( "blocked" )
py.display.update()
menu()
Upvotes: 1