Partie 1 - Dessiner le caractère ‘@’ et le déplacer


Bienvenue dans la partie 1 du Tutoriel Roguelike revesité ! Cette série va vous aider à créer votre tout premier jeu roguelike, écrit en Python.

Ce tutoriel est largement inspiré de celui trouvé sur Roguebasin. Une grande partie des décisions de conception furent prises afin de conserver une progression commune avec celui-ci (du moins en ce concerne la composition des chapitres et la direction générale). Ce tutoriel n’aurait pu être possible sans le travail des concepteurs d’origine ainsi que celui des merveilleux contributeurs de libtcod et python-tcod.

Nous supposons dans cette partie que vous avez déjà effectué la partie 0 et être prêt à commencer. Sinon, rendez-vous sur cette page est vérifiez votre installation de Python et de TCOD. Assurez-vous d’avoir un fichier engine.py dans le dossier choisi pour votre travail.

Supposant que vous avez accompli tout ça, commençons. Modifiez (ou créez si vous ne l’avez pas déjà fait) le fichier engine.py pour qu’il ressemble à ça :

import tcod as libtcod


def main():
    print('Hello World!')


if __name__ == '__main__':
    main()

Vous pouvez exécuter le programme comme n’importe quel programme Python mais, pour les novices, vous le faîtes en tapant python engine.py dans le terminal. Si vous avez à la fois Python 2 et 3 d’installé sur votre machine, il se peut que vous deviez taper python3 engine.py pour le lancer (cela dépend de votre python par défaut et vous utilisez un environnement virtuel ou non).

D’accord, ce n’est pas le programme le plus enthousiasmant qui soit, je le reconnais, mais nous avons déjà une différence majeure avec l’autre tutoriel. C’est cette chose étrange ici :

if __name__ == '__main__':
    main()

Qu’est-ce que ça fait ? Simplement, nous disons que nous n’allons lancer la fonction principale “main” que si nous lançons explicitement le script avec la commande python engine.py. Ce n’est pas fondamental que vous compreniez ça maintenant mais si vous tenez à comprendre, cette réponse sur Stackoverflow donne un bon aperçu.

Assurez-vous que le programme précédent tourne (sinon, c’est probablement un soucis d’installation de libtcod). Une fois que cela est accompli, nous pouvons allez vers des étapes un peu plus intéressantes. La première étape importante dans la conception d’un roguelike est d’obtenir un caractère ‘@’ à l’écran et de le déplacer aussi allons-y.

Modifiez engine.py pour qu’il ressemble à ça :

import tcod as libtcod


def main():
    screen_width = 80
    screen_height = 50

    libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)

    libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)

    while not libtcod.console_is_window_closed():
        libtcod.console_set_default_foreground(0, libtcod.white)
        libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
        libtcod.console_flush()

        key = libtcod.console_check_for_keypress()

        if key.vk == libtcod.KEY_ESCAPE:
            return True


if __name__ == '__main__':
    main()

Exécutez engine.py à nouveau et vous devriez vous un ‘@’ à l’écran. Une fois que vous êtes complètement repu de la gloire provoquée par l’écran devant vous, vous pouvez presser la touche `Esc` pour quitter le programme.

Il se passe beaucoup de choses ici, aussi découpons ligne par ligne.

    screen_width = 80
    screen_height = 50

C’est assez simple. On définit quelques variables pour la dimension de l’écran. Éventuellement, nous pourrions charger ces valeurs depuis un fichier JSON plutôt que de les coder en dur dans les sources, mais nous ne en soucierons pas avant d’avoir plus de variables.

    libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)

Ici, nous disons à libtcod quelle police employer. La partie 'arial10x10.png' est le fichier qu’on lit (il devrait exister dans votre dossier de projet). Les deux autres parties disent à libtcod quel type de fichier nous lisons.

    libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'libtcod tutorial revised', False)

