====== Scrolling dans le jeu de plateforme ======
On souhaite que la scène du jeu soit plus grande que l'écran. À mesure que le personnage se déplace, on découvre l'espace du jeu.
{{ :nsi:tds:pygame:plateforme:plateformes_scrolling.svg?800x280 |}}
À présent, quand on indique les coordonnées d'un sprite (exemple le sprite en vert) il faut savoir s'il s'agit de coordonnées par rapport au bord de l'écran qui est affiché ou s'il s'agit de coordonnées par rapport au bord de la scène.
Tous les sprites ont attribut ''rect'' qui définit leur position. Si on utilise la fonction ''draw'' des groupes de sprites, ''rect'' sert à définir la position du sprite **à l'écran**. On a donc deux options :
* doter tous les sprites de deux ensembles de coordonnées. ''rect'' pour la position à l'écran et ''(x,y)'' pour la position relativement à la scène de jeu.
* modifier la fonction ''draw'' utilisée pour tenir compte du décalage de la caméra.
Après avoir tenté la première option, je passe à la seconde option (//c'est ce que m'a montré l'un d'entre vous qui m'a fait comprendre que c'était mieux !//)
=== Camera ===
Je propose donc de créer un objet ''Camera'' dont le but est de contenir les informations sur le cadre et de prendre en charge la nouvelle fonction ''draw'' :
class Camera:
def __init__(self, originex:int, originey:int, zone):
"""
originex, originey: position initiale du cadre
zone: (left, right, top, bottom)
généralement on veut que la caméra suive le joueur.
on précise alors dans quel zone de l'écran il doit rester.
Si le joueur tend à en sortir, on ajuste l'écran
"""
self.x = originex
self.y = originey
self.left, self.right, self.top, self.bottom = zone
def adjust(self, px, py):
"""
px, py: coordonnées du joueur par rapport à la scène
selon les coordonnées px, py fournies
réajuste la position de la caméra
"""
if px < self.left + self.x:
# il faut décaler l'écran vers la gauche
self.x = px - self.left
elif px > self.right + self.x:
# il faut décaler l'écran vers la droite
self.x = px - self.right
if py < self.top + self.y:
# il faut décaler l'écran vers le haut
self.y = py - self.top
elif py > self.bottom + self.y:
# il faut décaler l'écran vers la droite
self.y = py - self.bottom
def draw_sprite(self, surface, sprite):
"""
surface: objet Surface sur lequel imprimer le sprite
sprite: objet Sprite à dessiner
dessine un sprite sur la surface tenant compte de l'offset
renvoie le rect de ce qui a été dessiné
"""
# on calcule la position (x,y) du sprite à l'écran
# tenant compte de :
# - sa position dans la scène (sprite.rect)
# - de la position de la caméra (self.x, self.y)
x = sprite.rect.left - self.x
y = sprite.rect.top - self.y
return surface.blit(sprite.image, (x,y))
def draw(self, surface, group):
"""
surface: objet Surface sur lequel imprimer les sprites
group: groupe de sprites
dessine les sprites du groupe sur une surface
"""
for sprite in group:
self.draw_sprite(surface, sprite)
=== Modification du player ===
Puisque la caméra se déplace, cela n'a plus de sens de contraindre la position de ''player'' entre un ''xmin'' et un ''xmax''. On pourra donc enlever cette configuration de ''player''.
=== Modification du main ===
# main.py
...
from camera import Camera
...
cam = Camera(0, 0, (100, 700, 0, 600))
# signifie que la caméra est initialisée en haut à gauche du niveau
# et que la caméra bougera pour que player ne soit jamais
# en dehors de la zone délimitée par (100, 700, 0, 600)
# donc si en allant à droite, le joueur dépasse 700, la caméra le suit.
...
while running:
clock.tick(30)
screen.fill((0,0,0))
player.update_position(platesformes_sprites)
cam.adjust(player.rect.centerx, player.rect.centery)
cam.draw(screen, all_sprites)
...
Dans la boucle :
* on met à jour la position du joueur selon son mouvement,
* on ajuste la caméra selon la nouvelle position du joueur,
* on dessine les sprites en utilisant le ''draw'' de la caméra afin de tenir compte de l'offset de cette caméra.
Certains sprites pourraient ne pas être soumis au mouvement de caméra. Par exemple le hud. Ces sprites ne devront alors pas être soumis à ''cam.draw_sprite''. On devrait d'ailleurs renommer ''all_sprites'' en un nom qui exprime clairement que les sprites du groupe sont soumis au mouvement de caméra.
=== optimisation ===
Dans ''Camera.draw'', la boucle ''for'' peut occasionner des lenteurs. Il faut dire que les boucles ''for'' de Python sont lentes.
On peut ruser en utilisant une méthode qui utilise une boucle, mais une boucle cachée et plus rapide : on peut utiliser la fonction ''map'' typique de la [[nsi:terminales:programmation_fonctionnelle:presentation|programmation fonctionnelle]].
>>> t = [1, 2, 3, 4, 5]
>>> map(lambda x:x**2, t)
Avec ''map'', on applique une fonction à tous les éléments d'un itérable comme une ''list'' ou un groupe de sprites. Dans l'exemple ci-dessus, la fonction est $x \mapsto x^2$. Dans notre exemple, la fonction serait plutôt la fonction consistant à dessiner un sprite.
Vous remarquez que ''map'' tout seul n'a pas produit le résultat désiré. Il a produit un objet de type ''