Contrôle et affichage
Étape 3
Interception des interactions utilisateur.
Contrôle des déplacements du serpent.
Affichage du serpent sur la scène de jeu.
Ordonnancement des tâches du moteur de jeu.
Contrôler les déplacements du serpent
Le joueur contrôle les déplacements du serpent en utilisant le PAD directionnel de la META, c'est-à-dire les boutons LEFT
, RIGHT
, UP
et DOWN
:
En fonction du bouton sur lequel le joueur aura appuyé, il suffira d'appeler une fonction dirSnake()
chargée d'appliquer une commande directionnelle pour modifier le déplacement du serpent.
Cette fonction se définit très simplement :
# ----------------------------------------------------------
# Snake management
# ----------------------------------------------------------
def dirSnake(dx, dy):
snake['vx'] = dx
snake['vy'] = dy
dx
et dy
sont en effet directement associés à la vitesse de déplacement du serpent, puisqu'ils correspondent exactement à des variations instantanées de positions.
Il existe plusieurs manières d'intercepter la façon dont le joueur agit sur les boutons de la console. Nous utiliserons l'une d'entre elles, qui permet simplement de détecter si le joueur enfonce un bouton en particulier. Par exemple :
buttons.pressed(buttons.LEFT)
retourne un booléen (True
ou False
) selon que le bouton LEFT
est enfoncé ou non.
Les commandes directionnelles peuvent alors être facilement appliquées de la manière suivante :
# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------
def handleButtons():
if buttons.pressed(buttons.LEFT):
dirSnake(-1, 0)
elif buttons.pressed(buttons.RIGHT):
dirSnake(1, 0)
elif buttons.pressed(buttons.UP):
dirSnake(0, -1)
elif buttons.pressed(buttons.DOWN):
dirSnake(0, 1)
elif
est la contraction de else if
et permet de relancer le test sur une nouvelle condition.
À ce stade, le serpent ne se déplace pas encore véritablement... en effet, on n'a fait que lui affecter une vitesse. Il faut maintenant mettre en œuvre son déplacement en modifiant la position de sa tête en fonction de la vitesse qu'on vient de lui appliquer :
# ----------------------------------------------------------
# Snake management
# ----------------------------------------------------------
def moveSnake():
h = snake['head']
x = snake['x'][h]
y = snake['y'][h]
h = (h + 1) % snake['len']
snake['x'][h] = x + snake['vx']
snake['y'][h] = y + snake['vy']
snake['head'] = h
La fonction moveSnake()
récupère d'abord les coordonnées de la tête du serpent dans les listes snake['x']
et snake['y']
:
h = snake['head']
x = snake['x'][h]
y = snake['y'][h]
Puis elle avance la tête de lecture d'un cran, en effectuant une rotation grâce à l'opérateur modulo %
:
h = (h + 1) % snake['len']
La nouvelle position de la tête du serpent est alors calculée et enregistrée dans les listes à l'endroit pointé par la tête de lecture :
snake['x'][h] = x + snake['vx']
snake['y'][h] = y + snake['vy']
On sauvegarde enfin la nouvelle position de la tête de lecture :
snake['head'] = h
Afficher le serpent sur la scène
À l'étape précédente, nous en étions restés à la définition suivante pour la fonction draw()
, chargée d'effectuer le rendu graphique de la scène de jeu :
# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------
def draw():
display.clear(COLOR_BG)
display.setColor(COLOR_WALL)
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
On va avoir besoin de définir une nouvelle variable globale pour la couleur du serpent :
# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------
COLOR_SNAKE = 0xfd40
Et maintenant que les élément sur la scène commencent à se multiplier, on va aussi structurer l'affichage en créant des fonctions plus spécialisées :
# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------
def draw():
clearScreen()
drawWalls()
def clearScreen():
display.clear(COLOR_BG)
def drawWalls():
display.setColor(COLOR_WALL)
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
On va y ajouter une nouvelle fonction drawSnake()
qui sera chargée d'afficher le serpent sur la grille. La manière la plus simple d'afficher le serpent est de dessiner un à un chacun de ses tronçons :
def drawSnake():
display.setColor(COLOR_SNAKE)
n = snake['len']
for i in range(n):
x = snake['x'][i]
y = snake['y'][i]
display.fillRect(
OX + x * SNAKE_SIZE,
OY + y * SNAKE_SIZE,
SNAKE_SIZE,
SNAKE_SIZE
)
Pour cela, on parcourt simplement les listes snake['x']
et snake['y']
pour y piocher les coordonnées de chaque tronçon et on dessine un rectangle de la taille d'une cellule de la grille. Néanmoins, il ne s'agit pas de la manière la plus rapide d'exécuter ce tracé. Nous verrons dans une prochaine étape comment l'optimiser.
Pour le moment, ajoutons l'appel à cette nouvelle fonction dans notre fonction draw()
:
# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------
def draw():
clearScreen()
drawWalls()
drawSnake()
Intégration à l'ordonnanceur
Maintenant, il nous reste à intégrer tout ce que nous venons de faire à l'ordonnanceur principal pour que le jeu commence enfin à prendre forme.
Pour cela, nous allons organiser les tâches à exécuter en fonction des phases du jeu. Au départ, quand le jeu démarre, il faut réinitialiser le serpent. Puis, on lance la partie et on active le contrôleur chargé d'observer le comportement du joueur en examinant les boutons de la console. L'affichage de la scène de jeu doit être réalisé également.
Pour modéliser ces différentes phase du jeu, on va définir quelques variables globales :
# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------
MODE_START = 0
MODE_PLAY = 1
Puis on va définir un nouveau dictionnaire pour représenter, en quelque sorte, le moteur du jeu :
# ----------------------------------------------------------
# Initialization
# ----------------------------------------------------------
game = {
'mode': MODE_START
}
Le moteur de jeu va donc gérer les différentes phases du jeu.
Maintenant, revenons sur notre fonction tick()
, chargée d'appliquer l'ordonnancement des différentes tâches :
# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------
def tick():
if game['mode'] == MODE_START:
resetSnake()
game['mode'] = MODE_PLAY
elif game['mode'] == MODE_PLAY:
handleButtons()
moveSnake()
draw()
Et voyons un peu ce que ça donne :
Ça marche plutôt bien, nan ?
Bon... ok, la détection des murs n'est pas encore implémentée... ni le fait que le serpent ne puisse pas rebrousser chemin... car, dans ce cas, il se mordrait la queue. Nous allons traiter ces cas de figure à l'étape suivante !
Voici le script code.py
complet, qui intègre tout ce que nous avons fait jusqu'à maintenant :
from gamebuino_meta import waitForUpdate, display, color, buttons
# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 64
SNAKE_SIZE = 2
SNAKE_LENGTH = 4
COLS = (SCREEN_WIDTH - 4) // SNAKE_SIZE
ROWS = (SCREEN_HEIGHT - 4) // SNAKE_SIZE
OX = (SCREEN_WIDTH - COLS * SNAKE_SIZE) // 2
OY = (SCREEN_HEIGHT - ROWS * SNAKE_SIZE) // 2
COLOR_BG = 0x69c0
COLOR_WALL = 0xed40
COLOR_SNAKE = 0xfd40
MODE_START = 0
MODE_PLAY = 1
# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------
def tick():
if game['mode'] == MODE_START:
resetSnake()
game['mode'] = MODE_PLAY
elif game['mode'] == MODE_PLAY:
handleButtons()
moveSnake()
draw()
def handleButtons():
if buttons.pressed(buttons.LEFT):
dirSnake(-1, 0)
elif buttons.pressed(buttons.RIGHT):
dirSnake(1, 0)
elif buttons.pressed(buttons.UP):
dirSnake(0, -1)
elif buttons.pressed(buttons.DOWN):
dirSnake(0, 1)
# ----------------------------------------------------------
# Snake management
# ----------------------------------------------------------
def resetSnake():
x = COLS // 2
y = ROWS // 2
snake['x'] = []
snake['y'] = []
for _ in range(SNAKE_LENGTH):
snake['x'].append(x)
snake['y'].append(y)
snake['head'] = SNAKE_LENGTH - 1
snake['len'] = SNAKE_LENGTH
snake['vx'] = 0
snake['vy'] = 0
def dirSnake(dx, dy):
snake['vx'] = dx
snake['vy'] = dy
def moveSnake():
h = snake['head']
x = snake['x'][h]
y = snake['y'][h]
h = (h + 1) % snake['len']
snake['x'][h] = x + snake['vx']
snake['y'][h] = y + snake['vy']
snake['head'] = h
# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------
def draw():
clearScreen()
drawWalls()
drawSnake()
def clearScreen():
display.clear(COLOR_BG)
def drawWalls():
display.setColor(COLOR_WALL)
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
def drawSnake():
display.setColor(COLOR_SNAKE)
n = snake['len']
for i in range(n):
x = snake['x'][i]
y = snake['y'][i]
display.fillRect(OX + x * SNAKE_SIZE, OY + y * SNAKE_SIZE, SNAKE_SIZE, SNAKE_SIZE)
# ----------------------------------------------------------
# Initialization
# ----------------------------------------------------------
game = {
'mode': MODE_START
}
snake = {
'x': [],
'y': [],
'head': 0,
'len': 0,
'vx': 0,
'vy': 0
}
# ----------------------------------------------------------
# Main loop
# ----------------------------------------------------------
while True:
waitForUpdate()
tick()