Cette ligne est ce qui crée l’écran. Nous lui donnons les valeurs screen_width et screen_height définies plus haut (80 et 50, respectivement), ainsi qu’un titre (changez le si vous connaissez déjà le nom de votre jeu) et une valeur booléenne qui indique à libtcod si le jeu est plein écran ou non.

    while not libtcod.console_is_window_closed():

C’est ce qu’on appelle la ‘boucle de jeu’. C’est simplement une boucle qui ne va jamais s’interrompre tant qu’on n’aura pas fermé la fenêtre. Chaque jeu a une boucle de jeu d’une manière ou d’une autre.

        libtcod.console_set_default_foreground(0, libtcod.white)

Cette ligne indique à libtcod de régler la couleur de notre symbole ‘@’. Si vous souhaitez qu’il soit d’une autre couleur, remplacez libtcod.white par quelque chose comme libtcod.red et voyez ce que cela donne. Le ‘0’ dans cette fonction est la console sur laquelle nous écrivons. Nous en reparlerons plus tard.

        libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)

Le premier argument est ‘0’ (à nouveau, la console sur laquelle nous écrivons). Les deux suivants sont des coordonnées x et y, dans ce cas 1 et 1 (essayez de les remplacer pour voir ce que cela donne). Ensuite nous affichons le symbole ‘@’ et réglons le fond sur ‘none’ avec libtcod.BKGND_NONE.

        libtcod.console_flush()

C’est la partie qui affiche tout à l’écran. Plutôt directe.

        key = libtcod.console_check_for_keypress()

        if key.vk == libtcod.KEY_ESCAPE:
            return True

Cette partie nous permet de quitter gracieusement le jeu (sans planter) en pressant la touche Esc. La fonction libtcod.console_check_for_keypress() récupère une saisie clavier du programme que nous stockons dans la variable key. Ensuite, nous vérifions si la touche pressée est Esc ou non. Si c’est le cas, nous quittons la boucle, ce qui termine le programme.

Maintenant que nous avons notre symbole @ dessiné, déplaçons le !

Nous devons connaître la position du joueur, donc nous allons créer deux variables, player_x et player_y.

    ...
    screen_height = 50
+
+   player_x = int(screen_width / 2)
+   player_y = int(screen_height / 2)
+
    libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
    ...
    ...
    screen_height = 50
    
    player_x = int(screen_width / 2)
    player_y = int(screen_height / 2)
    
    libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
    ...

Remarque: Les trois points désignent une partie omise du code. J’inclurai des lignes autour du code à insérer afin que vous sachiez exactement où écrire les nouvelles parties de code mais je ne montrerai pas le fichier entier à chaque fois. Les lignes vertes indiquent le code que vous devriez ajouter.

Nous plaçons le joueur en plein milieu de l’écran. Que fait la fonction int() ? Et bien, Python 3 ne tronque pas automatiquement la division comme Python 2 aussi nous devons convertir le résultat (un flottant) en entier. Sinon libtcod va renvoyer une erreur.

Nous devons aussi modifier la commande affichant le symbole ‘@’ afin d’employer ces nouvelles coordonnées.

        ...
        libtcod.console_set_default_foreground(0, libtcod.white)
-       libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
+       libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE)
        libtcod.console_flush()
        ...
        ...
        libtcod.console_set_default_foreground(0, libtcod.white)
        libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
        libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE)
        libtcod.console_flush()
        ...

Remarque : les lignes en rouge signalent le code qui a été enlevé.

Exécutez le programme maintenant et vous devriez voir le ‘@’ au centre de l’écran. Occupons nous maintenant de le rendre mobile.

Ajoutez les deux lignes suivantes juste au dessus de la boucle principale.

    ...
    libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)

+   key = libtcod.Key()
+   mouse = libtcod.Mouse()

    while not libtcod.console_is_window_closed():
    ...
    ...
    libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)

    key = libtcod.Key()
    mouse = libtcod.Mouse()

    while not libtcod.console_is_window_closed():
    ...

Ainsi que les noms le suggèrent, ces variables vont contenir nos saisies clavier et souris. Nous n’implémenterons pas la souris immédiatement mais la fonction que nous allons ajouter en tient compte aussi nous pouvons l’ajouter.

    ...
    while not libtcod.console_is_window_closed():
