1
votes

I know there are already a few questions about wall collision out there, but none of the techniques in those answers have helped me. I'm doing a topdown exploration game without gravity. Movement works just fine, but once I made walls and collisions it started glitching out and teleporting. I've included (and greatly cut down) the two functions where I think the problem is, and at the bottom I've included the whole code.

def render(self):
    self.x += self.xvel
    self.app.wall_collision(self, self.xvel, 0)
    self.y += self.yvel
    self.app.wall_collision(self, 0, self.yvel)
    self.rect.topleft = (self.x, self.y)
    self.app.screen.blit(self.surf, self.rect)

Following the advice of pygame sprite wall collision, I split the x and y wall collision code. The wall collision code is based off of https://github.com/marcusmoller/pyweek17-miner/blob/master/miner/engine.py#L202-L220.

def wall_collision(self, player, xvel, yvel)
    for wall in self.maze.walls:
        if pygame.sprite.collide_rect(player, wall):
            if xvel > 0:
                player.x = wall.rect.left-player.w
            if xvel < 0:
                player.x = wall.rect.right
            if yvel > 0:
                player.y = wall.rect.top-player.h
            if yvel < 0:
                player.y = wall.rect.bottom

This code works fine for movement in one dimension. I.e, hug a wall and going straight works great, but press two movement buttons at the same time and teleportation starts happening. Any help is greatly appreciated! I've debugged the crap out of this thing. Here is a short snippet of output of the position of the player (x,y):

348.4 278.4
348.4 220
348.4 220
348.4 185
348.4 185
348.4 150
348.4 150
348.4 115
348.4 115
348.4 80
348.4 80
348.4 45

As you can see, there is a lot of random jumping around in the y direction. The reason the jumps are 35 is the block size for the walls is 35x35.

import pygame
import pygame.freetype
from pygame.locals import (
    K_UP,
    K_DOWN,
    K_LEFT,
    K_RIGHT,
    K_ESCAPE,
    KEYDOWN,
    QUIT,
)

import numpy as np


class App:
    def __init__(self):
        self.running = True
        self.width = 800
        self.height = 600
        self.player = Player(self)
        self.maze = Maze(self, 35)
        pygame.init()
        self.screen = pygame.display.set_mode((self.width, self.height))
        self.clock = pygame.time.Clock()
        self.font = pygame.freetype.SysFont('Times New Roman', 10) 

        while self.running:
            pressed_keys = pygame.key.get_pressed()
            
            for event in pygame.event.get():
                if event.type == QUIT:
                    self.running = False
            self.render()
            self.player.update(pressed_keys)
            
    def render(self):
        self.screen.fill((255, 255, 255))
        text_surf, text_rect = self.font.render('({}, {}) ({}, {})'.format(self.player.x, self.player.y, self.player.xvel, self.player.yvel), (0, 0, 0), size=10)
        self.screen.blit(text_surf, (500, 300))
        self.player.move()
        self.maze.render()
        pygame.display.flip()
        self.clock.tick(60)
        

    def wall_collision(self, player, xvel, yvel):
        for wall in self.maze.walls:
            if pygame.sprite.collide_rect(self.player, wall):
                if xvel > 0:
                    self.player.x = wall.rect.left-self.player.w
                if xvel < 0:
                    self.player.x = wall.rect.right
                if yvel > 0:
                    self.player.y = wall.rect.top-self.player.h
                if yvel < 0:
                    self.player.y = wall.rect.bottom
        
