TD2. Game On!

des sprites, des cartes et des boutons : premiers jeux

Auteur·rice·s
Affiliations

Mathieu Liedloff

Université d’Orléans

Nicolas Ollinger

Résumé

L’objectif de cette deuxième fiche est de découvrir les premiers éléments de TIC-80 spécifiquement orientés vers la production de jeux vidéos : interaction utilisateur à travers une manette de jeu, dessin de sprites et de cartes pour le décor, utilisation des éditeurs intégrés associés. Côté programmation, la boucle de jeu s’enrichie, la gestion de l’état de certains objets et d’un inventaire apparaît. En Python, on aborde la notion de fonction et les boucles s’enrichissent.

1 Retour sur TIC-80

Pendant cette séance, ne pas oublier de récupérer régulièrement les cartouches produites en utilisant save et get.

À vous de jouer

Ouvrir le lien GameOn TIC-80 dans une autre fenêtre ou un autre onglet du navigateur. C’est votre environnement de travail pour toute la séance ! Garder cette fenêtre ouverte.

Attention

Attention à bien utiliser la version de TIC-80 fournie pour ce TP pas une autre. Cette instance contient des éléments spécifiques pour vous aider pendant les séances. Bien sûr, les cartouches produites seront ensuite utilisables dans toute instance de TIC-80.

1.1 Nouvelle commande console

L’environnement de travail TIC-80 que nous mettons à disposition s’est enrichie depuis la séance précédente : une commande sel est apparue qui permet de modifier le contenu du tampon de copier-coller de TIC-80. Grâce à cette commande, il est possible de copier du code depuis et vers TIC-80.

commande sel

1.2 L’éditeur de sprites

L’éditeur de sprites (sprite editor) est le deuxième des cinq éditeurs spécialisés de TIC-80. On y accède depuis les éditeurs avec la touche F2 ou avec l’icône en forme de fantôme éditeur de sprite.

éditeur de sprites

L’éditeur de sprites permet de manipuler deux images de 128x128 pixels : les 256 sprites de décor, appelés habituellement tuiles, utilisables dans la carte ainsi qu’une seconde image de sprites supplémentaire. L’éditeur fourni quelques outils élémentaires pour simplifier l’édition : crayon de taille variable, pipette de sélection de couleur, sélection de zone rectangulaire pour la déplacer, remplissage, symétries et rotations. Il est possible d’éditer les sprites par zone de 8x8, 16x16, 32x32 ou 64x64 pixels grâce à la glissière en haut à droite.

éditeur de sprites

Le mode avancé de l’éditeur s’active en cochant une case en haut à gauche. Nous l’utiliserons pour associer des drapeaux aux sprites de décor. Huit drapeaux numérotés de 0 à 7 sont activables ou désactivables en un clic à côté de la zone de dessin. C’est au programmeur de leur associer une signification. Typiquement on pourra utiliser un drapeau pour indiquer quelles tuiles de décor bloquent le passage, quelles tuiles correspondent à des objets, etc.

éditeur de sprites

Les sprites sont sauvegardés dans la cartouche avec le code source.

À vous de jouer

À vos pinceaux ! Créer une nouvelle cartouche avec new python puis passer dans l’éditeur de sprites. Effacer les sprites déjà présents et dessiner les sprites suivant :

  • dans l’emplacement numéro 1, dessiner un petit personnage mignon ;
  • dans l’emplacement numéro 2, dessiner une clé, activer le drapeau numéro 1.
  • dans les emplacements numéro 16 à 19, dessiner des murs, activer le drapeau numéro 0 ;
  • dans l’emplacement numéro 3, dessiner une porte fermée, activer les drapeaux numéro 0 et 2.

exemple de réalisation

Nous en aurons besoin plus tard, sauvegarder la cartouche sous le nom maze !

1.3 L’éditeur de carte

L’éditeur de carte (map editor) est le troisième des cinq éditeurs spécialisés de TIC-80. On y accède depuis les éditeurs avec la touche F3 ou avec l’icône en forme de fenêtre éditeur de sprite.

éditeur de carte

