Un mécanisme commun aux jeux d’action est que les ennemis lâchent un objet ou une récompense à leur mort. Le personnage peut alors récupérer ce butin pour obtenir un avantage. C'est un mécanisme attendu dans de nombreux jeux, comme les JdR, car il incite le joueur à se débarrasser de ses ennemis, ainsi que d'une petite explosion d'endorphines lorsqu'il découvre quelle en est la récompense immédiate..
Dans ce didacticiel, nous examinerons le fonctionnement interne d’un tel mécanisme et verrons comment le mettre en œuvre, quels que soient le type de jeu et l’outil de codage / le langage que vous utiliserez éventuellement..
Les exemples que j’utilise pour illustrer cela ont été réalisés à l’aide de Construct 2, un outil de création de jeux HTML5, mais ne lui sont nullement spécifiques. Vous devriez pouvoir implémenter le même mécanisme, quel que soit votre langage de codage ou votre outil..
Les exemples ont été réalisés dans la version 167.2 et peuvent être ouverts et modifiés dans la version gratuite du logiciel. Vous pouvez télécharger la dernière version de Construct 2 ici (depuis que j'ai commencé à écrire cet article, au moins deux versions plus récentes ont été publiées) et vous amuser avec les exemples qui vous plaisent. Les exemples de fichiers source CAPX sont joints à ce didacticiel dans le fichier zip..
Lors de la mort d'un ennemi (donc, lorsque ses HP sont inférieurs ou égaux à zéro), une fonction est appelée. Le rôle de cette fonction est de déterminer s’il ya chute ou non, et, dans l’affirmative, le type de chute qu’elle devrait avoir..
La fonction peut également gérer la création de la représentation visuelle de la goutte, en la générant aux anciennes coordonnées d'écran de l'ennemi..
Prenons l'exemple suivant:
Clique le Tuez 100 bêtes bouton. Cela exécutera un traitement par lots qui crée 100 bêtes aléatoires, les tue et affiche le résultat pour chaque bête (c'est-à-dire, si la bête laisse tomber un objet et, le cas échéant, quel type d'objet). Les statistiques en bas de l'écran indiquent le nombre d'objets supprimés et le nombre d'objets supprimés de chaque type..
Cet exemple est strictement un texte qui montre la logique derrière la fonction et montre que ce mécanisme peut être appliqué à tout type de jeu, que ce soit un jeu de plateforme sur lequel vous écrasez les ennemis, un jeu de tir à vue de haut en bas, ou un RPG.
Regardons comment fonctionne cette démo. Premièrement, les bêtes et les gouttes sont contenues dans des tableaux. Ici se trouve le la bête
tableau:
Index (X) | Nom (Y-0) | Taux de chute (Y-1) | Rareté de l'objet (Y-2) |
0 | Sanglier | 100 | 100 |
1 | Lutin | 75 | 75 |
2 | Écuyer | 65 | 55 |
3 | ZogZog | 45 | 100 |
4 | Hibou | 15 | 15 |
5 | Mastodonte | 35 | 50 |
Et voici la gouttes
tableau:
Index (X) | Nom (Y-0) | Rareté de l'objet (Y-1) |
0 | Sucette | 75 |
1 | Or | 50 |
2 | Roches | 95 |
3 | Bijou | 25 |
4 | Encens | 35 |
5 | Équipement | 15 |
le X
valeur (la Indice
column) pour le tableau agit comme un identifiant unique pour la bête ou le type d’article. Par exemple, la bête d'index 0
est un Sanglier
. L'élément d'index 3
est un Bijou
.
Ces tableaux agissent pour nous comme des tables de recherche, contenant le nom ou le type de chaque bête ou élément, ainsi que d'autres valeurs nous permettant de déterminer la rareté ou le taux de chute. Dans le tableau de la bête, il y a deux autres colonnes après le nom:
Le taux d'abandon
est la probabilité que la bête laisse tomber un objet lorsqu'il est tué. Par exemple, le sanglier aura 100% de chances de laisser tomber un objet lorsqu'il est tué, tandis que le hibou aura 15% de chance de faire la même chose..
Rareté
définit la rareté des objets pouvant être supprimés par cette bête. Par exemple, un verrat risquera d’abandonner des objets d’une valeur de rareté de 100. Maintenant, si nous vérifions la gouttes
tableau, nous pouvons voir que les roches est l'élément avec la plus grande rareté (95). (Malgré le fait que la valeur de rareté soit élevée, en raison de la façon dont j'ai programmé la fonction, plus le nombre de rareté est élevé, plus l'élément est courant. Il a plus de chances de laisser tomber les rochers qu'un article avec une valeur de rareté inférieure.)
Et cela nous intéresse du point de vue de la conception de jeux. Pour l’équilibre du jeu, nous ne voulons pas que le joueur ait accès trop tôt à trop d’équipements ou d’objets haut de gamme. Sinon, le personnage risque d’être maîtrisé trop tôt et le jeu sera moins intéressant à jouer..
Ces tables et valeurs ne sont que des exemples. Vous pouvez et devez les utiliser et les adapter à votre propre système de jeu et à votre propre univers. Tout dépend de l'équilibrage de votre système. Si vous souhaitez en savoir plus sur l'équilibrage, je vous recommande de consulter cette série de didacticiels: Équilibrage de RPG tour par tour.
Regardons maintenant le code (pseudo) de la démo:
CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 CONSTANT BEAST_RARITY = 2 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Ces constantes sont utilisées pour une meilleure lisibilité des tableaux Au début du projet, remplissez les tableaux avec les valeurs correctes aBeast (6 , 3) // Le tableau qui contient les valeurs de chaque tableau beast aDrop (6,2) // Le tableau qui contient les valeurs de chaque tableau d’éléments aTemp (0) // Un tableau temporaire nous autorisant à choisir le type d’élément drop array aStats (6) // Le tableau qui contiendra la quantité de chaque élément déposé. Cliquez sur la fonction d'appel "SlainBeast (100)", fonction SlainBest (répétitions) int BeastDrops = 0 // La variable qui conservera le nombre de De nombreuses bêtes ont supprimé l'item Text.text = "" aStats (). clear // Réinitialise toutes les valeurs contenues dans ce tableau afin de créer de nouvelles statistiques pour le lot en cours. Répétitions répétées fois int BeastType int DropChance int Rarity BeastType = Random (6) / / Depuis que nous avons 6 bêtes dans notre tableau Rarity = aBeast (BeastType, BE AST_RARITY) // Récupère la rareté des éléments que la bête devrait supprimer du tableau aBeast DropChance = ceil (random (100)) // Choisit un nombre compris entre 0 et 100) Text.text = Text.text & loopindex & "_" & aBeast (BeastType, BEAST_NAME) & "is slared" Si DropChance> aBeast (BeastType, BEAST_DROPRATE) // La valeur DropChance est supérieure à la vitesse de défilement de cette bête Text.text = Text.text & "." & newline // Nous nous arrêtons ici, on considère que cette bête n'a pas lâché d'objet. Si DropChance <= aBeast(BeastType,BEAST_DROPRATE) Text.text = Text.Text & " dropping " //We will put some text to display what item was dropped //On the other hand, DropChance is less or equal the droprate for this beast aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array aDrop(a,DROP_RATE) >= Rarity // Lorsque le taux de suppression d'élément est supérieur ou égal à la valeur attendue pour Rarity Push aTemp, a // Nous plaçons l'index en cours dans le tableau temp. Nous savons que cet index est un type d'élément possible à déposer dans DropType DropType = random (aTemp.width) // DropType est l'un des index contenus dans le tableau temporaire Text.text = Text.text & aDrop (DropType, DROP_NAME) & "." & newline // Nous affichons le nom de l'élément qui a été supprimé // Nous effectuons des statistiques aStats (DropType) = aStats (DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & "beasts drop items." & newline Pour a = 0 à aStats.width // Affiche chaque quantité d’article qui a été supprimée et aStats (a)> 0 TextStats.Text = TextStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & " "
Tout d’abord, l’action de l’utilisateur: cliquer sur le bouton Tuez 100 bêtes bouton. Ce bouton appelle une fonction avec un paramètre de 100
, juste parce que 100 se sent comme un bon nombre d'ennemis à tuer. Dans un vrai jeu, il est plus probable que vous tuiez des bêtes une à une, bien sûr.
À partir de là, la fonction SlainBeast
est appelé. Son but est d’afficher du texte pour informer l’utilisateur sur ce qui s’est passé. Tout d'abord, il nettoie la BeastDrops
variable et aStats
tableau, qui sont utilisés pour les statistiques. Dans un vrai jeu, il est peu probable que vous en ayez besoin. Il nettoie le Texte
ainsi, de sorte qu'un nouveau 100 lignes seront affichées pour voir les résultats de ce lot. Dans la fonction même, trois variables numériques sont créées: Type de bête
, DropChance
, et Rareté
.
Type de bête
sera l'indice que nous utilisons pour faire référence à une ligne spécifique du une bête
tableau; c'est fondamentalement le genre de bête que le joueur devait affronter et tuer. Rareté
est pris de la une bête
tableau également; c'est la rareté de l'objet que cette bête devrait laisser tomber, la valeur de la Rareté de l'objet
champ dans le une bête
tableau.
finalement, DropChance
est un nombre que nous choisissons au hasard entre 0
et 100
. (La plupart des langages de codage auront pour fonction d’obtenir un nombre aléatoire dans une plage, ou au moins d’obtenir un nombre aléatoire entre 0
et 1
, que vous pouvez ensuite simplement multiplier par 100
.)
À ce stade, nous pouvons afficher notre premier bit d’information dans Texte
objet: nous savons déjà quel genre de bête a engendré et a été tué. Donc, nous concaténons à la valeur actuelle de Text.text
la BEAST_NAME
du courant Type de bête
nous avons choisi au hasard, sur une bête
tableau.
Ensuite, nous devons déterminer si un élément doit être supprimé. Nous le faisons en comparant les DropChance
valeur à la BEAST_DROPRATE
valeur de la une bête
tableau. Si DropChance
est inférieur ou égal à cette valeur, nous lâchons un élément.
(J'ai décidé d'adopter l'approche "Inférieur ou égal à", après avoir été influencé par ces acteurs réels utilisant l'ensemble de règles de D & D King Arthur: Pendragon concernant les jets de dés, mais vous pouvez très bien coder la fonction dans l'autre sens. , décider que les baisses ne se produiront que lorsque "plus grand ou égal". C'est juste une question de valeurs numériques et de logique. Cependant, restez cohérent tout au long de votre algorithme, et ne changez pas la logique à mi-chemin, sinon vous pourriez vous retrouver avec problèmes lorsque vous essayez de le déboguer ou de le maintenir.)
Deux lignes déterminent si un élément est supprimé ou non. Premier:
DropChance> aBeast (BeastType, BEAST_DROPRATE)
Ici, DropChance
est supérieure à la Le taux d'abandon
, et nous considérons que cela signifie qu'aucun article n'est abandonné. A partir de là, la seule chose affichée est un "." (point final) qui termine la phrase "[BeastType] a été tué.", avant de passer au prochain ennemi de notre lot.
D'autre part:
DropChance <= aBeast(BeastType,BEAST_DROPRATE)
Ici, DropChance
est inférieur ou égal à la Le taux d'abandon
pour le courant Type de bête
, Nous considérons donc que cela signifie qu’un élément est supprimé. Pour ce faire, nous allons effectuer une comparaison entre le Rareté
de l'article que le courant Type de bête
est "autorisé" à baisser, et les nombreuses valeurs de rareté que nous avons définies dans le une goutte
table.
Nous parcourons le une goutte
table, vérifiant chaque index pour voir si sa LE TAUX D'ABANDON
est supérieur ou égal à Rareté
. (Rappelez-vous, contre-intuitivement, plus le Rareté
plus l'élément est commun.) Pour chaque index qui correspond à la comparaison, nous poussons cet index dans un tableau temporaire., une température
.
A la fin de la boucle, nous devrions avoir au moins un index dans le une température
tableau. (Sinon, nous devons repenser notre une goutte
et une bête
les tables!). Nous faisons ensuite une nouvelle variable numérique DropType
qui choisit au hasard l'un des indices de la une température
tableau .; ce sera l'élément que nous lâchons.
Nous ajoutons le nom de l'élément à notre objet Texte, ce qui donne à la phrase quelque chose comme "Type de bête
a été tué, laissant tomber un DROP_NAME
.". Ensuite, pour cet exemple, nous ajoutons quelques chiffres à nos diverses statistiques (dans le aStats
tableau et BeastDrops
).
Enfin, après les 100 répétitions, nous affichons ces statistiques, le nombre de bêtes (sur 100) qui ont supprimé des éléments et le nombre de chaque élément supprimé..
Considérons un autre exemple:
presse Espace pour créer une boule de feu qui va tuer l'ennemi.
Comme vous pouvez le constater, un ennemi aléatoire (d'un bestiaire de 11) est créé. Le personnage du joueur (à gauche) peut créer une attaque de projectile. Quand le projectile frappe l'ennemi, l'ennemi meurt.
À partir de là, une fonction similaire à celle décrite dans l'exemple précédent détermine si l'ennemi lance un objet ou non, et détermine en quoi il consiste. Cette fois, il crée également la représentation visuelle de l'élément supprimé et met à jour les statistiques en bas de l'écran..
Voici une implémentation en pseudocode:
CONSTANT ENEMY_NAME = 0 CONSTANT ENEMY_DROPRATE = 1 CONSTANT ENEMY_RARITY = 2 CONSTANT ENEMY_ANIM = 3 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Constantes pour la lisibilité des tableaux int EnemiesSpawned = 0 (17,2) array aStats (17) array aTemp (0) Au début du projet, nous lançons les données dans aEnemy et aDrop Start Timer "Spawn" pendant 0,2 seconde. Fonction "SpawnEnemy" int EnemyType = 0 EnemyType = random (11 ) // Nous sortons un type d’ennemi parmi les 11 disponibles Créer un objet Enemy // Nous créons un objet visuel Enemy à l’écran Enemy.Animation = aEnemy (EnemyType, ENEMY_ANIM) EnemiesSpawned = EnnemisSpawned + 1 txtEnemy.text = aEnemy (EnemyType, ENEMY_NAME) ) & "est apparu" Enemy.Name = aEnemy (EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Clavier Clavier "Espace" pressé Créer un objet Projectile à partir de Char.Position Projectile entre en collision avec Enemy Destroy Projectile Ennemi début Fade txtEnemy.text = Enemy.Name & "a été vaincu." Fondu Enemy terminé Démarrer la minuterie "Spawn" pendant 2,5 secondes // Une fois le fondu en sortie terminé, nous attendons 2,5 secondes avant de générer un nouvel ennemi à une position aléatoire sur l'écran. Fonction "Lâcher" (Enemy.Type, Enemy.X, Enemy .Y, Enemy.Name) Fonction Drop (EnemyType, EnemyX, EnemyY, EnemyName) int DropChance = 0 int Rarity = 0 DropChance = ceil (random (100)) Rarity = aEnemy (EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & " supprimé "Si DropChance> aEnemy (EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text &" rien ". // Rien n'a été oublié Si DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE) aTemp.clear/set size to 0 For a = 0 to aDrop.Width and aDrop(a, DROP_RATE) >= Rarity aTemp.Push (a) // Nous poussons l'index actuel dans le tableau aTemp en tant qu'indice de dépôt possible. DropType = 0 DropType = Random (aTemp.Width) // Nous sélectionnons quel est l'index de dépôt parmi les index stockés dans aTemp. aStats (DropType) = aStats (DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Créer une chute d'objet sur EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = txtEnemy.Text & aDrop. (DropType, DROP_NAME) & "." // Nous affichons le nom de la chute txtStats.text = EnemiesDrops & "ennemis sur" & EnemiesSpawned & "objets lâchés". & newline Pour a = 0 à aStats.width et aStats (a)> 0 txtStats.text = txtStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & "" Minuteur "spawn" Fonction d'appel "SpawnEnemy "
Regardez le contenu de la un ennemi
et une goutte
tables, respectivement:
Index (X) | Nom (Y-0) | Taux de chute (Y-1) | Rareté de l'objet (Y-2) | Nom de l'animation (Y-3) |
0 | Femme guérisseur | 100 | 100 | Guérisseur_F |
1 | Guérisseur mâle | 75 | 75 | Guérisseur_M |
2 | Mage femelle | 65 | 55 | Mage_F |
3 | Mage Male | 45 | 100 | Mage_M |
4 | Ninja Femme | 15 | 15 | Ninja_F |
5 | Ninja Male | 35 | 50 | Ninja_M |
6 | Ranger Male | 75 | 80 | Ranger_M |
7 | Townfolk Femme | 75 | 15 | Townfolk_F |
8 | Townfolk Male | 95 | 95 | Townfolk_M |
9 | Guerrier Femme | 70 | 70 | Guerrier_f |
dix | Guerrier Mâle | 45 | 55 | Guerrier_m |
Index (X) | Nom (Y-0) | Rareté de l'objet (Y-1) |
0 | Pomme | 75 |
1 | banane | 50 |
2 | Carotte | 95 |
3 | Grain de raisin | 85 |
4 | Potion vide | 80 |
5 | Potion bleue | 75 |
6 | Potion rouge | 70 |
7 | Potion verte | 60 |
8 | Coeur rose | 65 |
9 | Perle bleue | 15 |
dix | Roche | 100 |
11 | Gant | 25 |
12 | Armure | 30 |
13 | Bijou | 35 |
14 | Chapeau de mage | 65 |
15 | Bouclier en bois | 85 |
16 | Hache de fer | 65 |
Contrairement à l'exemple précédent, le tableau contenant les données ennemies est nommé un ennemi
et contient une autre ligne de données, ENEMY_ANIM
, qui porte le nom de l'animation de l'ennemi. De cette façon, lors de la génération de l'ennemi, nous pouvons le rechercher et automatiser l'affichage graphique.
Dans la même veine, une goutte
contient maintenant 16 éléments, au lieu de six, et chaque index fait référence à la trame d’animation de l’objet - mais j’aurais pu aussi avoir plusieurs animations, comme pour les ennemis, si les objets lâchés devaient être animés.
Cette fois, il y a beaucoup plus d'ennemis et d'objets que dans l'exemple précédent. Vous pouvez toutefois constater que les données concernant les taux de chute et les valeurs de rareté sont toujours disponibles. Une différence notable est que nous avons séparé le frai des ennemis de la fonction qui calcule s'il y a une chute ou non. En effet, dans un vrai jeu, les ennemis feraient probablement plus que simplement attendre à l'écran pour être tués.!
Alors maintenant, nous avons une fonction SpawnEnemy
et une autre fonction Laissez tomber
. Laissez tomber
est assez similaire à la façon dont nous avons géré le "jet de dés" de notre article tombe dans l'exemple précédent, mais prend plusieurs paramètres cette fois: deux d'entre eux sont les coordonnées X et Y de l'ennemi à l'écran, puisque c'est l'endroit où nous allons vouloir créer l'élément lorsqu'il y a une chute; les autres paramètres sont les EnemyType
, afin que nous puissions rechercher le nom de l'ennemi dans le un ennemi
table, et le nom du personnage sous forme de chaîne, pour rendre plus rapide l'écriture du retour que nous voulons donner au joueur.
La logique du Laissez tomber
function est par ailleurs similaire à l'exemple précédent; ce qui change principalement, c’est la façon dont nous affichons les commentaires Cette fois, au lieu d’afficher uniquement du texte, nous créons également un objet à l’écran pour donner une représentation visuelle au joueur..
(Remarque: pour créer les ennemis sur plusieurs positions à l'écran, j'ai utilisé un objet invisible, Frayer
, comme référence, qui se déplace continuellement à gauche et à droite. Chaque fois que le SpawnEnemy
fonction est appelée, elle crée l'ennemi aux coordonnées actuelles du Frayer
objet, de sorte que les ennemis apparaissent et une variété d'emplacements horizontaux.)
Une dernière chose à discuter est quand exactement le Laissez tomber
la fonction est appelée. Je ne le déclenche pas directement à la mort de l'ennemi, mais une fois que celui-ci s'est estompé (animation de la mort de l'ennemi). Vous pouvez bien sûr demander le largage lorsque l’ennemi est encore visible à l’écran, si vous préférez; encore une fois, cela dépend vraiment de la conception de votre jeu.
Au niveau de la conception, le fait de laisser un butin à un ennemi incite le joueur à le confronter et à le détruire. Les objets lâchés vous permettent de donner des bonus, des statistiques ou même des buts au joueur, de manière directe ou indirecte..
Au niveau de la mise en œuvre, la suppression d'éléments est gérée via une fonction que le codeur décide quand appeler. La fonction vérifie la rareté des objets à supprimer en fonction du type d’ennemi tué, et peut également déterminer où le faire apparaître à l’écran si et quand nécessaire. Les données des objets et des ennemis peuvent être conservées dans des structures de données telles que des tableaux, et consultées par la fonction..
La fonction utilise des nombres aléatoires pour déterminer la fréquence et le type des gouttes. Le codeur a le contrôle sur les jets aléatoires et les données qu'il recherche afin d'adapter la sensation de ces gouttes dans le jeu..
J'espère que vous avez apprécié cet article et que vous comprenez mieux comment faire perdre à vos monstres des butins dans votre jeu. Je suis impatient de voir vos propres jeux en utilisant ce mécanicien.