class Player(pygame.sprite.Sprite):
    def __init__(self, app):
        super(Player,self).__init__()
        self.app = app
        self.x = 100
        self.y = 100
        self.xvel = 0
        self.yvel = 0
        self.max_speed = 8
        self.acc = self.max_speed / 2
        self.de_acc = self.max_speed * 0.8

        self.h = 25
        self.w = 25
        
        self.surf = pygame.Surface((self.h, self.w))
        self.surf.fill((0, 0, 204))
        self.rect = self.surf.get_rect()
        self.rect.topleft = self.x, self.y
        
    def update(self, pressed_keys):
        if pressed_keys[K_UP]:
            self.yvel = max(self.yvel - self.acc, -self.max_speed)
        if pressed_keys[K_DOWN]:
            self.yvel = min(self.yvel + self.acc, self.max_speed)
        if pressed_keys[K_LEFT]:
            self.xvel = max(self.xvel - self.acc, -self.max_speed)
        if pressed_keys[K_RIGHT]:
            self.xvel = min(self.xvel + self.acc, self.max_speed)

        if not pressed_keys[K_UP] and not pressed_keys[K_DOWN]:
            if self.yvel > 0:
                self.yvel = max(0, self.yvel - self.de_acc)
            if self.yvel < 0:
                self.yvel = min(0, self.yvel + self.de_acc)
                
        if not pressed_keys[K_LEFT] and not pressed_keys[K_RIGHT]:
            if self.xvel > 0:
                self.xvel = max(0, self.xvel - self.de_acc)
            if self.xvel < 0:
                self.xvel = min(0, self.xvel + self.de_acc)

    def move(self):
        self.x += self.xvel
        self.app.wall_collision(self, self.xvel, 0)
        self.y += self.yvel
        self.app.wall_collision(self, 0, self.yvel)
        self.rect.topleft = (self.x, self.y)
        self.app.screen.blit(self.surf, self.rect)






        
class Maze:
    def __init__(self, app, size):
        super(Maze, self).__init__()
        self.maze = np.array([[1,1,1,1,1,1,1,1,1,1],
                     [1,0,0,0,0,0,0,0,0,1],
                     [1,0,0,0,0,0,0,0,0,1],
                     [1,0,1,1,1,1,1,1,0,1],
                     [1,0,1,0,0,0,0,0,0,1],
                     [1,0,1,0,1,1,1,1,0,1],
                     [1,0,0,0,0,0,0,0,0,1],
                     [1,1,1,1,1,1,1,1,1,1]])
        self.size = size
        self.app = app
        self.draw()

    def draw(self):
        self.walls = pygame.sprite.Group()
        for i, row in enumerate(self.maze):
            for j, el in enumerate(row):
                if el == 1:
                    x = i * self.size
                    y = j* self.size
                    self.walls.add(Wall(x, y, self.size))
                    
    def render(self):
        for wall in self.walls:
            self.app.screen.blit(wall.surf, wall.rect)
            

class Wall(pygame.sprite.Sprite):
    def __init__(self, x, y, size):
        super(Wall, self).__init__()
        self.surf = pygame.Surface((size, size))
        self.x = x
        self.y = y
        self.rect = self.surf.get_rect()
        self.rect.topleft = (self.x, self.y)
    


app = App()
pygame.quit()
1

1 Answers

1
votes

The player's position is correctly limited to the walls, but the player's velocity is still increasing. If the velocity is great enough, the player "jumps" over the wall with one step. You need to set the velocity to 0 when the player hits the wall.
Also, you need to take into account which side of the wall the player is on and you need to update the .rect attribute of the palyer:

class App:
    # [...]

    def wall_collision(self, player, xvel, yvel):
        for wall in self.maze.walls:
            if pygame.sprite.collide_rect(self.player, wall):
                if xvel > 0 and self.player.rect.left < wall.rect.left:
                    self.player.rect.right = wall.rect.left
                    self.player.x = self.player.rect.x
                    self.xvel = 0
                if xvel < 0 and self.player.rect.right > wall.rect.right:
                    self.player.rect.left = wall.rect.right
                    self.player.x = self.player.rect.x
                    self.xvel = 0
                if yvel > 0 and self.player.rect.top < wall.rect.top:
                    self.player.rect.bottom = wall.rect.top
                    self.player.y = self.player.rect.y
                    self.yvel = 0
                if yvel < 0 and self.player.rect.bottom > wall.rect.bottom:
                    self.player.rect.top = wall.rect.bottom
                    self.player.y = self.player.rect.y
                    self.yvel = 0