1

I am trying to make a simple platformer in pygame, with simulated gravity and collision. I can't make the collision working. On collision with a sprite, the player slowly falls through the sprite and continues falling at normal speed when reached through.

Main.py:

class Game:
    def __init__(self):
        # initialize pygame library
        pg.init()
        pg.mixer.init()

        # initialize screen
        self.screen =  pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        self.font = pg.font.match_font(FONT_NAME)

        self.running = True
        self.playing = True

    def new(self):
        # initzialize sprite groups
        self.sprites = pg.sprite.Group()
        self.objects = pg.sprite.Group()

        self.p = Player(self)
        self.sprites.add(self.p)

        self.g = Ground()
        self.sprites.add(self.g)
        self.objects.add(self.g)

        self.o = Object(100, 350, 100, 20)
        self.sprites.add(self.o)
        self.objects.add(self.o)

        self.collide = False

        self.run()

    # constant running functions
    def run(self):
        while self.playing:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()
        self.running = False


    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.playing = False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_UP:
                    self.p.jump() 

    def update(self):
        self.sprites.update()

        hits = pg.sprite.spritecollide(self.p, self.objects, False)
        if hits:
            self.collide = True
            if self.p.vel.y >= 0.0:
                self.p.x = hits[0].rect.top
                print("Collide bottom")
            elif self.p.vel.y < 0: self.p.top  = hits[0].rect.bottom
            elif self.p.vel.x > 0: self.p.rect.right = hits[0].rect.left
            elif self.p.vel.x < 0: self.p.rect.left = hits[0].rect.right
            self.p.vel.y = 0
            #self.p.acc.y = 0
            #print(f"Collision with {hits[0].name}")
        else:
            self.collide = False



    def draw(self):
        self.screen.fill(BLACK)
        self.sprites.draw(self.screen)

        self.drawtext(f"X Pos: = {int(self.p.pos.x)}", 15, WHITE, WIDTH - 5, 20, 3)
        self.drawtext(f"Y Pos: = {int(self.p.pos.y)}", 15, WHITE, WIDTH - 5, 40, 3)
        self.drawtext(f"Y Velocity = {self.p.vel.y}", 15, WHITE, 5, 50, 0)
        self.drawtext(f"Y Accelleration = {self.p.acc.y}", 15, WHITE, 5, 70, 0)
        self.drawtext(f"Collision: = {self.collide}", 15, WHITE, 5, 200, 0)
        #print(self.p.vel.y)

        pg.display.flip()

    # other functions
    def drawtext(self, text, size, color, x, y, align):
        font = pg.font.Font(self.font, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == 0:
            text_rect.midleft = (x, y)
        elif align == 1:
            text_rect.midtop = (x, y)
        elif align == 2:
            text_rect.midbottom = (x, y)
        elif align == 3:
            text_rect.midright = (x, y)
        else:
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

    # def checkCollisionY(self):
    #     hits = pg.sprite.spritecollide(self.p, self.objects, False)
    #     if hits:
    #         self.collide = True
    #         return True
    #     else:
    #         self.collide = False
    #         return False


g = Game()
while g.running:
    g.new()

pg.quit()

Sprites.py:

from settings import *
import pygame as pg

vec = pg.math.Vector2

class Player(pg.sprite.Sprite):
    def __init__(self, game):
        pg.sprite.Sprite.__init__(self)
        self.game = game
        self.width = 30
        self.height = 30
        self.image = pg.Surface((self.width, self.height))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.rect.center = vec(150, 100)

        self.pos = vec(150, 100)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)

    def update(self):
        self.acc = vec(0, PLAYER_GRAV)

        #input
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pg.K_RIGHT]:
            self.acc.x = PLAYER_ACC



        self.acc.x += self.vel.x * PLAYER_FRICTION

        self.vel += self.acc   
        self.pos += self.vel + 0.5 * self.acc
        print(f"{self.vel.y} + 0.5 * {self.acc.y} = {self.vel.y + 0.5 * self.acc.y}")


        self.rect.topleft = self.pos
    def jump(self):
        hits = pg.sprite.spritecollide(self, self.game.objects, False)
        if hits:
            self.vel.y = -20

class Object(pg.sprite.Sprite):
    def __init__(self, x, y, w, h):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((w, h))
        self.image.fill((255, 0, 144))
        self.name = "Object"
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y



class Ground(pg.sprite.Sprite):
    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        self.name = "Ground"
        self.image = pg.image.load("ground.png")    
        self.rect = self.image.get_rect()
        self.rect.x = -100
        self.rect.y = 550

Settings.py:

# game settings
TITLE = "My Game"
WIDTH = 480
HEIGHT = 800
FPS = 60
FONT_NAME = 'impact'

#Player properties
PLAYER_ACC = 0.7
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.7


# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)
PURPLE = (255, 0, 255)

The important code here is the update() in main.py and update() in sprites.py. Help?

EDIT

hits = pg.sprite.spritecollide(self.p, self.objects, False)
        for hit in hits:
            self.collide = True
            if self.p.vel.x > 0.0: 
                self.p.rect.right = hit.rect.left
                self.p.pos.x = self.p.rect.centerx
                self.p.vel.x = 0

            elif self.p.vel.x < 0.0: 
                self.p.rect.left = hit.rect.right
                self.p.pos.x = self.p.rect.centerx

            self.p.vel.x = 0
            self.p.pos.x = self.p.rect.x
        else:
            self.collide = False

        hits = pg.sprite.spritecollide(self.p, self.objects, False)
        for hit in hits:
            self.collide = True
            if self.p.vel.y >= 0.0:
                self.p.rect.bottom = hit.rect.top
                self.p.pos.y = self.p.rect.centery
                self.p.vel.y = 0

            elif self.p.vel.y < 0.0:
                self.p.rect.top = hit.rect.bottom
                self.p.pos.y = self.p.rect.centery
                self.p.vel.y = 0
            self.p.pos.y = self.p.rect.y
        else:
            self.collide = False
