python - dirtysprite - sprites for pygame
Añadir detección de colisión a un plattformer en pygame (2)
Estoy trabajando en un pequeño juego de plataformas en el que colocas bloques para hacer un nivel y luego lo juegas.
Obtuve gravedad, saltos y movimientos de izquierda y derecha ... pero no estoy seguro de cómo hacer que el jugador colisione con las paredes cuando se mueve hacia la izquierda o hacia la derecha.
La forma en que quiero que funcione es así-
if key[K_LEFT]:
if not block to the left:
move to the left
Cómo voy a hacer esto (en relación con esta fuente):
import pygame,random
from pygame.locals import *
import itertools
pygame.init()
screen=pygame.display.set_mode((640,480))
class Block(object):
sprite = pygame.image.load("texture//dirt.png").convert_alpha()
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
class Player(object):
sprite = pygame.image.load("texture//playr.png").convert()
sprite.set_colorkey((0,255,0))
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
blocklist = []
player = []
colliding = False
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key=pygame.key.get_pressed()
if key[K_LEFT]:
p.rect.left-=1
if key[K_RIGHT]:
p.rect.left+=1
if key[K_UP]:
p.rect.top-=10
for event in pygame.event.get():
if event.type == QUIT: exit()
if key[K_LSHIFT]:
if event.type==MOUSEMOTION:
if not any(block.rect.collidepoint(mse) for block in blocklist):
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
else:
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
for b in to_remove:
blocklist.remove(b)
if not to_remove:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
elif event.button == 3:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
player=[]
player.append(Player(x+16,y+16))
for b in blocklist:
screen.blit(b.sprite, b.rect)
for p in player:
if any(p.rect.colliderect(block) for block in blocklist):
#collide
pass
else:
p.rect.top += 1
screen.blit(p.sprite, p.rect)
pygame.display.flip()
Como estás usando pygame, puedes usar el rect.colliderect()
de rect.colliderect()
para ver si el sprite del jugador está colisionando con un bloque. A continuación, realice una función que devuelva el lado de un cierto rect en relación con el otro rect:
def rect_side(rect1, rect2): # Returns side of rect1 relative to rect2.
if rect1.x > rect2.x:
return "right"
else:
return "left"
# If rect1.x == rect2.x the function will return "left".
rect_side()
si rect_side()
devuelve "right"
, restringe el movimiento hacia la izquierda y viceversa.
PD: si quieres el mismo movimiento vertical, pero también puedes comparar rect1.y
a rect2.y
y lidiar con las salidas "up"
y "down"
. Puede hacer una tupla que represente direcciones horizontales y verticales.
Un enfoque común es separar el manejo de colisiones horizontal y vertical en dos pasos separados.
Si haces esto y también rastreas la velocidad de tu jugador, es fácil saber de qué lado ocurrió una colisión.
Antes que nada, demos al jugador algunos atributos para hacer un seguimiento de su velocidad:
class Player(object):
...
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
# indicates that we are standing on the ground
# and thus are "allowed" to jump
self.on_ground = True
self.xvel = 0
self.yvel = 0
self.jump_speed = 10
self.move_speed = 8
Ahora necesitamos un método para realmente verificar una colisión. Como ya dijimos, para facilitar las cosas, usamos nuestro xvel
y yvel
para saber si colisionamos con nuestro lado izquierdo o derecho, etc. Esto entra en la clase Player
:
def collide(self, xvel, yvel, blocks):
# all blocks that we collide with
for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:
# if xvel is > 0, we know our right side bumped
# into the left side of a block etc.
if xvel > 0: self.rect.right = block.rect.left
if xvel < 0: self.rect.left = block.rect.right
# if yvel > 0, we are falling, so if a collision happpens
# we know we hit the ground (remember, we seperated checking for
# horizontal and vertical collision, so if yvel != 0, xvel is 0)
if yvel > 0:
self.rect.bottom = block.rect.top
self.on_ground = True
self.yvel = 0
# if yvel < 0 and a collision occurs, we bumped our head
# on a block above us
if yvel < 0: self.rect.top = block.rect.bottom
A continuación, movemos nuestro manejo de movimiento a la clase Player
. Así que creemos en el objeto que realiza un seguimiento de la entrada. Aquí, uso una namedtuple
, porque por qué no.
from collections import namedtuple
...
max_gravity = 100
Move = namedtuple(''Move'', [''up'', ''left'', ''right''])
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key = pygame.key.get_pressed()
for event in pygame.event.get():
...
move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])
for p in player:
p.update(move, blocklist)
screen.blit(p.sprite, p.rect)
Pasamos la blocklist
de blocklist
al método de update
del Player
para que podamos verificar la colisión. Usando el objeto move
, ahora sabemos dónde debe moverse el jugador, así que implementemos Player.update
:
def update(self, move, blocks):
# check if we can jump
if move.up and self.on_ground:
self.yvel -= self.jump_speed
# simple left/right movement
if move.left: self.xvel = -self.move_speed
if move.right: self.xvel = self.move_speed
# if in the air, fall down
if not self.on_ground:
self.yvel += 0.3
# but not too fast
if self.yvel > max_gravity: self.yvel = max_gravity
# if no left/right movement, x speed is 0, of course
if not (move.left or move.right):
self.xvel = 0
# move horizontal, and check for horizontal collisions
self.rect.left += self.xvel
self.collide(self.xvel, 0, blocks)
# move vertically, and check for vertical collisions
self.rect.top += self.yvel
self.on_ground = False;
self.collide(0, self.yvel, blocks)
Lo único que queda es usar un Clock
para limitar la velocidad de fotogramas para que el juego funcione a una velocidad constante. Eso es.
Aquí está el código completo:
import pygame,random
from pygame.locals import *
from collections import namedtuple
pygame.init()
clock=pygame.time.Clock()
screen=pygame.display.set_mode((640,480))
max_gravity = 100
class Block(object):
sprite = pygame.image.load("dirt.png").convert_alpha()
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
class Player(object):
sprite = pygame.image.load("dirt.png").convert()
sprite.set_colorkey((0,255,0))
def __init__(self, x, y):
self.rect = self.sprite.get_rect(centery=y, centerx=x)
# indicates that we are standing on the ground
# and thus are "allowed" to jump
self.on_ground = True
self.xvel = 0
self.yvel = 0
self.jump_speed = 10
self.move_speed = 8
def update(self, move, blocks):
# check if we can jump
if move.up and self.on_ground:
self.yvel -= self.jump_speed
# simple left/right movement
if move.left: self.xvel = -self.move_speed
if move.right: self.xvel = self.move_speed
# if in the air, fall down
if not self.on_ground:
self.yvel += 0.3
# but not too fast
if self.yvel > max_gravity: self.yvel = max_gravity
# if no left/right movement, x speed is 0, of course
if not (move.left or move.right):
self.xvel = 0
# move horizontal, and check for horizontal collisions
self.rect.left += self.xvel
self.collide(self.xvel, 0, blocks)
# move vertically, and check for vertical collisions
self.rect.top += self.yvel
self.on_ground = False;
self.collide(0, self.yvel, blocks)
def collide(self, xvel, yvel, blocks):
# all blocks that we collide with
for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:
# if xvel is > 0, we know our right side bumped
# into the left side of a block etc.
if xvel > 0: self.rect.right = block.rect.left
if xvel < 0: self.rect.left = block.rect.right
# if yvel > 0, we are falling, so if a collision happpens
# we know we hit the ground (remember, we seperated checking for
# horizontal and vertical collision, so if yvel != 0, xvel is 0)
if yvel > 0:
self.rect.bottom = block.rect.top
self.on_ground = True
self.yvel = 0
# if yvel < 0 and a collision occurs, we bumped our head
# on a block above us
if yvel < 0: self.rect.top = block.rect.bottom
blocklist = []
player = []
colliding = False
Move = namedtuple(''Move'', [''up'', ''left'', ''right''])
while True:
screen.fill((25,30,90))
mse = pygame.mouse.get_pos()
key = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == QUIT: exit()
if key[K_LSHIFT]:
if event.type==MOUSEMOTION:
if not any(block.rect.collidepoint(mse) for block in blocklist):
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
else:
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
for b in to_remove:
blocklist.remove(b)
if not to_remove:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
blocklist.append(Block(x+16,y+16))
elif event.button == 3:
x=(int(mse[0]) / 32)*32
y=(int(mse[1]) / 32)*32
player=[]
player.append(Player(x+16,y+16))
move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])
for b in blocklist:
screen.blit(b.sprite, b.rect)
for p in player:
p.update(move, blocklist)
screen.blit(p.sprite, p.rect)
clock.tick(60)
pygame.display.flip()
Tenga en cuenta que cambié los nombres de las imágenes, así que solo necesito un solo archivo de imagen para probar esto. Además, no sé por qué mantienes al jugador en una lista, pero aquí hay una buena animación de nuestro juego en acción: