Reputation: 164
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()
Upvotes: 1
Views: 312
Reputation: 210889
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
Upvotes: 1