Reputation: 681
Here is my direct question: How do I make collision work in pygame with my code?
I have spent the last few days trying to add collision into my game, to no avail. Every attempt I have made has not worked as expected even when following other answers provided on similar threads. In my current implementation the player simply bounces across the rect to the other side. I can see collision been detected but the results are not as expected. I simply want my player to not run through platforms and stand on them or if they hit his head he will fall. My code is below for my current collision detection effort.
import pygame as pg
from settings import Music_Mixer, loadCustomFont, States, screen, GROUND_HEIGHT
from time import sleep
"""This section contains entity states which are separate from game and menu states."""
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.health = 100
self.speed = 1
self.screen = screen
self.pos_x = x
self.pos_y = y
self.running = False
self.is_jumping = False
self.velocity = 15
self.mass = 3
#List of pictures for animations.
stick_still = pg.image.load('Images/Animations/PlayerRun/Stickman_stand_still.png').convert_alpha()
stick_still_2 = pg.image.load('Images/Animations/PlayerRun/Stickman_stand_still_2.png').convert_alpha()
#Right running pictures.
stick_run_1_right = pg.image.load('Images/Animations/PlayerRun/Stickman_run_1.png').convert_alpha()
stick_run_2_right = pg.image.load('Images/Animations/PlayerRun/Stickman_run_2.png').convert_alpha()
stick_run_3_right = pg.image.load('Images/Animations/PlayerRun/Stickman_run_3.png').convert_alpha()
stick_run_4_right = pg.image.load('Images/Animations/PlayerRun/Stickman_run_4.png').convert_alpha()
stick_run_5_right = pg.image.load('Images/Animations/PlayerRun/Stickman_run_4.png').convert_alpha()
#Left running pictures.
stick_run_1_left = pg.image.load('Images/Animations/PlayerRun/Stickman_run_1_left.png').convert_alpha()
stick_run_2_left = pg.image.load('Images/Animations/PlayerRun/Stickman_run_2_left.png').convert_alpha()
stick_run_3_left = pg.image.load('Images/Animations/PlayerRun/Stickman_run_3_left.png').convert_alpha()
stick_run_4_left = pg.image.load('Images/Animations/PlayerRun/Stickman_run_4_left.png').convert_alpha()
stick_run_5_left = pg.image.load('Images/Animations/PlayerRun/Stickman_run_4_left.png').convert_alpha()
#Lists for animation movement.
self.STICKMAN_IDLE = [stick_still]
self.STICKMAN_RUN_RIGHT = [stick_run_1_right, stick_run_2_right, stick_run_5_right, stick_run_3_right, stick_run_4_right]
self.STICKMAN_RUN_LEFT = [stick_run_1_left, stick_run_2_left, stick_run_5_left, stick_run_3_left, stick_run_4_left]
self.images = self.STICKMAN_IDLE
self.image = self.images[0]
self.rect = self.image.get_rect(center=(x, y))
self.anim_index = 0
self.anim_timer = 0
self.ms = 0
#Moves the player and begins the animation phase.
def move_player(self, speed, dt):
self.pressed = pg.key.get_pressed()
if self.pressed[pg.K_a]:
self.running = True
if self.running:
self.pos_x -= 5 # Move left.
self.ms = 0.07
self.images = self.STICKMAN_RUN_LEFT # Change the animation.
if self.pressed[pg.K_d]:
self.running = True
if self.running:
self.pos_x += 5 # Move right.
self.ms = 0.07
self.images = self.STICKMAN_RUN_RIGHT # Change the animation.
if not self.pressed[pg.K_d] and not self.pressed[pg.K_a]:
self.images = self.STICKMAN_IDLE # Change the animation.
self.ms = 0.07
if self.pressed[pg.K_w]:
self.is_jumping = True
# Update the rect because it's used to blit the image.
self.rect.center = self.pos_x, self.pos_y
#Makes the player jump.
def jumping(self, dt):
if self.is_jumping:
#Calculate force.
F = (0.5 * self.mass * (self.velocity))
#Change position.
self.pos_y = self.pos_y - F
#Change velocity.
self.velocity = self.velocity - 1
if self.pos_y == GROUND_HEIGHT:
self.pos_y = GROUND_HEIGHT
self.is_jumping = False
self.velocity = 15
#Checks for collision.
def is_collided_with(self, l):
for wall in l:
if self.rect.colliderect(wall.rect):
if self.rect.right > wall.rect.left:
self.rect.right = wall.rect.left
if self.rect.left < wall.rect.right:
self.rect.left = wall.rect.right
if self.rect.bottom < wall.rect.top:
self.rect.bottom = wall.rect.top
if self.rect.top > wall.rect.bottom:
self.rect.top = wall.rect.bottom
#Animates the running movement of the player.
def runAnim(self, dt):
# Add the delta time to the anim_timer and increment the
# index after 70 ms.
self.anim_timer += dt
if self.anim_timer > self.ms:
self.anim_timer = 0 # Reset the timer.
self.anim_index += 1 # Increment the index.
self.anim_index %= len(self.images) # Modulo to cycle the index.
self.image = self.images[self.anim_index] # And switch the image.
#draws the player to the screen.
def draw_entity(self):
screen.blit(self.image, self.rect)
#Creates platforms that the user can jump onto.
class Platform(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.pos_x = x
self.pos_y = y
self.moving = False
self.image = None
self.rect = pg.Rect(x, y, 150, 20)
#draws the platform to the screen.
def draw_plat(self):
pg.draw.rect(screen, (0,0,0), self.rect)
Upvotes: 1
Views: 1202
Reputation: 63
Please see https://pygame.org/docs/ref/sprite.html#pygame.sprite.collide_rect. It will help you understand it a bit better.
Upvotes: 0
Reputation: 11
In my opinion the best way to do collision is not using coordinates. Pygame automatically checks if two sprites are collided or not. I'm not sure exactly what you want to do with your collisions, but this link walks you through exactly how to do it and has video examples on how to solve your exact issue.Heres the link: http://programarcadegames.com/index.php?chapter=introduction_to_sprites&lang=en#section_13
Upvotes: 0
Reputation: 104712
Your problem is with your attempts to figure out which side of an object you've collided with. The issue is that your current checks are not mutually exclusive. If you're interpenetrating an object (from any side), you'll usually trigger all the checks in this function:
def is_collided_with(self, l):
for wall in l:
if self.rect.colliderect(wall.rect):
if self.rect.right > wall.rect.left:
self.rect.right = wall.rect.left
if self.rect.left < wall.rect.right:
self.rect.left = wall.rect.right
if self.rect.bottom < wall.rect.top:
self.rect.bottom = wall.rect.top
if self.rect.top > wall.rect.bottom:
self.rect.top = wall.rect.bottom
The net result is that you always end up below and to the left of any wall you touch.
You need to check for the direction of the collision in some other way. A common approach is to keep track of the direction you're moving, and use that to figure out which side of an object you've collided with (since if you're colliding with a non-moving object, you can only collide from your own movement). Unfortunately, you don't appear to be maintaining a velocity for your character, so this may not be as simple for your game. Note though, if you ever want your character to have some "inertia" and take some time to speed up and slow down when you start and stop pressing a key, you will probably need to add a velocity attribute, so maybe you can add it now (without adding the acceleration stuff yet), and just use it for the collision response logic.
Anyway, here's some code you can adapt to whatever means you want to use to tell the direction you're moving (I just put in function calls to abstract away the test, you can probably write an inline test, e.g. self.velocity_x > 0
or whatever):
def is_collided_with(self, l):
for wall in l:
if self.rect.colliderect(wall.rect):
if self.is_moving_right(): # replace this with a velocity check or something
self.rect.right = wall.rect.left
elif self.is_moving_left(): # and here
self.rect.left = wall.rect.right
if self.is_moving_down: # and here
self.rect.bottom = wall.rect.top
elif self.is_moving_up(): # and here too
self.rect.top = wall.rect.bottom
This code may still not do exactly what you want when it comes to collisions when you're moving diagonally, but it should work perfectly for collisions that occur along a single axis. You might want to split up your movement into two parts, one horizonal and one vertical with an extra collision test in between them, to avoid the need to resolve diagonal collisions.
Diagonal collisions have a lot of complexity, as you can't tell if you just collided face to face with another object while moving diagonally, or if you actually hit corner to corner. You may want to do some more complicated calculations involving both your relative position and velocity to see where exactly the collision first occurred (so you can decide how to resolve the collision).
There can be a lot of subtleties around collision response, and different styles of games have different conventional solutions. If your game is a platformer, you should search for specific solutions for that style of game, since more general solutions for other game styles may not be good enough for your needs. For instance, in a platformer it's often important to know if your character is standing on the ground, rather than falling or jumping, which requires some extra steps when you're handle your vertical collisions.
Upvotes: 1