0
votes

I'm trying to find a way to make perfect collision in my platformer game for school. Most of it works but there is a slight problem. When I stand on a platform above ground level and move left or right off of it, the player continues to float in midair at the same height of the platform that I got off of. This can be fixed by jumping and the collision reverts to normal. I am using a state system to track if the player is standing or not by having a folder of possible player states and switching between them. This is set to "Falling" by default because the player starts in midair when the game runs. The code for the game is divided into three separate files below (main.py, obj.py and settings.py). Please tell me how I can fix this glitch.

Main.py

import pygame
import random
from settings import *
from obj import *

pygame.init()
pygame.mixer.init()
pygame.font.init()

pygame.display.set_caption(TITLE)
screen = pygame.display.set_mode([WIDTH,HEIGHT])

clock = pygame.time.Clock()

me = Player()
all_sprites.add(me)

platforms = []

pf = Wall(20,40,500,480, 0)
pf2 = Wall(WIDTH,40, 400,500, 0)
platforms.append(pf)
platforms.append(pf2)

for i in platforms:
    wall_sprites.add(i)

running = True

while running:
    clock.tick(FPS)

    all_sprites.update()
    wall_sprites.update()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(GREY)
    all_sprites.draw(screen)
    wall_sprites.draw(screen)
    pygame.display.update()

pygame.quit()

obj.py

import pygame
import math
from settings import *

class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((40,40))
        self.image.fill(BLACK)
        self.rect = self.image.get_rect()
        self.rect.x = WIDTH / 2
        self.rect.y = 70
        self.vx = 0
        self.vy = 0
        self.SW = False  # Can you screen wrap?
        self.player_states = ["Standing","Falling"]
        self.state = self.player_states[1]

    def update(self):
        self.vx = 0  # X speed set to 0 if no input is received
        if self.state == self.player_states[1]:
            self.vy += GRAVITY  # Gravity only added while falling
        else:
            self.vy = 0

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.vx = -SPEED
        if keys[pygame.K_RIGHT]:
            self.vx = SPEED
        if keys[pygame.K_SPACE] and self.state == self.player_states[0]:
            self.vy -= JUMP_SPEED
            self.state = self.player_states[1]        

        self.rect.left += self.vx  # X and Y positions are updated
        self.collide(self.vx, 0, wall_sprites)  # Collision is checked. Second param is 0 b/c we aren't checking for vertical collision here
        self.rect.top += self.vy
        self.collide(0, self.vy, wall_sprites)


        if self.SW:
            if self.rect.left > WIDTH:
                self.rect.right = 0
            if self.rect.right < 0:
                self.rect.left = WIDTH

            if self.rect.top > HEIGHT:
                self.rect.bottom = 0
            if self.rect.bottom < 0:
                self.rect.top = HEIGHT

    def collide(self, xDif, yDif, platform_list):
        for i in platform_list:                      # Shuffle through list of platforms
            if pygame.sprite.collide_rect(self, i):  # If there is a collision between the player and a platform...
                if xDif > 0:                         # And our x (horizontal) speed is greater than 0...
                    self.rect.right = i.rect.left    # That means that we are moving right, 
                if xDif < 0:                         # So our right bounding box becomes equal to the left bounding box of all platforms and we don't collide    
                    self.rect.left = i.rect.right
                if yDif > 0:
                    self.rect.bottom = i.rect.top
                    self.state = self.player_states[0] 
                if yDif < 0:
                    self.rect.top = i.rect.bottom


class Wall(pygame.sprite.Sprite):  # Collision is added for platforms just in case that they are moving. If they move to you, they push you
    def __init__(self, width, height, xpos, ypos, speed):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((width,height))
        self.image.fill(BLUE)
        self.rect = self.image.get_rect()
        self.rect.centerx = xpos
        self.rect.centery = ypos
        self.speed = speed

    def update(self):
        self.rect.left += self.speed
        self.collide(self.speed, all_sprites)  # Collision only for platforms moving left and right. Not up and down yet

    def collide(self, xDif, player_list): 
        for i in player_list:                      
            if pygame.sprite.collide_rect(self, i):

                if xDif > 0:                         # If the platform is moving right... (has positive speed)
                    i.rect.left += self.speed        # Platform pushes player
                    self.rect.right = i.rect.left    # Player sticks to the wall and is pushed

                if xDif < 0:
                    i.rect.right -= self.speed
                    self.rect.left = i.rect.right

settings.py

import pygame

FPS = 60
WIDTH = 800
HEIGHT = 600
TITLE = "Perfect collision"

GREY = (150,150,150)
BLACK = (0,0,0)
BLUE = (0,0,255)

SPEED = 5
JUMP_SPEED = 9
GRAVITY = 0.3

all_sprites = pygame.sprite.Group()
wall_sprites = pygame.sprite.Group()
1

1 Answers

0
votes

Just add the GRAVITY to self.vy in every frame and set self.vy to 0 when the sprite touches the ground:

def update(self):
    self.vx = 0
    self.vy += GRAVITY

def collide(self, xDif, yDif, platform_list):
    for i in platform_list:
        if pygame.sprite.collide_rect(self, i):
            # Code omitted.

            if yDif > 0:
                self.rect.bottom = i.rect.top
                self.state = self.player_states[0]
                self.vy = 0  # Set vy to 0 if the sprite touches the ground.