Avant propos
À quoi sert ce tutoriel ?
À faire un jeu de shoot ou d'action RPG où l'on tire sur les méchants pour les tuer avec notre pistolet sur RPG Maker 2003 !
Un exemple d'application dans Le jeu qui est trop bien génial,
sûrement une future référence en la matière
À qui s'adresse ce tutoriel ?
Plutôt à des gens d'un niveau intermédiaire, qui ont déjà essayé de le faire, mais qui n'ont pas réussi. Pour les plus avancés, une lecture évasive permet aussi de saisir le principe sans entrer dans les détails.
Je connais rien du tout !
Voilà quelques liens utiles :
Introduction
Le jeu de tir ou de shoot en A-RPG est le rêve du maker débutant : qui n'a jamais été impressionné et un peu jaloux devant le
SURVIVE de Kilam1110 ? Des tutos pour ce faire existent déjà, comme
celui de coco,
celui de Molokov,
ou de Naked Snake.
Ces tutoriels reposent sur la comparaison de coordonnées. Dans le cas de celui de coco ou de Naked Snake, on vérifie si deux événements sont superposées l'un à l'autre (leur coordonnées sont égales). Celui de molokov vérifie seulement si le héros qui tire et un ennemi sont sur la même ligne, puis quel ennemi est le plus proche du héros.
Ces méthodes sont efficaces, mais présentent un désavantage, qui est la lourdeur de leur mise en place : il est requis de faire des dizaines et des dizaines de comparaisons de coordonnées, pour chaque ennemi, etc. Or, on sait tous que les makers sont des fainéant (moi le premier, je suis devenu allergique aux chaînes de conditions après
AA City), donc la légèreté et la versatilité d'un système sont des points très importants.
Le tutoriel que je présente ne se veut pas comme l'ultime et unique gameplay de jeu de tir imaginable, mais plus comme une approche du problème grâce à une méthode différente, un tronc sur lequel facilement grimper sur les branches. J'explique beaucoup ce que je fais, mais rien n'est sorcier :
in facto, c'est très simple, et avec l'idée en tête vous serez capable de réaliser en 15 minutes un système de tir tout à fait fonctionnel.
Principe
On a une balle imaginaire. Elle n'existe que par deux variables correspondant à ses coordonnées X et Y. On la fait avancer en boucle selon la direction dans laquelle le héros tire lorsqu'on appuie sur la touche action.
Pour ne pas que la balle ne traverse les murs, on vérifie l'ID du terrain correspondant à ses coordonnées. On attribuera un ID spécifique dans la base de données aux tiles non traversables. Si la balle est sur un terrain non traversable, on l'interrompt.
Pour vérifier si la balle rencontre un ennemi, on testera l'ID de l'événement qui est à la position correspondant à ses coordonnées. Si la balle est sur un événement, on l'interrompt. Pour éviter de tirer sur les portes et les coffres, on attribuera une bande d'ID aux ennemis (ex : tous les événements à l'ID supérieur à 50 sont des ennemis).
Pour infliger les dommages aux ennemis, on incrémente à l'aide des pointeurs une variable spécifique à chaque ID d'ennemi, par exemple la variable V[100+ID]. Ainsi, la variable V[158] sera la vie de l'ennemi à l'ID n°58. On pensera à mettre une page spéciale sur l'événement à l'ID n°58 lorsqu'il est mort. C'est là l'originalité.
C'est tout !
Table des matières :
- Programmer l'événement-maître : l'appui de touche, le déplacement de la balle imaginaire, le test d'ID terrain, le test d'ID d'événement
- Programmer l'ennemi
- Améliorations secondaires (enrichir le gameplay) : système de straff, rajouter des munitions, ranger/sortir l'arme
- Améliorations tierces (si vous voulez en faire un vrai jeu) : rajouter des armes, faire respawn les ennemis, faire une intelligence artificielle plus développée, rajouter une barre de vie et de munition
- Améliorations quartes (si vous voulez faire le ouf) : Rajouter des animations/des interactions avec l'ennemi touché
- Le défi des 15 minutes
Programmer l'événement-maître
L'appui de touche
Créez un événement en processus parallèle. Demandez l'appui d'une touche (ici on fera avec la touche de validation, entrée ou espace) en décochant attendre l'appuie d'une touche, puis vérifier si la variable vaut 5 (= touche entrée). Éventuellement, rajoutez un petit effet sonore, puis un "attendre" au bout, histoire qu'on ne puisse pas tirer trop vite (ça fera office de cooldown de votre arme).
Pourquoi ne pas cocher (attendre qu'une touche soit appuyée) ?
C'est parce que sinon, on ne pourra pas programmer des mitraillettes ou des armes automatiques, qui tirent en boucle lorsque l'on maintient la touche appuyée.
Oui, mais, si je veux une arme non automatique (où l'on ne tire qu'un coup à la fois) ?
On va simuler une attente d'appui de touche alors. Tout en bas, rajoutez un autre test de touche pour la touche entrée, mais vérifiez si cette variable vaut 0 (= pas de touche appuyée), dans ce cas vous activerez l'interrupteur "touche relâchée". On testera ensuite l'état de cet interrupteur pour les armes que vous choisirez comme non automatiques. Je place une condition sur ce test de touche, pour qu'il ne soit effectuée que lorsque l'interrupteur touche relâchée est désactivé, afin d'éviter de faire des tests pour rien.
Mon héros tire lorsque je suis dans un dialogue !
Mettez comme condition d'activation de cet événement un interrupteur, et pensez à le désactiver lorsque vous parlez à quelqu'un
Le déplacement de la balle imaginaire
On a fait les formalités. Passons à la programmation de la balle imaginaire. On rend égale ses coordonnées initiale à celles du héros, on stocke aussi la direction du héros dans une variable. Dans une boucle, on incrémente les coordonnées de la balle selon sa direction.
Astuce : Si vous peinez à savoir quelle valeur à la variable direction lorsque le héros regarde vers la gauche ou la droite, pensez à votre pavé numérique.
Les valeurs concordent avec les flèches.
7
8 9 -----> 8 = haut
4 5
6 -----> 4 = gauche ; 6 = droite
1
2 3 -----> 2 = bas
Attention ! Ne lancez pas le jeu maintenant !
La boucle tournera à l'infini, et pourra faire exploser votre ordinateur. Peut être.
Le test d'ID terrain
Notre balle imaginaire se déplace, mais
se fiche complètement des murs ou des zones infranchissables. Pour contrer cela, dans l'éditeur de chipset de la base de données, on attribue aux tiles praticables un ID (un "type de terrain") particulier, qu'on peut créer dans l'éditeur des types de terrains de la base de données aussi.
Ici, j'ai attribué l'ID 22 aux environnements que je juge impraticables pour les balles (les murs) et 21 à ceux praticables (les sols). Le numéro est arbitraire, vous pouvez le choisir comme cela vous arrange.
Ensuite, on effectue le test à proprement dit à l'aide de la commande "Stocker l'ID d'un terrain" (page 2, en bas à gauche), pour les coordonnées correspondant à celles de notre balle imaginaire (balle X et balle Y). Si l'ID est différent de 21 (l'ID de notre tile praticable), on sort de la boucle.
J'ai des tiles impraticables sur la couche haute et ma balle les traverse quand même !
Il est nécessaire que vous placiez un tile sur la couche basse avec le bon ID. Si votre tile en dessous est du sol, éditez votre chipset pour y rajouter un second tile identique graphiquement, mais avec un ID différent dans l'éditeur. On pourrait penser que cette solution est casse-pieds, mais au contraire, elle vous donne une meilleure maîtrise : des tiles impraticables pour le héros peuvent l'être pour vos balles (ex : des tables), et vice versa (ex : des échelles) !
Comment gérer mon autotile correspondant au toit dans un intérieur (avec le symbole "carré" dessus), qui est à la fois praticable et impraticable ?
Je vous conseille de rajouter sur les côtés des tiles identiques graphiquement à cet autotile avec le bon ID.
Spoiler: |
|
Sur le screen j'ai indiqué en marron les endroits où, dans l'éditeur, j'ai mis ces tiles identiques à l'ID différent. In-game, le résultat est parfaitement invisible. Dans l'éditeur, vous aurez peut être besoin d'un place holder pour bien voir si vous n'avez rien oublié. Pensez à utiliser la touche Shift en plaçant le tile pour ne pas briser l'autotile. |
Le test d'ID d'événement
On veut que lorsque notre balle imaginaire se retrouve aux mêmes coordonnées qu'un ennemi, on soustrait 1 à la variable vie de notre ennemi. On va pour cela utiliser la commande "stocker l'ID d'un événement" (page 2, en bas à gauche) avec les coordonnées de la balle. Si l'ID est différent de 0, cela signifie qu'il y a un événement sur la case où se situe notre balle imaginaire.
J'ai dit dans la partie "Principe" qu'on attribuait une bande d'ID d'événements aux ennemis : on va donc seulement vérifier si l'ID de l'événement est compris dans l'intervalle des valeurs correspondantes. On fait ça pour éviter de pouvoir tirer (et donc tuer) sur des PNJ, des coffres, des portes, etc. Ici, j'ai choisi les ID entre 50 et 100, mais c'est à vous de le choisir et de l'adapter en fonction de vos besoins. Si vous avez un très gros projet, vous devez avoir moyen de définir ces valeurs dynamiquement en fonction des maps.
Comme énoncé dans "Principe", on attribue à chaque ID d'événement ennemi une variable correspondante, dont l'ID est du style V[100+ID événement], en gros V[constante+ID de l'événement ennemi]. Pour cela, on s'aide des pointeurs.
Qu'est ce qu'un pointeur ?
Le pointeur va prendre l'ID de la variable qu'on veut modifier. Pour modifier une variable, à la place d'indiquer son ID manuellement, on indique le pointeur. RM 2k3 fait ensuite le boulot à notre place : si le pointeur vaut 150, et qu'on rend égal la variable pointée à 4, la variable à l'ID 150 vaudra désormais 4. C'est pratique car ça évite de faire plein de conditions, et
je déteste faire plein de conditions.
J'ai rien compris du tout !
Pas de panique ! Pour plus de détails sur les pointeurs,
ce tutoriel de Joke est top.
Voilà. Les deux tiers du boulot sont faits !
Programmer l'ennemi
Notre ennemi est un nouvel événement dont l'ID est compris dans l'intervalle défini précédemment, qui a deux pages. La première est une page où l'ennemi est en mode "aggressif", c'est à dire qu'il marche vers le héros (mais pas trop vite, c'est relou les zombies qui sprintent), il s'active lorsqu'il est en contact événement/héros. À l'intérieur, il y a ce que vous voulez qui arrive quand votre héros est touché. Je n'ai pas particulièrement développé cet endroit, c'est libre, c'est à vous de choisir ce qui va se passer. J'ai indiqué plusieurs idées sur l'image, mais cela ne dépend que de vous.
L'astuce d'Alex RE
Pour éviter d'avoir à modifier tout les ennemis du jeu si vous voulez faire un tout petit ajustement sur l'attaque de vos monstres, mettez ce code dans un événement commun "attaque ennemie" (condition d'activation appel). Dans la première page de l'événement de votre monstre, vous ne mettrez donc qu'une seule commande, l'appel de l'événement commun "attaque ennemi".
La deuxième page correspond au moment où l'ennemi est mort, elle est en processus parallèle. Elle a pour condition d'activation la variable V[constante + id de l'événement] qui est égale à l'opposé de ses PV. Dans le cas ici présent, on avait choisi 100 comme constante pour nos pointeurs, et j'ai pris 5 comme nombre de PV. Je teste donc si la variable V[100+52] (cet événement a pour ID 52), donc V[152], est égale à -5. Dedans, on se contente d'effacer l'événement. Vous pouvez rajouter une petite animation.
Je peux tuer mon ennemi, mais j'entends encore l'effet sonore d'un monstre qui agonise !
Il faut tester la variable vie correspondant à l'événement en dessous de la balle imaginaire. Cela se fait dans la partie "test ID d'event". Si elle est supérieure à l'opposé de sa vie (donc si il est mort), on ne lui retire pas de PV. Ici, on imbriquerait tout le bloc de modifications de variables dans une autre condition "Si V[100+ID] supérieure à -5".
Attention ! RM 2k3 ne peut pas utiliser de pointeur dans les conditions. Il faudra donc utiliser des variables tierces pour stocker la valeur de la vie de l'événement.
Vous pouvez essayer allouer dynamiquement la valeur de la vie en passant par encore une autre variable (par exemple V[200+ID]) qui contiendrait la vie maximale de votre ennemi. Vous testeriez alors "Si V[100+ID] supérieure à V[200+ID]".
Voilà, vous pouvez dors et déjà tester votre projet, ça marche bien, et en 15 minutes vous pouvez faire rapidement tout ce processus ! On peut intégrer autant d'ennemi que l'on veut au jeu, avec une modification minime sur chacun d'eux. Mais n'oubliez pas de modifier la variable sur la deuxième page !
Améliorations secondaires : enrichir le gameplay
Je n'inclue pas dans ces améliorations les idées graphiques etc. C'est uniquement sur le point de vue de la programmation.
Système de straff
Le straff, c'est le système de fixation de direction ; à mon avis c'est essentiel. Lorsque l'on maintient une touche, notre héros va lui aussi maintenir sa direction. Ça peut paraître anodin, mais c'est grave pratique et beaucoup plus agréable à jouer. Le nom et l'idée
me viennent de molokov.
Une des solutions imaginables est de créer un événement, qui est un autre processus parallèle, et qui teste l'appui d'une touche sans attendre que l'on appuie dessus. Il vérifie si la touche appuyée correspond, et dans ce cas il fixe la direction du héros. Sinon, il arrête la fixation du héros.
Spoiler: |
|
NB : Pensez à utiliser une variable et une touche différente de celle vue dans la programmation de l'événement maître ! |
Mais attention ! Avec cet événement simpliste, aucun déplacement appliqué au héros ne peut marcher (impossible d'utiliser la commande "déplacer un événement" sur le héros). C'est parce qu'à toute les frames, le "déplacer/modifier" de cet événement sera prioritaire sur toute autre déplacer/modifier événement. Pour contrer à cela, je préconise un autre événement, un peu plus complexe. À l'aide d'un interrupteur, on ne va fixer la direction du héros qu'une seule fois lors du premier appui de la touche maj, et la défixer qu'une seule fois aussi, lors du premier non appui de touche. C'est une méthode similaire à celle que l'on a utilisé dans l'appui de touche de l'événement-maître.
Rajouter des munitions
Bon, ça paraît peu probable à prime abord quand on regarde des films hollywoodiens, mais c'est vrai qu'on peut rarement tirer à l'infini, cela nécessite des munitions. Ce n'est rien de sorcier, il suffit d'englober tout ce qu'on avait déjà fait dans l'événement maître pour vérifier si une variable munition est supérieure à 0, et de retirer 1 à cette variable si oui. Vous n'oublierez pas de faire en sorte que l'on puisse récupérer des balles à certains endroits. J'en profite pour mettre tout l'événement-maître, si vous avez raté quelque chose.
Spoiler: |
|
|
Ranger/sortir l'arme
C'est décoratif, mais moi j'aime bien. Définissez un nouvel interrupteur "arme sortie". Dans l'événement maître, imbriquez tout ce qui est actuellement dans la condition "Si touche appuyée = 5" (//On a appuyé sur la touche Entrée) dans une autre condition "si l'interrupteur arme sortie = ON". Indépendamment, testez si l'on appuie sur la touche MAJ, ou Esc, ou tout autre touche que vous voulez définir comme celle sortant l'arme. Dans ce cas, activez l'interrupteur "arme sortie". Pensez à faire de même pour une touche qui servira à ranger l'arme et désactiver l'interrupteur "arme sortie".
À ce stade, vous pouvez déjà refaire SURVIVE v1.0, il vous suffit de copier/coller 100 ennemis sur une map
Attention ! N'oubliez pas de changer à chaque fois la variable vie du deuxième onglet, pour qu'elle corresponde bien à celle de l'ennemi ! (V[Constante + ID])
Améliorations tierces : si vous voulez en faire un vrai jeu
Bon, là on a largement dépassé les 15 minutes. Si vous voulez creuser un peu plus profondément, voilà de la matière.
Rajouter des armes
En définissant une variable comme "arme équipée" et en testant sa valeur, vous êtes capables d'altérer le comportement qu'elle aura dans l'événement maître. Je ne vais pas m'attarder sur le sujet, puisque ça me semble être relatif à chacun, mais je vous cite des choses que vous pouvez très très facilement modifier de cette manière :
- Le cooldown de l'arme (c'est le "Attendre 0.2 Sec" qui est à la fin). Augmentez le pour faire un fusil à canon scié !
- Les dégâts de l'arme. Au lieu de retirer 1 à la variable vie de l'ennemi, retirez plus !
- Arme automatique : enlevez la condition "Si interrupteur touche relâchée : ON" pour que l'on puisse tirer indéfiniment et non plus par acoup.
- Changer les effets sonores et le coût en munition
- Changer le tile qui est traversable : peut être qu'un lance flamme est inefficace si vous êtes dans l'eau, mais qu'un pistolet, oui ?
Faire respawn les ennemis
Sur la page de la mort de l'événement, au lieu de mettre un "effacer cet événement", mettez un "Attendre" assez long (une trentaine de secondes), puis rendez égale la vie de cet ennemi à 0.
Je vais devoir changer deux variables pour chaque ennemi à chaque fois lorsque je le copie colle ?
RPG Maker 2003 ne propose pas de méthode directe pour obtenir directement l'ID d'un événement. Une astuce détournée consiste à rendre égale deux variables tests aux coordonnées X et Y de "cet événement", puis d'utiliser la commande "stocker l'id d'un événement" en y précisant les deux variables tests précédentes. La variable dans laquelle l'ID sera stocké sera celui de "cet événement".
Une intelligence artificielle plus développée
Reprenons notre événement de l'ennemi. Ce qui n'est pas terrible, c'est que pour l'instant il ne fait que suivre le héros tout le temps ; c'est pas très logique lorsqu'il est à l'autre bout de la map. On souhaite faire un ennemi qui, lorsque le héros n'est pas là, fait sa vie, et qui une fois vois le héros apparaître dans une zone autour de lui, va alors chercher à le pourchasser. Une fois qu'il le pourchasse, pas de raison qu'il s'arrête !
Pour cela, on rajoute une page en première position. La page 1 sera la page où l'ennemi est inactif (iddle), la page 2 sera celle où il est agressif, et la page 3 sera celle où il est mort.
La page 1 va être en processus parallèle. Dedans, on va vérifier la distance de cet événement par rapport au héros à l'aide d'une technique (expliquée par Naked Snake) détaillée dans l'image. On peut utiliser les mêmes variables pour tous nos événements ennemis, ce n'est pas très important. Si cette distance est inférieure à une valeur donnée, on va activer un interrupteur du type I[Constante + Id de l'événement ennemi], ici I[40+ID], un peu comme pour la vie de ces monstres. J'utilise encore une fois les ID d'événements pour éviter de faire plein de conitions, et pouvoir copier/coller facilement ces événements sur la carte.
Attention ! Ici j'ai fait en sorte que l'ennemi ne bouge pas dans sa phase inactive. Cependant, vous pouvez très bien faire qu'il exécute une routine, un va et viens, voire peut être un déplacement au hasard. Cela dépend du level design que vous souhaitez adopter.
Dans la seconde page, c'est la phase agressive. Elle est désormais activée par l'interrupteur I[Constante+ID] qu'on a activé en page 1. Ici, c'est l'interrupteur I[40+63] donc I[103] qui doit être vérifié. Sur cette page, on réadopte le comportement agressif de notre zombie de tout à l'heure : il marche vers le héros, il inflige des dégâts, activation en contact événement/héros, etc.
Spoiler: |
|
|
Rajouter une barre de vie et de munition
En effet, ce serait pratique. Je vous renvoie vers
un tutoriel spécialisé sur le sujet.
Améliorations quartes : si vous voulez faire le ouf
Rajouter des animations/des interactions avec l'ennemi touché
C'est une section que j'ai volontairement passé sous silence. Si RPG Maker 2003 nous permet facilement d'incrémenter une variable pointée, c'est plus compliqué de faire des animations, du style flasher l'ennemi qui est touché, faire apparaître une gerbe de sang sur lui, le faire reculer d'un pas en arrière, etc. Le seul cache-misère que j'ai utilisé pour l'instant, c'est un effet sonore, mais faut le dire : c'est bof. Pour résoudre ça, je vois deux méthodes.
Première méthode : encore un interrupteur.
La première consiste à activer (encore) un autre interrupteur du style I[Constante+ID de l'événement] dans l'événement-maître. Lorsque la balle va toucher un ennemi, elle va activer cet interrupteur. Dans la programmation de l'événement ennemi, on va faire une nouvelle page en antépénutième position (juste avant celle de mort), activée par cet interrupteur I[Constante+ID de l'événement]. Dans cette page, on case alors toutes les animations que l'on veut faire : flasher cet événement, afficher une animation de combat sur cet événement, fixe la direction et fuit le héros d'un pas... Puis, on désactive cet interrupteur.
Cette méthode peut fonctionner. Mais entre ça et l'intelligence artificielle, on commence à avoir un peu beaucoup d'interrupteurs et de constantes. De plus, il peut y avoir des bugs, à cause des priorités des pages, etc. Pour cette raison, je ne la détaille pas.
Seconde méthode : avec DynRpg
DynRPG est un patch qui permet d'appliquer des mini-patchs (plugins) très puissants à votre jeu super facilement. Je vous renvoie à
la présentation d'Alex RE sur le sujet.
Il vous est possible
d'utiliser le plugin DynParams (
que vous installez comme ceci) afin de préciser n'importe quel argument d'une commande en event par une variable. Le manuel d'utilisation est en anglais et un peu abscons à saisir, mais en gros c'est du pain béni pour ce que l'on veut faire.
En gros, dans notre événement-maître, au même endroit que là où on joue l'effet sonore ("cow" dans mon exemple), on précise simplement dans des commentaires :
<>// @dynparams_add param 1, V12
<>// @dynparams_overwrite_next
<>Flasher événement ou héros : Héros, 0.1s, pausé
Et cela va flasher l'événement dont l'idée est dans la variable V[12] (c'est pour cela que l'on indique V12, on peut remplacer V12 par V84 ou autre, selon les besoins). Le fonctionnement est très bien détaillé dans le readme du plugin, et cela revient à grossièrement appliquer le code ci dessus dérivé à toutes les sauces.
Conclusion : réaliser le tutoriel en 15 minutes
Pour ne pas faire mentir le titre,
je relève le défi des 15 minutes dans cette vidéo, comme quoi c'est archi faisable de tout programmer en un quart d'heure yo
Spoiler: |
|
Repères et remarques critiques :
- À 1:00, j'ai fini l'appui de touche
- À 2:30, j'ai fini le déplacement de la balle imaginaire
- À 3:40, j'ai édité mon chipset et j'ai fini le test d'ID terrain
- À 5:20, j'ai fini le test d'ID event. Je me trompe et j'oublie d'indiquer un "sortir de la boucle".
- À 6:30, j'ai fini de programmer l'ennemi. Cependant, je me trompe dans son ID, et je ne corrige cet erreur qu'à 8:30.
- À 9:40, j'ai fini de programmer le straff.
- À 15:20, je corrige mon oubli dans le test d'ID event et inclue le "sortir de la boucle".
Dans le reste de la vidéo, je mappe et je rajoute au final les 30 ennemis. |
Voilà le lien de la démo que j'ai réalisé en 15 minutes sous la contrainte de la caméra. Autant dire que c'est un système qui conviendra parfaitement à toutes les sessions de speed-making et
Delirium, un vrai sport !
Bon making à tous !