python - sprites - Agrega desplazamiento a una plataforma en pygame
pygame matrices (3)
Ok, entonces incluí el código para mi proyecto a continuación, solo estoy haciendo algunos experimentos con pygame para hacer un juego de plataformas. Estoy tratando de descubrir cómo hacer un desplazamiento muy simple que sigue al jugador, por lo que el jugador es el centro de la cámara y rebota / lo sigue. ¿Alguien puede ayudarme?
import pygame
from pygame import *
WIN_WIDTH = 800
WIN_HEIGHT = 640
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 32
FLAGS = 0
CAMERA_SLACK = 30
def main():
global cameraX, cameraY
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("Use arrows to move!")
timer = pygame.time.Clock()
up = down = left = right = running = False
bg = Surface((32,32))
bg.convert()
bg.fill(Color("#000000"))
entities = pygame.sprite.Group()
player = Player(32, 32)
platforms = []
x = y = 0
level = [
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
"P P",
"P P",
"P P",
"P P",
"P P",
"P P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P P",
"P P",
"P P",
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]
# build the level
for row in level:
for col in row:
if col == "P":
p = Platform(x, y)
platforms.append(p)
entities.add(p)
if col == "E":
e = ExitBlock(x, y)
platforms.append(e)
entities.add(e)
x += 32
y += 32
x = 0
entities.add(player)
while 1:
timer.tick(60)
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYDOWN and e.key == K_DOWN:
down = True
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYDOWN and e.key == K_SPACE:
running = True
if e.type == KEYUP and e.key == K_UP:
up = False
if e.type == KEYUP and e.key == K_DOWN:
down = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
# draw background
for y in range(32):
for x in range(32):
screen.blit(bg, (x * 32, y * 32))
# update player, draw everything else
player.update(up, down, left, right, running, platforms)
entities.draw(screen)
pygame.display.update()
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Player(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.xvel = 0
self.yvel = 0
self.onGround = False
self.image = Surface((32,32))
self.image.fill(Color("#0000FF"))
self.image.convert()
self.rect = Rect(x, y, 32, 32)
def update(self, up, down, left, right, running, platforms):
if up:
# only jump if on the ground
if self.onGround: self.yvel -= 10
if down:
pass
if running:
self.xvel = 12
if left:
self.xvel = -8
if right:
self.xvel = 8
if not self.onGround:
# only accelerate with gravity if in the air
self.yvel += 0.3
# max falling speed
if self.yvel > 100: self.yvel = 100
if not(left or right):
self.xvel = 0
# increment in x direction
self.rect.left += self.xvel
# do x-axis collisions
self.collide(self.xvel, 0, platforms)
# increment in y direction
self.rect.top += self.yvel
# assuming we''re in the air
self.onGround = False;
# do y-axis collisions
self.collide(0, self.yvel, platforms)
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, ExitBlock):
pygame.event.post(pygame.event.Event(QUIT))
if xvel > 0:
self.rect.right = p.rect.left
print "collide right"
if xvel < 0:
self.rect.left = p.rect.right
print "collide left"
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
self.yvel = 0
if yvel < 0:
self.rect.top = p.rect.bottom
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.image = Surface((32, 32))
self.image.convert()
self.image.fill(Color("#DDDDDD"))
self.rect = Rect(x, y, 32, 32)
def update(self):
pass
class ExitBlock(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image.fill(Color("#0033FF"))
if __name__ == "__main__":
main()
Como es correcto, tienes un fondo estático, y el jugador que controlas, está deslumbrado en la posición en la que está, tienes 2 opciones para mostrar siempre el personaje en el medio.
Si el mapa es lo suficientemente pequeño, puede tener un gran img A, y derivar un rectángulo, basado en la posición del jugador que tendrá el tamaño de la pantalla. De esa forma, el jugador siempre estará en el medio. Un Rect.clamp (Rect) o Rect.clamp_ip (Rect) te ayudará en eso.
Otro enfoque es tener una tupla diferente para la posición en la pantalla. El jugador tendrá un valor constante en el centro de la pantalla, mientras que la posición de los fondos será el negativo de la posición del jugador.
Debes aplicar un desplazamiento a la posición de tus entidades cuando las dibujas. Vamos a llamar a eso compensar una camera
, ya que este es el efecto que queremos lograr con esto.
En primer lugar, no podemos (y no debemos) utilizar la función draw
del grupo sprite, ya que los sprites no necesitan saber que su posición ( rect
) no es la posición en la que van a dibujarse en la pantalla. (Por supuesto, podríamos subclasificar la clase Group
y volver a draw
para tener en cuenta la cámara, pero por el mero hecho de aprender, seamos explícitos aquí).
Comencemos creando una clase de Camera
para mantener el estado del desplazamiento que queremos aplicar a la posición de nuestras entidades:
class Camera(object):
def __init__(self, camera_func, width, height):
self.camera_func = camera_func
self.state = Rect(0, 0, width, height)
def apply(self, target):
return target.rect.move(self.state.topleft)
def update(self, target):
self.state = self.camera_func(self.state, target.rect)
algunas cosas a tener en cuenta aquí:
Necesitamos almacenar la posición de la cámara y el ancho y alto del nivel en píxeles (ya que queremos dejar de desplazarnos por los bordes del nivel). Utilicé un Rect
para almacenar toda esta información, pero podría simplemente usar algunos campos.
Usar Rect
es útil en la función de apply
. Aquí es donde volvemos a calcular la posición de una entidad en la pantalla para aplicar el desplazamiento.
Una vez por iteración del ciclo principal, necesitamos actualizar la posición de la cámara, por lo tanto, está la función de update
. Simplemente altera el estado al llamar a la función camera_func
, que hará todo el trabajo duro por nosotros. Lo implementamos más tarde.
Vamos a crear una instancia de la cámara:
for row in level:
...
total_level_width = len(level[0])*32 # calculate size of level in pixels
total_level_height = len(level)*32 # maybe make 32 an constant
camera = Camera(*to_be_implemented*, total_level_width, total_level_height)
entities.add(player)
...
y alterar nuestro ciclo principal:
# draw background
for y in range(32):
...
camera.update(player) # camera follows player. Note that we could also follow any other sprite
# update player, draw everything else
player.update(up, down, left, right, running, platforms)
for e in entities:
# apply the offset to each entity.
# call this for everything that should scroll,
# which is basically everything other than GUI/HUD/UI
screen.blit(e.image, camera.apply(e))
pygame.display.update()
Nuestra clase de cámara ya es muy flexible y, sin embargo, muy simple. Puede usar diferentes tipos de desplazamiento (proporcionando diferentes funciones de camera_func
), y puede seguir cualquier sprite arbitario, no solo el jugador. Incluso puedes cambiar esto en tiempo de ejecución.
Ahora para la implementación de camera_func
. Un enfoque simple es simplemente centrar al jugador (o cualquier entidad que deseemos seguir) en la pantalla, y la implementación es directa:
def simple_camera(camera, target_rect):
l, t, _, _ = target_rect # l = left, t = top
_, _, w, h = camera # w = width, h = height
return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h)
Simplemente tomamos la posición de nuestro target
y agregamos el tamaño de pantalla medio total. Puedes intentarlo creando tu cámara así:
camera = Camera(simple_camera, total_level_width, total_level_height)
Hasta aquí todo bien. Pero tal vez no queremos ver el fondo negro fuera del nivel? Qué tal si:
def complex_camera(camera, target_rect):
l, t, _, _ = target_rect
_, _, w, h = camera
l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h # center player
l = min(0, l) # stop scrolling at the left edge
l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge
t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom
t = min(0, t) # stop scrolling at the top
return Rect(l, t, w, h)
Aquí simplemente utilizamos las funciones min
/ max
para asegurarnos de que no nos desplazamos fuera del nivel.
Pruébalo creando tu cámara así:
camera = Camera(complex_camera, total_level_width, total_level_height)
Hay una pequeña animación de nuestro nuevo desplazamiento en acción:
Aquí está el código completo nuevamente (nota que cambié tu nivel un poco para ser más grande y tener algunas plataformas más):
#! /usr/bin/python
import pygame
from pygame import *
WIN_WIDTH = 800
WIN_HEIGHT = 640
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 32
FLAGS = 0
CAMERA_SLACK = 30
def main():
global cameraX, cameraY
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("Use arrows to move!")
timer = pygame.time.Clock()
up = down = left = right = running = False
bg = Surface((32,32))
bg.convert()
bg.fill(Color("#000000"))
entities = pygame.sprite.Group()
player = Player(32, 32)
platforms = []
x = y = 0
level = [
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
"P P",
"P P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P P",
"P P",
"P PPPPPPPP P",
"P P",
"P PPPPPPP P",
"P PPPPPP P",
"P P",
"P PPPPPPP P",
"P P",
"P PPPPPP P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P P",
"P P",
"P P",
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]
# build the level
for row in level:
for col in row:
if col == "P":
p = Platform(x, y)
platforms.append(p)
entities.add(p)
if col == "E":
e = ExitBlock(x, y)
platforms.append(e)
entities.add(e)
x += 32
y += 32
x = 0
total_level_width = len(level[0])*32
total_level_height = len(level)*32
camera = Camera(complex_camera, total_level_width, total_level_height)
entities.add(player)
while 1:
timer.tick(60)
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYDOWN and e.key == K_DOWN:
down = True
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYDOWN and e.key == K_SPACE:
running = True
if e.type == KEYUP and e.key == K_UP:
up = False
if e.type == KEYUP and e.key == K_DOWN:
down = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
# draw background
for y in range(32):
for x in range(32):
screen.blit(bg, (x * 32, y * 32))
camera.update(player)
# update player, draw everything else
player.update(up, down, left, right, running, platforms)
for e in entities:
screen.blit(e.image, camera.apply(e))
pygame.display.update()
class Camera(object):
def __init__(self, camera_func, width, height):
self.camera_func = camera_func
self.state = Rect(0, 0, width, height)
def apply(self, target):
return target.rect.move(self.state.topleft)
def update(self, target):
self.state = self.camera_func(self.state, target.rect)
def simple_camera(camera, target_rect):
l, t, _, _ = target_rect
_, _, w, h = camera
return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h)
def complex_camera(camera, target_rect):
l, t, _, _ = target_rect
_, _, w, h = camera
l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h
l = min(0, l) # stop scrolling at the left edge
l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge
t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom
t = min(0, t) # stop scrolling at the top
return Rect(l, t, w, h)
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Player(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.xvel = 0
self.yvel = 0
self.onGround = False
self.image = Surface((32,32))
self.image.fill(Color("#0000FF"))
self.image.convert()
self.rect = Rect(x, y, 32, 32)
def update(self, up, down, left, right, running, platforms):
if up:
# only jump if on the ground
if self.onGround: self.yvel -= 10
if down:
pass
if running:
self.xvel = 12
if left:
self.xvel = -8
if right:
self.xvel = 8
if not self.onGround:
# only accelerate with gravity if in the air
self.yvel += 0.3
# max falling speed
if self.yvel > 100: self.yvel = 100
if not(left or right):
self.xvel = 0
# increment in x direction
self.rect.left += self.xvel
# do x-axis collisions
self.collide(self.xvel, 0, platforms)
# increment in y direction
self.rect.top += self.yvel
# assuming we''re in the air
self.onGround = False;
# do y-axis collisions
self.collide(0, self.yvel, platforms)
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, ExitBlock):
pygame.event.post(pygame.event.Event(QUIT))
if xvel > 0:
self.rect.right = p.rect.left
print "collide right"
if xvel < 0:
self.rect.left = p.rect.right
print "collide left"
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
self.yvel = 0
if yvel < 0:
self.rect.top = p.rect.bottom
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.image = Surface((32, 32))
self.image.convert()
self.image.fill(Color("#DDDDDD"))
self.rect = Rect(x, y, 32, 32)
def update(self):
pass
class ExitBlock(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image.fill(Color("#0033FF"))
if __name__ == "__main__":
main()
La única forma de hacerlo es separar posiciones lógicas en el mapa, desde posiciones físicas en la pantalla.
Cualquier código relacionado con dibujar realmente su mapa en la pantalla, en su caso todos los atributos .rect
de sus sprites, tiene que hacerlo en base a un desplazamiento de qué parte del mapa está realmente usando la pantalla.
Por ejemplo, su pantalla podría mostrar su mapa comenzando con la posición (10,10) en la esquina superior izquierda - todos los códigos relacionados con la visualización (que en el caso anterior son los atributos .rect
) deben restar el desplazamiento de la pantalla de la posición lógica actual - (supongamos que el carácter está en coords de mapa (12,15) - por lo tanto, debe dibujarse en (12,15) - (10, 10) -> (2, 5) * BLOCK_SIZE) En el ejemplo anterior, BLOCK_SIZE está codificado a 32,32, por lo que desea dibujarlo en la posición de píxel físico (2 * 32, 5 * 32) en la pantalla)
(sugerencia: evite codificar las cosas de esta manera, haga una declaración constante al comienzo de su código)