L’éditeur de carte permet de manipuler une carte de 240x136 tuiles de décor. Comme ces tuiles font 8x8 pixels, il suffit de 30x17 tuiles pour remplir l’écran. La zone de carte représente donc 8x8 écrans. L’éditeur propose différents outils accessible en haut à droite de l’écran. En particulier :

  • la touche TAB permet de sélectionner une zone d’édition sur la carte ;
  • rester appuyer sur la touche SHIFT permet de choisir la tuile active ;
  • un bouton permet de désactiver l’affichage de la grille.

éditeur de sprites
À vous de jouer

À vos pinceaux ! Charger la cartouche maze et passer en mode carte. Créer en haut à gauche de la carte (c’est l’écran qui s’affiche par défaut) un petit labyrinthe entouré de murs dans lesquel sont placés le personnage, la clé et la porte.

exemple de réalisation

Sauvegarder la cartouche !

1.4 Commandes console import/export

TIC-80 propose un mécanisme pour importer et exporter les éléments constitutifs des cartouches à travers les commandes import et export. Ainsi, les feuilles de tuiles et de sprites sont échangés sous la forme d’images au format PNG.

commandes import et export

Pour extraire les tuiles de décor il suffit d’utiliser la commande export tiles mazemaze précise le préfixe du nom de fichier crée (ici maze.png). Pour extraire la carte au format binaire on utilisera export map maze et pour l’extraire sous forme d’image export mapimg maze.

Exemple de feuille de tuiles

Pour importer des sprites, on utilise le nom de fichier avec son extension import tiles maze.png.

À vous de jouer

Pour valider cette partie et vérifier que vous avez bien compris le fonctionnement des sprites et de la carte en TIC-80, il est temps de faire une pause ludique ! Télécharger la cartouche .tic d’un jeu TIC-80 parmi ceux présentés en fin de fiche. Ajouter cette cartouche à votre TIC-80 et jouer une partie du jeu ! Ensuite, explorer les sprites et la carte du jeu : si le jeu choisi est trop pauvre de ce point de vue, réessayer avec un autre. Faites des modifications et relancer le jeu avec Ctrl+R pour les voir à l’écran.

2 Un peu de Python

Avant de nous lancer dans la création d’un premier jeu, musclons un peu notre Python !

Apprentissage de Python

Nous abordons toujours Python avec une approche de type « observer, copier et modifier » en vous fournissant du code TIC-80 à modifier. Si cette approche ne vous convient pas, nous vous encourageons fortement à travailler en dehors des séances de TD avec futurecoder, un outil d’aide à l’apprentissage de Python.

2.1 Fonctions

Pour garder un code source lisible et éviter de répéter plusieurs fois le même morceau de code, on utilise des fonctions. Dans le cas d’un jeu, on peut séparer la boucle de jeu en une fonction nommée update qui se charge de tester les entrées et de mettre à jour l’état du jeu et une fonction nommée draw qui se charge de mettre à jour l’affichage. Voici un exemple simple à utiliser avec les sprites de la cartouche maze :

# script:  python

x=120
y=68

def update():
  global x,y
  if btn(0): y=y-1
  if btn(1): y=y+1
  if btn(2): x=x-1
  if btn(3): x=x+1

def draw():
  cls()
  spr(1,x,y)

def TIC():
  update()
  draw()

Ce code utilise deux fonctions de l’API TIC-80 que nous n’avons pas encore rencontrées :

  1. btn(x) teste si le bouton x de la manette de jeu est enfoncé (explication plus loin) ;
  2. spr(z,x,y) affiche le sprite numéro z en position (x,y) à l’écran.
À vous de jouer

Copier ce code dans TIC-80 dans la cartouche maze. Lancer avec run et vérifier que le sprite réagit bien aux touches flèches. Supprimer la ligne update() de la fonction TIC() et vérifier que le sprite ne répond plus aux commandes.

Retirer la ligne cls() de la fonction draw() pour la placer juste avant la définition de TIC(). Remettre une boucle qui efface des pixels comme dans le TD1. Modifier la fonction update() pour n’autoriser que les déplacements horizontaux. Tester !

2.2 Expressions booléennes

Nous avons vu dans le TD1 des instructions conditionnelles introduites par le mot-clé if :

if t%2==0:
  circ(x,y,5,5)
else:
  rect(x-5,y-5,10,10,3)

Derrière le mot-clé if on place une expression qui est évaluée comme un booléen. Un booléen peut prendre la valeur vrai ou faux. Il est possible de créer des expressions booléennes de multiples manières :

  • avec les constantes True et False ;
  • en comparant des expressions avec ==, <, <=, > ou >= ;
  • en combinant des expressions :
    • prendre la négation d’une expression avec not
    • prendre la conjonction de deux expressions avec and
    • prendre la disjonction de deux expressions avec or
  • et d’autres manières encore que nous verrons au besoin.

Ainsi dans le code ci-dessous, le corps de l’instruction conditionnelles est exécutée uniquement lorsque x est plus grand que 5 et que le bouton 2 n’est pas appuyé :

if x>5 and not btn(2):
  spr(1,x,y)
À vous de jouer

Modifier la fonction update de votre cartouche TIC-80 précédente pour que les déplacement ne se fassent que selon une diagonale nord-ouest / sud-est. Tester !

2.3 Boucles tant que

Lorsqu’on ne connaît pas le nombre d’itérations d’une boucle avant de la lancer, on utilise une boucle tant que (while) plutôt qu’une boucle for. Voici un exemple de code TIC-80 qui dessine un quart de disque :

# script: python
def TIC():
  cls(0)
  for y in range(100):
    x=0
    while x*x+y*y<=100*100:
      pix(x,y,12)
      x+=1

La boucle ligne 6 s’exécute tant que la condition est satisfaite, c’est-à-dire tant que la distance entre le point (0,0) et le point (x,y) est inférieure à 100.

3 Premiers jeux

Aujourd’hui, nous allons créer nos premières cartouches de jeu vidéo rétro. Pour des jeux simples et efficaces, on commence avec des jeux de réflexion dans lesquels les interactions se font en tour par tour : l’état du jeu évolue uniquement lorsque le joueur appuie sur une touche et les sprites sont affichés sur une grille.

3.1 Labyrinthe

Nous allons transformer le jeu de tuiles et la carte labyrinthe créés ci-dessus en un petit jeu simple : le personnage doit ramasser la clé, sans traverser les murs et rejoindre ensuite la porte pour gagner la partie.

À vous de jouer

Charger votre cartouche maze pour que les sprites et la carte soient prêts.

3.1.1 Premier programme

Voici un premier code source (très imparfait) :

# script: python
x,y=0,0

def draw():
  global x,y
  map()
  spr(1,8*x,8*y)

def update():
  global x,y
  dx,dy=0,0
  if btnp(0): dy=-1
  if btnp(1): dy=1
  if btnp(2): dx=-1
  if btnp(3): dx=1
  x,y=x+dx,y+dy

def TIC():
  update()
  draw()
À vous de jouer

Recopier et exécuter ce programme. Utiliser les flèches du clavier pour le tester.

Ce code a plusieurs soucis, nous allons y revenir. Observons tout d’abord les nouvelles fonctions d’API qu’il utilise.

La fonction d’API btnp(x) et son homologue btn(x) servent à interroger les boutons des manettes de jeu. Elles retournent une valeur booléenne et leur paramètre précise le bouton à interroger. Pour connaître les valeurs, il suffit d’utiliser la commande help buttons ! On voit que TIC-80 gère jusqu’à 4 manettes à 4 bouttons chacune.

la commande help buttons

Alors que btn(x) est vrai tant que le bouton est appuyé, l’appel btnp(x) retourne vrai une seule fois par appui sur la touche, ce qui convient mieux au type de jeu envisagé ici.

La fonction d’API map(x,y,w,h,sx,sy) dessine à l’écran, à partir du pixel (sx,sy) le contenu de la carte débutant à la cellule (x,y) et de dimensions (w,h). Sans paramètres, c’est le coin en haut à gauche de la carte qui vient remplir tout l’écran de jeu. Notons qu’il existe des utilisations plus avancées de cette fonction que nous ne décrivons pas ici.

Enfin la fonction d’API spr(t,x,y) dessine à l’écran le sprite numéro t en position (x,y). Il est aussi possible d’indiquer qu’une couleur doit être considérée comme transparente, de redimensionner le sprite, de le tourner, de changer ses dimensions, etc. Pour les utilisations avancées, se reporter à l’aide de TIC-80.

3.1.2 Lire et écrire la carte à la volée

Le premier souci à régler : nous avons deux personnages à l’écran et nous démarrons en (0,0).

L’API TIC-80 nous fourni deux fonctions très utiles pour accéder à la carte : mget(x,y) permet de connaître le numéro de la tuile en position (x,y) sur la carte et mset(x,y,t) permet de modifier la tuile en position (x,y) sur la carte pour la remplacer par la tuile t. Par défaut, ces modifications ne durent que pendant l’exécution de la cartouche (il est possible de les rendre permanentes à l’aide de la fonction sync).

À vous de jouer

Modifier votre code pour ajouter une fonction init() et ajouter un appel à cette fonction juste avant la définition de la fonction TIC().

def init():
  global x,y
  ## TODO : remplacer pass et ce commentaire par votre code
  pass
  
init()

def TIC():
  update()
  draw()

La fonction init() doit parcourir les cases de la carte d’abscisse inférieure à 30 et d’ordonnée inférieure à 17 à la recherche de la tuile 1. Une fois cette tuile trouvée, mettre à jour x et y et remplacer cette tuile par la tuile 0.

Tester la cartouche. Penser à sauvegarder.

3.1.3 Ne plus traverser les murs

Pour éviter de traverser les murs, nous allons utiliser les drapeaux : lorsque nous avons dessiné les sprites, les murs dans les tuiles 16 à 19 ont reçu un drapeau 0 positionnée à vrai.

Pour lire et écrire les drapeaux sur les tuiles, TIC-80 fournit les fonctions d’API fget(x,f) qui teste si le drapeau f est positionné sur le sprite x et fset(x,f,v) qui permet de modifier la valeur du drapeau f sur le sprite x pour le positionner à la valeur v.

À vous de jouer

Modifier la fonction update pour qu’elle modifie les coordonnées (x,y) uniquement lorsque la fonction can_move(x,y) renvoie vrai. Puis écrire une fonction can_move(x,y) qui teste à l’aide de fget et mget si la case (x,y) est une case autorisée. Pour retourner une valeur depuis une fonction, il faut utiliser le mot-clé return suivi de la valeur à retourner.

Tester la cartouche. Penser à sauvegarder.

3.1.4 Ramasser la clé et gagner la partie

Pour ramasser la clé, nous utiliserons aussi le drapeau 1. Il faut déclarer une variable globale has_key initialisée à False qui mémorise si la clé a été ramassée. Lorsque la clé est ramassée, il faut la retirer de la carte et mettre à jour cette variable.

Pour gagner la partie, le joueur doit tenter d’accéder à la case de sortie (drapeau 2) avec la clé en sa possession.

À vous de jouer

Modifier votre jeu pour gérer correctement la clé et la porte. Lorsque le joueur réussi à sortir, appeler la fonction reset() pour redémarrer la cartouche !

Modifier votre jeu pour afficher une clé dans le coin en haut à gauche de l’écran lorsque le joueur a ramassé la clé.

Tester la cartouche. Penser à sauvegarder.

3.1.5 Gérer des écrans de jeu

Il manque encore à notre jeu un élément essentiel : un écran de titre.

Comment gérer plusieurs états pour un jeu ? En utilisant des variables et en appelant des fonctions différentes en fonction de l’état du jeu.

À vous de jouer

Ajouter une variable globale in_game a votre jeu et renommer votre fonction TIC() actuelle en game(). Créer ensuite une nouvelle fonction TIC() qui teste la variable in_game et appelle game() lorsqu’elle est vraie. Lorsque la variable est fausse, afficher un écran de titre (avec cls et print) invitant le joueur a appuyer sur un bouton et modifier la variable lorsque btnp(4) s’évalue à vrai.

Tester la cartouche. Penser à sauvegarder.

3.2 Des portes et des clés

Félicitations pour votre premier jeu ! Il était cependant un peu court.

En utilisant plusieurs écrans de la carte, il est possible de prévoir une succession de niveaux qui s’enchaînent dans un même jeu. Il faut bien sûr prévoir une variable pour mémoriser le niveau courant et prévoir une fonction pour mettre en place le niveau.