+       libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)

        libtcod.console_set_default_foreground(0, libtcod.white)
    ...
    ...
    while not libtcod.console_is_window_closed():
        libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)

        libtcod.console_set_default_foreground(0, libtcod.white)
    ...

C’est la fonction qui capture les nouveaux “événements” (saisie clavier). Elle va mettre à jour les variables key et mouse avec ce que saisit l’utilisateur. À nouveau seule key nous intéresse pour l’instant.

D’accord, nous mettons key à jour avec la saisie de l’utilisateur. Mais qu’en faisons nous ? Cela va traduire les saisies en action de jeu.

Pour l’instant, ce tutoriel n’a pas vraiment divergé de celui d’origine mais voici un changement radical. Nous sommes sur le point de définir une fonction appelée handle_keys qui va gérer les saisies clavier. Nous pourrions l’ajouter à notre fichier engine.py… mais est-ce la place de cette fonction ? Je pense que non. Le moteur (la boucle principale) capture les saisies et devrait en faire quelque chose mais traduire de l’un vers l’autre ne le concerne pas.

Aussi, plutôt que d’ajouter la fonction handle_keys à engine.py, créez un nouveau fichier, appelé input_handlers.py. Placez le code suivant dans ce nouveau fichier.

import tcod as libtcod


def handle_keys(key):
    # Movement keys
    if key.vk == libtcod.KEY_UP:
        return {'move': (0, -1)}
    elif key.vk == libtcod.KEY_DOWN:
        return {'move': (0, 1)}
    elif key.vk == libtcod.KEY_LEFT:
        return {'move': (-1, 0)}
    elif key.vk == libtcod.KEY_RIGHT:
        return {'move': (1, 0)}

    if key.vk == libtcod.KEY_ENTER and key.lalt:
        # Alt+Enter: toggle full screen
        return {'fullscreen': True}

    elif key.vk == libtcod.KEY_ESCAPE:
        # Exit the game
        return {'exit': True}

    # No key was pressed
    return {}

C’est un gros morceau à comprendre en une seule fois aussi, à nouveau, découpons le en différentes parties.

def handle_keys(key):

On crée une fonction appelée handle_keys qui prend un paramètre, key. key désigne cette fois la touche capturée plus tôt.

    if key.vk == libtcod.KEY_UP:

Cette expression if (ainsi que les autres elifs) nous dit quelle touche a été pressée. Pour l’instant, c’est l’une des flèches pour le mouvement. Ce qui est plus intéressant est le code dans cette expression if.

    return {'move': (0, -1)}

Que faisons nous ici ? Quand nous retournons de cette fonction, le moteur va devoir faire quelque chose. Dans ce cas, nous voulons déplacer notre personnage. Mais si nous voulons utiliser une autre touche ? Alors nous ne voulons peut-être pas bouger mais utiliser un item, lancer un sort ou quitter le jeu. Une manière de traiter toutes ces possibilités est de renvoyer un dictionnaire depuis cette fonction. Le moteur le lira et décidera ce qu’il en fera.

Pour ce faire, nous retournons un dictionnaire avec la clé 'move' et pour valeur une paire de nombres. Ces nombres dirons au moteur dans quelle direction déplacer le joueur. Par exemple, la touche ‘haut’ va déplacer le joueur de ‘0’ selon les x et ‘-1’ selon les y.

    if key.vk == libtcod.KEY_ENTER and key.lalt:
        # Alt+Enter: toggle full screen
        return {'fullscreen': True}
    elif key.vk == libtcod.KEY_ESCAPE:
        # Exit the game
        return {'exit': True}

Voici les actions sans mouvement que nous acceptons pour l’instant. Si l’utilisateur presse ‘ALT+enter’, le jeu va passer en mode plein écran. Si l’utilisateur presse ‘Esc’, le jeu va quitter.

    return {}

