Comment coder les gouttes de butin de monstre

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..

Le mécanicien de base

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é..

Autre exemple: supprimer des éléments visuellement

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 tomberLaissez 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. 

Conclusion

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.

Références

  • Crédit image: Gold Treasure Icons de Clint Bellanger.
  • Sprite credit: Sprites de caractère par Antifareas.
  • Sprite credit: Battle Backgrounds de Trent Gamblin.
  • Sprite credit: Icônes Pixel Art pour les jeux de rôle de 7SoulDesign.