Viktoracri
  • 61
  • 1
  • 7

1 Answers1

2

Your Player class doesn't have x and y attributes but a pos attribute which you need to change after a collision. The rect of the object needs to be updated as well and it's better to do that first and then set the pos.y coordinate to the rect.centery coordinate afterwards.

if self.p.vel.y >= 0.0:
    self.p.rect.bottom = hits[0].rect.top
    self.p.pos.y = self.p.rect.centery
    self.p.vel.y = 0

Do the same for the other directions.

Also, the horizontal and vertical movement should be handled separately, otherwise you'll see odd jumps for example from the side to the top of a platform. Take a look at the first part of this platformer example.


And in the jump method you need to move the rect down by 1 pixel so that it's able to collide with the platform sprites.

def jump(self):
    self.rect.y += 1
    # ...

Here's a complete, runnable example:

import pygame as pg


# game settings
TITLE = "My Game"
WIDTH = 480
HEIGHT = 800
FPS = 60
FONT_NAME = 'impact'

#Player properties
PLAYER_ACC = 0.7
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.7

# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
YELLOW = (255, 255, 0)

vec = pg.math.Vector2


class Player(pg.sprite.Sprite):
    def __init__(self, game):
        pg.sprite.Sprite.__init__(self)
        self.game = game
        self.image = pg.Surface((30, 30))
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect(center=(150, 100))

        self.pos = vec(150, 100)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)
        self.objects = game.objects

    def update(self):
        self.acc = vec(0, PLAYER_GRAV)

        #input
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pg.K_RIGHT]:
            self.acc.x = PLAYER_ACC

        self.acc.x += self.vel.x * PLAYER_FRICTION
        self.vel += self.acc

        # Move along the x-axis first.
        self.pos.x += self.vel.x + 0.5 * self.acc.x
        # print(f"{self.vel.y} + 0.5 * {self.acc.y} = {self.vel.y + 0.5 * self.acc.y}")
        self.rect.centerx = self.pos.x
        # Check if the sprite collides with a platform.
        hits = pg.sprite.spritecollide(self, self.objects, False)
        if hits:
            # Reset the x position.
            if self.vel.x > 0:
                self.rect.right = hits[0].rect.left
                self.pos.x = self.rect.centerx
                self.vel.x = 0
            elif self.vel.x < 0:
                self.rect.left = hits[0].rect.right
                self.pos.x = self.rect.centerx
                self.vel.x = 0

        # Move along the y-axis.
        self.pos.y += self.vel.y + 0.5 * self.acc.y
        self.rect.centery = self.pos.y
        # Check if the sprite collides with a platform.
        hits = pg.sprite.spritecollide(self, self.objects, False)
        if hits:
            # Reset the y position.
            if self.vel.y >= 0.0:
                self.rect.bottom = hits[0].rect.top
                self.pos.y = self.rect.centery
                self.vel.y = 0
            elif self.vel.y < 0:
                self.rect.top = hits[0].rect.bottom
                self.pos.y = self.rect.centery
                self.vel.y = 0

    def jump(self):
        self.rect.y += 1  # Move it down to check if it collides with a platform.
        hits = pg.sprite.spritecollide(self, self.game.objects, False)
        if hits:
            self.vel.y = -20
        self.rect.y -= 1  # Move it up again after the collision check.


class Object(pg.sprite.Sprite):
    def __init__(self, x, y, w, h):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((w, h))
        self.image.fill((255, 0, 144))
        self.name = "Object"
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y


class Ground(pg.sprite.Sprite):
    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        self.name = "Ground"
        self.image = pg.Surface((500, 300))
        self.image.fill((90, 30, 30))
        self.rect = self.image.get_rect()
        self.rect.x = -100
        self.rect.y = 550


class Game:
    def __init__(self):
        pg.init()
        pg.mixer.init()
        self.screen =  pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        self.font = pg.font.match_font(FONT_NAME)

        self.running = True
        self.playing = True

    def new(self):
        self.sprites = pg.sprite.Group()
        self.objects = pg.sprite.Group()

        self.p = Player(self)
        self.sprites.add(self.p)

        self.g = Ground()
        self.sprites.add(self.g)
        self.objects.add(self.g)

        rects = [(100, 350, 100, 20), (50, 380, 100, 20),
                 (200, 450, 100, 100)]
        for x, y, w, h in rects:
            obj = Object(x, y, w, h)
            self.sprites.add(obj)
            self.objects.add(obj)

        self.collide = False

        self.run()

    def run(self):
        while self.playing:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()
        self.running = False

    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.playing = False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_UP:
                    self.p.jump()

    def update(self):
        self.sprites.update()

    def draw(self):
        self.screen.fill(BLACK)
        self.sprites.draw(self.screen)
        pg.display.flip()


g = Game()
while g.running:
    g.new()

pg.quit()
skrx
  • 19,980
  • 5
  • 34
  • 48
  • I did this: https://imgur.com/a/VSD8dCl, but it still does not work properly. The X value is going nuts. – Viktoracri Oct 13 '18 at 09:11
  • Please don't post screenshots of your code. Just insert it into the question, so that we can copy and paste it. – skrx Oct 13 '18 at 09:15
  • I did that. Edited into the question – Viktoracri Oct 13 '18 at 09:18
  • I've just added a complete, runnable example. Check out the comments. I've also moved the collision checking code into the `Player` class. – skrx Oct 13 '18 at 09:40