Parce que notre moteur attend un dictionnaire, nous devons renvoyer quelque chose, même si rien ne s’est produit.

Cela peut sembler surprenant mais cela prendra sens dans quelques insants. Retournons à ntore fichier engine.py pour appeler la fonction handle_keys.

        ...
        libtcod.console_flush()

-       key = libtcod.console_check_for_keypress()
+       action = handle_keys(key)
+
+       move = action.get('move')
+       exit = action.get('exit')
+       fullscreen = action.get('fullscreen')

+       if move:
+           dx, dy = move
+           player_x += dx
+           player_y += dy

-       if key.vk == libtcod.KEY_ESCAPE:
+       if exit:
+           return True
+
+       if fullscreen:
+           libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
        ...
        ...
        libtcod.console_flush()

        key = libtcod.console_check_for_keypress()
        action = handle_keys(key)

        move = action.get('move')
        exit = action.get('exit')
        fullscreen = action.get('fullscreen')

        if move:
            dx, dy = move
            player_x += dx
            player_y += dy

        if key.vk == libtcod.KEY_ESCAPE:
        if exit:
            return True

        if fullscreen:
            libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
        ...

Remarque : Je ferai apparaître en rouge les lignes effacées. Dans ce cas, retirez les lignes : key = libtcod.console_check_for_keypress() et if key.vk == libtcod.KEY_ESCAPE

Aussi, assurez-vous d’importer les fonctions handle_keys en haut de engine.py

import tcod as libtcod

+from input_handlers import handle_keys
import tcod as libtcod

from input_handlers import handle_keys

Esperons maintenant que le dictionnaire de handle_keys prenne un peu plus de sens. On capture la valeur de retour de handle_keys dans la variable action (qui devrait être un dictionnaire, quoi qu’on ait pressé) et on vérifie quelle touche quelle touche elle contient. Si elle contient une clé appelée move alors on sait qu’il faut chercher les coordonnées (x, y). Si elle contient ‘exit’, alors on sait qu’il faut quitter le jeu.

Essayez d’exécuter le fichier engine.py maintenant. Vous devriez pouvoir vous déplacer. Amusant !

Une dernière étape avant d’avancer. Regardez la fonction de dessin. Remarquez le premier argument qui est ‘0’. Cela représente la console dans laquelle on écrit. 0 est celle par défaut. Plutôt que d’écrire sur celle par défaut nous voulons préciser sur quelle console écrire après en avoir crée une nouvelle. La raison derrière est qu’il sera plus facile d’en créer pour dessiner dessus plus tard. Cela sera particulièrement utile quand nous aborderons la partie relative à l’interface graphique de cette série.

Modifiez le fichier engine.py ainsi :

    ...
    libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)

+   con = libtcod.console_new(screen_width, screen_height)

    key = libtcod.Key()
    mouse = libtcod.Mouse()

    while not libtcod.console_is_window_closed():
        libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)
+
+       libtcod.console_set_default_foreground(con, libtcod.white)
+       libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE)
+       libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)
-       libtcod.console_set_default_foreground(0, libtcod.white)
-       libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE)
        libtcod.console_flush()
+
+       libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE)
-       libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE)
    ...
    libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)

    con = libtcod.console_new(screen_width, screen_height)

    key = libtcod.Key()
    mouse = libtcod.Mouse()

    while not libtcod.console_is_window_closed():
        libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)
        
        libtcod.console_set_default_foreground(con, libtcod.white)
        libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE)
        libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)
        libtcod.console_set_default_foreground(0, libtcod.white)
        libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE)
        libtcod.console_flush()
        
        libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE)
        libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE)

Cela conclut la première partie de ce tutoriel ! Si vous utilisez git ou une autre forme de contrôle de version (et je vous le recommande), vous devriez faire un commit de vos changements maintenant.

Si vous voulez voir le code actuel entièrement, cliquez ici. Les fichiers que vous souhaitez consulter sont engine.py et input_handlers.py.

Cliquez ici pour vous rendre à la partie suivante de ce tutoriel.