Tirer des projectiles

Bienvenue dans ce chapitre sur la réalisation d'un shoot'em up dans l'espace ! Vous pouvez commencer par le shooter ou par le jeu d'aventure dans l'ordre que vous voulez. Ceci dit, j'expliquerai certaines choses plus en détail ici, donc si vous ne vous sentez pas encore à l'aise, je vous conseille de commencer par ce chapitre.

Précédemment, nous avons vu comment déplacer un personnage, mais si vous venez d'arriver, je vous laisse recopier ce code tout simple qui nous servira de base.

Code de base

Un tir aura trois propriétés : une position X, une position Y, et une vitesse. Pour l'instant, on n'a pas encore d'ennemi. On va donc commencer par faire avancer les projectiles à chaque frame, puis les supprimer quand ils atteignent le bord de l'écran, afin de ne pas nous retrouver avec une quantité gigantesque de projectiles au bout de quelques minutes.

Mais d'abord, dessinons notre petit projectile !

Dessin du tir

Nous allons créer un tableau bullets qui contiendra tous nos projectiles. Déclarez le tableau dans _init() et laissez-le vide : on le remplira plus tard.

Déclaration du tableau bullets vide

La fonction de tir

Je vous propose de créer un nouvel onglet pour y voir plus clair, dans lequel on mettra tout ce qui concerne les balles. Si vous écrivez un commentaire à la première ligne de l'onglet, cela affichera un petit nom lorsque vous passerez la souris dessus.

Nouvel onglet

Créez une fonction shoot() que l'on appellera dès que le vaisseau doit tirer. Cette fonction va créer une nouvelle balle à l'endroit où se situe le joueur. Définissons la balle dans un tableau :

Tableau new bullet

Vous voyez que comme pour le vaisseau, on définit une vitesse que l'on utilisera plus tard. local signifie que cette variable est locale : lorsque nous quitterons la fonction, elle sera détruite. C'est une bonne pratique de créer une variable locale si vous n'en avez pas besoin en dehors du bloc où vous la créez.

Nous avions préparé un tableau bullets pour contenir toutes nos balles. Dans PICO-8, une commande permet d'ajouter un nouvel élément à la suite d'un tableau :

add(tableau, nouvel_element)

Dans notre cas, on ajoute le contenu de la variable locale new_bullet dans un nouvel emplacement de notre tableau bullets.

Commande Add

Si vous avez du mal à visualiser ce que cela veut dire, imaginez qu'en jeu, si l'on tire trois fois, le tableau bullets contiendra quelque chose comme :

bullets = {
    {x = 20, y = 30, speed = 4},
    {x = 26, y = 43, speed = 4},
    {x = 22, y = 61, speed = 4}
}

Toutes les balles existantes dans le jeu sont dans le tableau. C'est pratique car pour manipuler toutes les balles d'un coup, il suffira d'accéder à ce tableau.

On va appeler la fonction shoot() lorsque le joueur appuie sur X. Retournez à la fonction d'update, dans le premier onglet (naviguez avec Ctrl+Tab et Ctrl+Shift+Tab).

Appel de la fonction shoot Maj+X affiche le symbole X.

Tout simplement ! Vous savez maintenant écrire une fonction et l'appeler ailleurs, ce qui est très pratique pour découper son code d'une façon plus lisible.

Notez que l'on utilise btnp() au lieu de btn(). C'est le diminutif de "button pressed" : au lieu d'être tout le temps vraie tant que le bouton est appuyé, cette fonction renvoie true par intervalles quand le bouton est maintenu. On l'utilise généralement pour se déplacer dans un menu, mais dans notre cas, ça permet d'obtenir très simplement un tir qui fonctionne. Vous pouvez modifier la durée de l'intervalle en consultant l'explication de "btnp" dans le manuel.

Afficher les tirs

Si vous testez le jeu maintenant, il ne devrait rien se passer de spécial. C'est parce que nous devons afficher le sprite de tir dans la fonction _draw().

Afficher le sprite du vaisseau était tout simple, parce qu'il n'y en a qu'un. Mais cette fois, nous devons afficher autant de sprites qu'il y a de balles dans le tableau bullets ! Il peut très bien y en avoir deux, vingt, ou zéro... Nous devons donc automatiser ça.

