Ok donc j'ai inclus le code de mon projet ci-dessous, je fais juste quelques expériences avec pygame sur la création d'un jeu de plateforme. J'essaie de comprendre comment faire un défilement très simple qui suit le joueur, donc le joueur est au centre de la caméra et il rebondit/le suit. Quelqu'un peut-il m'aider?
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 __== "__main__":
main()
Vous devez appliquer un décalage à la position de vos entités lorsque vous les dessinez. Appelons cela offset a camera
, car c'est l'effet que nous voulons obtenir avec cela.
Tout d'abord, nous ne pouvons pas utiliser la fonction draw
du groupe Sprite, car les sprites n'ont pas besoin de savoir que leur position (rect
) n'est pas la position vers laquelle ils vont être dessiné sur l'écran (à la fin, nous sous-classerons la classe Group
et réimplémenterons son draw
pour être au courant de la caméra, mais commençons lentement).
Commençons par créer une classe Camera
pour conserver l'état du décalage que nous voulons appliquer à la position de nos entités:
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)
certaines choses à noter ici:
Nous devons stocker la position de la caméra et la largeur et la hauteur du niveau en pixels (car nous voulons arrêter le défilement sur les bords du niveau). J'ai utilisé un Rect
pour stocker toutes ces informations, mais vous pouvez facilement utiliser certains champs.
L'utilisation de Rect
est très pratique dans la fonction apply
. C'est là que nous recalculons la position d'une entité sur l'écran pour appliquer le défilement.
Une fois par itération de la boucle principale, nous devons mettre à jour la position de la caméra, d'où la fonction update
. Il modifie simplement l'état en appelant le camera_func
fonction, qui fera tout le travail acharné pour nous. Nous l'implémentons plus tard.
Créons une instace de la caméra:
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)
...
et modifier notre boucle principale:
# 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()
Notre classe de caméras est déjà très flexible et pourtant très simple. Il peut utiliser différents types de défilement (en fournissant différents camera_func
fonctions), et il peut suivre n'importe quel Sprite arbitraire, pas seulement le joueur. Vous pouvez même changer cela au moment de l'exécution.
Maintenant pour l'implémentation de camera_func
. Une approche simple consiste à simplement centrer le joueur (ou l'entité que nous voulons suivre) à l'écran, et la mise en œuvre est simple:
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)
Nous prenons simplement la position de notre target
, et ajoutons la moitié de la taille totale de l'écran. Vous pouvez l'essayer en créant votre caméra comme ceci:
camera = Camera(simple_camera, total_level_width, total_level_height)
Jusqu'ici tout va bien. Mais peut-être que nous ne voulons pas voir le fond noir en dehors du niveau? Que diriez-vous:
def complex_camera(camera, target_rect):
# we want to center target_rect
x = -target_rect.center[0] + WIN_WIDTH/2
y = -target_rect.center[1] + WIN_HEIGHT/2
# move the camera. Let's use some vectors so we can easily substract/multiply
camera.topleft += (pygame.Vector2((x, y)) - pygame.Vector2(camera.topleft)) * 0.06 # add some smoothness coolnes
# set max/min x/y so we don't see stuff outside the world
camera.x = max(-(camera.width-WIN_WIDTH), min(0, camera.x))
camera.y = max(-(camera.height-WIN_HEIGHT), min(0, camera.y))
return camera
Ici, nous utilisons simplement les fonctions min
/max
pour nous assurer de ne pas faire défiler en dehors du niveau out.
Essayez-le en créant votre appareil photo comme ceci:
camera = Camera(complex_camera, total_level_width, total_level_height)
Il y a une petite animation de notre défilement final en action:
Voici à nouveau le code complet. Notez que j'ai changé certaines choses:
pygame.key.get_pressed
au lieu #! /usr/bin/python
import pygame
from pygame import *
SCREEN_SIZE = pygame.Rect((0, 0, 800, 640))
TILE_SIZE = 32
GRAVITY = pygame.Vector2((0, 0.3))
class CameraAwareLayeredUpdates(pygame.Sprite.LayeredUpdates):
def __init__(self, target, world_size):
super().__init__()
self.target = target
self.cam = pygame.Vector2(0, 0)
self.world_size = world_size
if self.target:
self.add(target)
def update(self, *args):
super().update(*args)
if self.target:
x = -self.target.rect.center[0] + SCREEN_SIZE.width/2
y = -self.target.rect.center[1] + SCREEN_SIZE.height/2
self.cam += (pygame.Vector2((x, y)) - self.cam) * 0.05
self.cam.x = max(-(self.world_size.width-SCREEN_SIZE.width), min(0, self.cam.x))
self.cam.y = max(-(self.world_size.height-SCREEN_SIZE.height), min(0, self.cam.y))
def draw(self, surface):
spritedict = self.spritedict
surface_blit = surface.blit
dirty = self.lostsprites
self.lostsprites = []
dirty_append = dirty.append
init_rect = self._init_rect
for spr in self.sprites():
rec = spritedict[spr]
newrect = surface_blit(spr.image, spr.rect.move(self.cam))
if rec is init_rect:
dirty_append(newrect)
else:
if newrect.colliderect(rec):
dirty_append(newrect.union(rec))
else:
dirty_append(newrect)
dirty_append(rec)
spritedict[spr] = newrect
return dirty
def main():
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE.size)
pygame.display.set_caption("Use arrows to move!")
timer = pygame.time.Clock()
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",]
platforms = pygame.Sprite.Group()
player = Player(platforms, (TILE_SIZE, TILE_SIZE))
level_width = len(level[0])*TILE_SIZE
level_height = len(level)*TILE_SIZE
entities = CameraAwareLayeredUpdates(player, pygame.Rect(0, 0, level_width, level_height))
# build the level
x = y = 0
for row in level:
for col in row:
if col == "P":
Platform((x, y), platforms, entities)
if col == "E":
ExitBlock((x, y), platforms, entities)
x += TILE_SIZE
y += TILE_SIZE
x = 0
while 1:
for e in pygame.event.get():
if e.type == QUIT:
return
if e.type == KEYDOWN and e.key == K_ESCAPE:
return
entities.update()
screen.fill((0, 0, 0))
entities.draw(screen)
pygame.display.update()
timer.tick(60)
class Entity(pygame.Sprite.Sprite):
def __init__(self, color, pos, *groups):
super().__init__(*groups)
self.image = Surface((TILE_SIZE, TILE_SIZE))
self.image.fill(color)
self.rect = self.image.get_rect(topleft=pos)
class Player(Entity):
def __init__(self, platforms, pos, *groups):
super().__init__(Color("#0000FF"), pos)
self.vel = pygame.Vector2((0, 0))
self.onGround = False
self.platforms = platforms
self.speed = 8
self.jump_strength = 10
def update(self):
pressed = pygame.key.get_pressed()
up = pressed[K_UP]
left = pressed[K_LEFT]
right = pressed[K_RIGHT]
running = pressed[K_SPACE]
if up:
# only jump if on the ground
if self.onGround: self.vel.y = -self.jump_strength
if left:
self.vel.x = -self.speed
if right:
self.vel.x = self.speed
if running:
self.vel.x *= 1.5
if not self.onGround:
# only accelerate with gravity if in the air
self.vel += GRAVITY
# max falling speed
if self.vel.y > 100: self.vel.y = 100
print(self.vel.y)
if not(left or right):
self.vel.x = 0
# increment in x direction
self.rect.left += self.vel.x
# do x-axis collisions
self.collide(self.vel.x, 0, self.platforms)
# increment in y direction
self.rect.top += self.vel.y
# assuming we're in the air
self.onGround = False;
# do y-axis collisions
self.collide(0, self.vel.y, self.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
if xvel < 0:
self.rect.left = p.rect.right
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, pos, *groups):
super().__init__(Color("#DDDDDD"), pos, *groups)
class ExitBlock(Platform):
def __init__(self, pos, *groups):
super().__init__(Color("#0033FF"), pos, *groups)
if __== "__main__":
main()
La seule façon de procéder consiste à séparer les positions logiques sur la carte des positions physiques à l'écran.
Tout code lié au dessin réel de votre carte à l'écran - dans votre cas, tous les .rect
les attributs de vos sprites - doivent le faire en fonction d'un décalage de la partie de votre carte que l'écran utilise réellement.
Par exemple, votre écran peut montrer votre carte en commençant par la position (10,10) en haut à gauche - tous affichent le code associé (qui dans le cas ci-dessus sont les .rect
attributes) devrait soustraire le décalage d'écran de la position logique actuelle - (disons que le caractère est dans les coordonnées de la carte (12,15) - donc, il devrait être tracé à (12,15) - (10, 10) -> (2 , 5) * BLOCK_SIZE) Dans votre exemple ci-dessus BLOCK_SIZE est codé en dur à 32,32, donc vous voulez le dessiner à la position physique des pixels (2 * 32, 5 * 32) sur l'écran)
(indice: évitez de coder en dur les choses de cette façon, faites-en une déclaration constante au début de votre code)
Comme vous le savez, vous avez un arrière-plan statique et le joueur que vous contrôlez est blitted dans la position dans laquelle il se trouve, vous avez 2 options pour toujours montrer le personnage au milieu.
Si votre carte est assez petite, vous pouvez avoir un grand img A et dériver un rectangle, basé sur la position du joueur qui sera la taille de l'écran. De cette façon, le joueur sera toujours au milieu. Un Rect.clamp (Rect) ou Rect.clamp_ip (Rect) vous y aidera.
Une autre approche consiste à avoir un Tuple différent pour la position à l'écran. Le joueur aura une valeur constante au centre de l'écran, tandis que la position des arrière-plans sera le négatif de la position du joueur.