Deux stratégies pour la gestion du niveau courant :

  • ou bien on recopie les tuiles lors de l’initialisation du niveau vers la zone en haut à gauche de la carte ;
  • ou bien on joue directement dans la portion de carte correspondant au niveau et on utilise sync pour réinitialiser le niveau.
À vous de jouer

À l’aide de la commande surf, tester la cartouche GameOn / TD2 / sheep. Lire son code et regarder sa carte (ne pas oublier d’utiliser la touche TAB pour observer plusieurs écrans).

Votre objectif est de créer un nouveau jeu avec son écran de titre et ses multiples niveaux. Le jeu comportera des clés de plusieurs couleurs (rouge, vert, bleu, jaune). La clé jaune permet toujours d’ouvrir la porte pour passer au niveau suivant. Les autres clés ouvrent chacune exactement une barrière de la même couleur.

À vous de jouer

En partant de vos propres graphismes ou de ceux du jeu sheep que vous pouvez modifier à votre guise, créer le jeu demandé.

Conseil

Avez-vous pensé à sauvegarder votre cartouche ? À la récupérer avec get ?

3.3 Glissades en temps chronométré

Dans l’esprit du jeu Fuga de Peniche, votre objectif est de créer un nouveau jeu dans lequel le personnage se déplace en ligne droite jusqu’au prochain obstacle. Le temps de jeu est chronométré (à l’aide de tstamp()) et les meilleurs scores pour terminer le jeu sont sauvegardés (à l’aide de la fonction d’API pmem).

Fuga de Peniche

Fuga de Peniche
À vous de jouer

En partant de vos propres graphismes ou de ceux du jeu sheep que vous pouvez modifier à votre guise, créer le jeu demandé.

Conseil

Avez-vous pensé à sauvegarder votre cartouche ? À la récupérer avec get ?

3.4 Pousser des caisses

Dans l’esprit du jeu Sokoban, votre objectif est de créer un nouveau jeu dans lequel le personnage peut pousser des caisses (y compris plusieurs à la fois !) devant lui. Le but du jeu est d’amener chaque caisse au-dessus d’une cible afin de valider le niveau.

Sokoban

Sokoban

Pour gérer les caisses mobiles, on pourra utiliser des listes Python ou bien utiliser deux types de caisses : avec et sans cible.

Pour en savoir davantage sur les différents jeux Sokoban, consulter le wiki Sokoban. On y trouve aussi des liens vers des jeux de niveaux intéressants.

À vous de jouer

En partant de vos propres graphismes ou de ceux du jeu sheep que vous pouvez modifier à votre guise, créer le jeu demandé.

Conseil

Avez-vous pensé à sauvegarder votre cartouche ? À la récupérer avec get ?

3.5 Avatars multiples

Besoin d’un autre défi ? Maintenant il n’y a plus un seul mais quatre personnages à l’écran ! L’appui sur les boutons de la manette permet de choisir le mouton actif. Les personnages se déplacent en ligne droite jusqu’à un obstacle (ce qui inclus les autres personnages). L’objectif reste de ramasser la clé et d’atteindre la porte. Mais attention, la clé est entre les mains d’un personnage particulier, c’est lui qui doit atteindre la porte !

Avec un peu de réflexion à la création de niveaux et un peu de travail, il y a de quoi créer un fantastique jeu de réflexion original !

À vous de jouer

En partant de vos propres graphismes ou de ceux du jeu sheep que vous pouvez modifier à votre guise, créer le jeu demandé.

Conseil

Avez-vous pensé à sauvegarder votre cartouche ? À la récupérer avec get ?

3.6 À vous de jouer !

La seule limite est votre imagination.

À vous de jouer

Créer le jeu de votre choix. Garder des mécanismes simples et compréhensibles. Prendre le temps de créer des niveaux intéressants. Tester les jeux de vos voisins !

Références

  1. Le site futurecoder pour débuter en Python en autonomie ;
  2. Le wiki officiel de TIC-80 ;
  3. Une sélection de jeux de réflexion TIC-80 pour nourrir votre inspiration :

Fuga de Peniche

The Traps

Bokdown

Sokoban

H+H=H2

Ultimate Tic-Tac-Toe
Figure 1: Sélection de cartouches TIC-80

Réutilisation