Petite leçon sur les boucles for

Une boucle for permet de répéter une instruction un nombre de fois donné.

for i=1,10 do -- Pour i allant de 1 à 10, répéter :
    spr(1, i*10, 60)
end

i est une variable locale qui vaudra 1, 2... jusqu'à 10 à l'intérieur de la boucle. Donc ce code affichera 10 fois le sprite 1, d'abord à la position 10 en X, puis 20... jusqu'à 100. Vous pouvez nommer i comme vous voulez : c'est votre compteur.

Une boucle for peut également parcourir un tableau. Cela veut dire qu'elle se jouera une fois par élément du tableau. Chaque élément de bullets étant une balle, la boucle va se jouer une fois par balle !

for b in all(bullets) do -- Pour b parcourant le tableau bullets, répéter :
    spr(2, b.x, b.y)
end

Etant donné que votre tableau bullets ressemble à ceci en cours de jeu, avec plus ou moins de balles :

bullets = {
    {x = 20, y = 30, speed = 4},
    {x = 26, y = 43, speed = 4},
    {x = 22, y = 61, speed = 4}
}

La variable b qui parcourt le tableau vaut quelque chose comme ceci à chaque passage dans la boucle :

b = {x = 20, y = 30, speed = 4}

C'est pour cela qu'on peut manipuler chaque balle en écrivant b.x, b.y... Puis lorsque la boucle a été répétée avec chaque élément de bullets, c'est fini et on sort de la boucle.

N'oubliez pas que b est simplement le nom de la variable locale qui parcourt le tableau. Elle peut être nommée comme vous le souhaitez.

Testez le jeu, et... les balles s'affichent, mais restent immobiles à l'endroit où elles ont été tirées. Nous avons fait le plus gros du travail ! Il faut maintenant les faire avancer.

Déplacer les projectiles

Comme déplacer les balles n'est pas une question d'affichage, mais plutôt de gestion du jeu, ça a sa place dans update. Encore une fois, pour rester bien organisés, nous allons créer une fonction update_bullets() dans l'onglet des bullets.

Création de la fonction Update bullets

En français, la fonction doit faire ceci : « Pour chaque élément dans bullets, le déplacer vers le haut. » On utilise donc une boucle for !

Boucle for pour déplacer les balles

Notez qu'on diminue Y pour aller vers le haut de l'écran. Dans mon cas, ma vitesse est de 4, donc c'est comme si j'avais écrit b.y -= 4. N'oublions pas de supprimer les balles qui sortent de l'écran, sinon la console va ramer au bout de quelques minutes. Comme le haut de l'écran est y = 0 et qu'un sprite fait 8 pixels de haut, on ne voit plus la balle si sa position Y est inférieure à -8.

Condition pour détruire les balles

del() fonctionne comme add(), mais pour enlever un élément d'un tableau.

Nous allons exécuter cette fonction à chaque frame, dans update.

Appel de la fonction Update bullets

Testez le jeu. Tadaam ! Pas mal, non ?

L'effet sonore

Un tir de vaisseau spatial sans son "piou piou", ce n'est pas un vrai tir ! Dans PICO-8, il est très facile de créer des sons et des musiques, même si vous n'y connaissez rien. Cliquez sur l'avant-dernière icône pour ouvrir l'éditeur de son.

Editeur de son de PICO-8

Dessinez dans le cadre noir et appuyez sur Espace pour jouer le son. Vous allez voir, c'est magique !

N'hésitez pas à essayer les 8 instruments différents. Pour changer l'instrument des notes déjà dessinées, faites Shift+Clic sur l'instrument.

En haut à gauche, c'est le numéro du son que vous éditez. A côté, vous pouvez changer la vitesse du son avec le clic gauche et le clic droit. Pour un effet sonore, la vitesse doit être très rapide, tandis que pour une mélodie, elle doit être plus lente.

En bas de l'écran, vous pouvez modifier le volume. Baisser le volume à zéro supprime la note. Pour un bon son de tir, vous pouvez essayer d'avoir un volume assez fort au début, puis qui diminue au fil du temps.

Son de tir finalisé

Dans la fonction shoot(), jouez le son numéro 0 avec sfx(0).

Commande SFX

Essayez-le jeu et écoutez : ça change tout !