Comment coder les réalisations déverrouillables pour votre jeu (approche simple)

Les réalisations sont extrêmement populaires parmi les joueurs. Ils peuvent être utilisés de différentes manières, de l'enseignement à la mesure du progrès, mais comment pouvons-nous les coder? Dans ce tutoriel, je présenterai une approche simple pour mettre en œuvre des réalisations..

Remarque: Bien que ce tutoriel ait été écrit avec AS3 et Flash, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux..

Vous pouvez télécharger ou insérer le code final à partir du référentiel GitHub: https://github.com/Dovyski/Achieve


Code d'accomplissement: Des bonbons ou un sort?

À première vue, programmer un système de réalisations semble trivial - et c'est en partie vrai. Ils sont généralement implémentés avec des marqueurs, chacun représentant une métrique de jeu importante, telle que le nombre d’ennemis tués ou la vie du joueur..

Une réalisation est déverrouillée si ces compteurs correspondent à des tests spécifiques:

 tuésEnnemis = tuésEnnemis + 1; if (killEnemies> = 10 && vies> = 2) // déverrouiller la réalisation

Il n’ya rien de mal à cette approche, mais imaginons un test avec 10 jetons ou plus. En fonction du nombre de réalisations (et de marqueurs), vous pouvez vous retrouver avec du code spaghetti répliqué dans tous les sens:

 si (mortsEnnemis> 5 && vies> 2 && décès <= 3 && perfectHits > 20 && ammo> = 100 && wrongHits <= 1)  // unlock achievement 

Une meilleure idée

Mon approche est également basée sur des compteurs, mais ils sont contrôlés par des règles simples:


Réalisations basées sur les propriétés. Les rectangles gris sont des propriétés / réalisations actives.

Une réalisation est déverrouillée lorsque toutes ses propriétés associées sont actives. Une réalisation peut avoir une ou plusieurs propriétés associées, chacune étant gérée par une seule classe, il n'est donc pas nécessaire d'écrire si() déclarations partout dans le code.

Voici l'idée:

  • Identifiez toutes les métriques de jeu intéressantes (victimes, morts, erreurs, matchs, etc.).
  • Chaque métrique devient un propriété, guidé par une contrainte de mise à jour. La contrainte contrôle si la propriété doit être changée quand une nouvelle valeur arrive.
  • Les contraintes sont: "mettre à jour uniquement si la nouvelle valeur est supérieure à la valeur actuelle"; "mise à jour uniquement si la nouvelle valeur est inférieure à la valeur actuelle"; et "mettre à jour quelle que soit la valeur actuelle".
  • Chaque propriété a un Activation règle - par exemple "kills est actif si sa valeur est supérieure à 10".
  • Vérifiez les propriétés activées périodiquement. Si toutes les propriétés associées d'une réalisation sont actives, la réalisation est déverrouillée.

Pour mettre en œuvre une réalisation, il faut définir quelles propriétés doivent être actives pour débloquer cette réalisation. Après cela, les propriétés doivent être mises à jour pendant le jeu et vous avez terminé.!

Les sections suivantes présentent une implémentation pour cette idée.


Description des propriétés et des réalisations

La première étape de mise en œuvre est la représentation de Propriétés et réalisations. La classe Propriété peut être le suivant:

 Classe publique Property private var mName: String; private var mValue: int; mActivation de var privé: String; private var mActivationValue: int; private var mInitialValue: int; public function Propriété (theName: String, theInitialValue: int, theActivation: String, theActivationValue: int) mName = theName; mActivation = theActivation; mActivationValue = theActivationValue; mInitialValue = theInitialValue; 

Une propriété a un nom (mName), une valeur (mValue, qui est le compteur), une valeur d’activation (mActivationValue) et une règle d'activation (activation).

La règle d'activation est quelque chose comme "actif si supérieur à" et elle contrôle si une propriété est active (plus sur cela plus tard). Une propriété est dite active lorsque sa valeur est comparée à la valeur d'activation et que le résultat satisfait à la règle d'activation.

Une réalisation peut être décrite comme suit:

 public class Achievement private var mName: String; // nom de la réalisation private var mProps: Array; // tableau de propriétés associées private var mUnlocked: Boolean; // réalisation est déverrouillée ou non fonction publique Achievement (theId: String, theRelatedProps: Array) mName = theId; mProps = theRelatedProps; mUnlocked = false; 

Une réalisation a un nom (mName) et un drapeau pour indiquer s’il est déjà déverrouillé (débloqué). Le tableau mProps contient une entrée pour chaque propriété nécessaire pour déverrouiller la réalisation. Lorsque toutes ces propriétés sont actives, la réalisation doit être déverrouillée.


Gestion des propriétés et des réalisations

Toutes les propriétés et les réalisations seront gérées par une classe centralisée nommée Atteindre. Cette classe doit se comporter comme une boîte noire recevant les mises à jour des propriétés et indiquant si une réalisation a été déverrouillée. Sa structure de base est:

 public class Achieve // règles d'activation public static const ACTIVE_IF_GREATER_THAN: String = ">"; Const statique public ACTIVE_IF_LESS_THAN: String = "<"; public static const ACTIVE_IF_EQUALS_TO :String = "=="; private var mProps :Object; // dictionary of properties private var mAchievements :Object; // dictionary of achievements public function Achieve()  mProps =  ; mAchievements =  ;  

Toutes les propriétés étant mises à jour en utilisant leur nom en tant qu’index de recherche, il est pratique de les stocker dans un dictionnaire (le mProps attribut dans la classe). Les réalisations seront traitées de la même manière, elles seront donc stockées de la même manière (les réalisations attribut).

Afin de gérer l’ajout de propriétés et de réalisations, nous créons les méthodes defineProperty () et defineAchievement ():

 fonction publique defineProperty (theName: String, theInitialValue: int, theaActivationMode: String, theValue: int): void mProps [theName] = nouvelle propriété (theName, theInitialValue, theeaActivationMode, theValue);  fonction publique defineAchievement (theName: String, theRelatedProps: Array): void mAchievements [theName] = new Achievement (theName, theRelatedProps); 

Les deux méthodes ajoutent simplement une entrée à la propriété ou au dictionnaire de réalisations.


Mise à jour des propriétés

Maintenant que le Atteindre La classe peut gérer les propriétés et les réalisations, il est temps de la rendre capable de mettre à jour les valeurs des propriétés. Une propriété sera mise à jour pendant le jeu et fera office de compteur. Par exemple la propriété tuéEnnemis devrait être incrémenté chaque fois qu'un ennemi est détruit.

Deux méthodes suffisent pour cela: une pour lire et une autre pour définir une valeur de propriété. Les deux méthodes appartiennent à la Atteindre classe et peut être implémenté comme suit:

 fonction publique getValue (theProp: String): int return mProps [theProp] .value;  fonction privée setValue (theProp: String, theValue: int): void mProps [theProp] .value = theValue; 

Il est également utile d'avoir une méthode pour ajouter une valeur à un groupe de propriétés, quelque chose comme un incrément / décrément de lot:

 fonction publique addValue (theProps: Array, theValue: int): void pour (var i: int = 0; i < theProps.length; i++)  var aPropName :String = theProps[i]; setValue(aPropName, getValue(aPropName) + theValue);  

Vérification des réalisations

Rechercher des réalisations débloquées est simple et facile: parcourez le dictionnaire des réalisations et vérifiez si toutes les propriétés associées à une réalisation sont actives..

Pour effectuer cette itération, nous avons d’abord besoin d’une méthode pour vérifier si une propriété est active:

 public class Property // // le reste du code de la classe a été omis… // fonction publique isActive (): Boolean var aRet: Boolean = false; commutateur (mActivation) case Atteindre.ACTIVE_IF_GREATER_THAN: aRet = mValue> mActivationValue; Pause; case Achieve.ACTIVE_IF_LESS_THAN: aRet = mValue < mActivationValue; break; case Achieve.ACTIVE_IF_EQUALS_TO: aRet = mValue == mActivationValue; break;  return aRet;  

Maintenant, implémentons le checkRéalisations () méthode dans le Atteindre classe:

 fonction publique checkAchievements (): Vector var aRet: Vector = new Vector (); pour (var n: chaîne dans les réalisations) var aAchivement: Achievement = réalisations = [n]; if (aAchivement.unlocked == false) var aActiveProps: int = 0; pour (var p: int = 0; p < aAchivement.props.length; p++)  var aProp :Property = mProps[aAchivement.props[p]]; if (aProp.isActive())  aActiveProps++;   if (aActiveProps == aAchivement.props.length)  aAchivement.unlocked = true; aRet.push(aAchivement);    return aRet; 

le checkRéalisations () méthode itère sur toutes les réalisations. À la fin de chaque itération, il teste si le nombre de propriétés actives pour cette réalisation particulière est égal au nombre de propriétés associées. Si cela est vrai, 100% des propriétés associées à cette réalisation sont actives. Le joueur a donc débloqué une nouvelle réalisation..

Pour plus de commodité, la méthode retourne un Vecteur (qui agit comme un tableau ou une liste dactylographiés) contenant toutes les réalisations qui ont été déverrouillées lors du contrôle. En outre, la méthode marque toutes les réalisations trouvées comme "déverrouillées", elles ne seront donc pas analysées à nouveau..


Ajout de contraintes aux propriétés

Jusqu'à présent, les propriétés n'ont pas de contraintes, ce qui signifie que toute valeur passée à travers setValue () mettra à jour la propriété. Imaginez une propriété nommée tué avec une bombe, qui stocke le nombre d'ennemis que le joueur a tué en utilisant une seule bombe.

Si sa règle d'activation est "si supérieur à 5" et que le joueur tue six ennemis, il devrait débloquer le succès. Cependant, supposons que checkRéalisations () La méthode n’a pas été invoquée juste après l’explosion de la bombe. Si le joueur fait exploser une autre bombe et tue trois ennemis, la propriété sera mise à jour pour 3.

Ce changement fera rater le succès au joueur. Afin de résoudre ce problème, nous pouvons utiliser la règle d'activation de propriété en tant que contrainte. Cela signifie qu'une propriété avec "si supérieur à 5" ne sera mise à jour que si la nouvelle valeur est supérieure à celle actuelle:

 fonction privée setValue (theProp: String, theValue: int): void // Quelle règle d'activation? switch (mProps [theProp] .activation) case Atteindre.ACTIVE_IF_GREATER_THAN: theValue = theValue> mProps [theProp] .value? theValue: mProps [theProp] .value; Pause; case Achieve.ACTIVE_IF_LESS_THAN: theValue = theValue < mProps[theProp].value ? theValue : mProps[theProp].value; break;  mProps[theProp].value = theValue; 

Réinitialisation et marquage des propriétés

Souvent, les réalisations ne sont pas liées au jeu entier, mais spécifiques à des périodes telles que les niveaux. Quelque chose comme "battre un niveau en tuant 40 ennemis ou plus" doit être compté pendant le niveau, puis réinitialisé pour que le joueur puisse réessayer au niveau suivant..

Une solution possible à ce problème est l'ajout de Mots clés aux propriétés. L'utilisation de balises permet la manipulation de groupes de propriétés. En utilisant l'exemple précédent, le tuéEnnemis propriété peut être étiquetée comme levelStuff, par exemple.

En conséquence, il est possible de vérifier les réalisations et de réinitialiser les propriétés en fonction des balises:

 // Définit la propriété à l'aide d'une balise defineProperty ("killEnemies",…, "levelStuff"); if (levelIsOver ()) // Vérifie les réalisations, mais uniquement celles basées sur des propriétés // étiquetées avec "levelStuff". Toutes les autres propriétés seront ignorées, // pour ne pas interférer avec d'autres réalisations. checkAchievements ("levelStuff"); // Réinitialise toutes les propriétés étiquetées avec 'levelStuff' resetProperties ("levelStuff"); 

La méthode checkRéalisations () devient beaucoup plus polyvalent avec les tags. Il peut être invoqué à tout moment maintenant, tant qu'il exploite le groupe de propriétés correct.


Démonstration d'utilisation

Voici un extrait de code montrant comment utiliser cette implémentation de réalisation:

 var a: Atteindre = nouveau Atteindre (); function initGame (): void a.defineProperty ("killEnemies", 0, Achieve.ACTIVE_IF_GREATER_THAN, 10, "levelStuff"); a.defineProperty ("lives", 3, Achieve.ACTIVE_IF_EQUALS_TO, 3, "levelStuff"); a.defineProperty ("completedLevels", 0, Achieve.ACTIVE_IF_GREATER_THAN, 5); a.defineProperty ("morts", 0, atteindre.ACTIVE_IF_EQUALS_TO, 0); a.defineAchievement ("masterKill", ["killEnemies"]); // Tue plus de 10 ennemis. a.defineAchievement ("cantTouchThis", ["lives"]); // Termine un niveau et ne meurs pas. a.defineAchievement ("NothingElse", ["completedLevels"]); // bat tous les 5 niveaux. a.defineAchievement ("héros", ["niveaux terminés", "morts"]); // bat tous les 5 niveaux, ne meurs pas pendant le processus function gameLoop (): void if (ennemiWasKilled ()) a.addValue (["killEnemies"], 1);  if (playerJustDied ()) a.addValue (["lives"], -1); a.addValue (["morts"], 1);  function levelUp (): void a.addValue (["completedLevels"], 1); a.checkAchievements (); // Réinitialise toutes les propriétés étiquetées avec 'levelStuff' a.resetProperties ("levelStuff"); 

Conclusion

Ce tutoriel a démontré une approche simple pour implémenter des réalisations dans du code. En se concentrant sur les compteurs et les tags gérés, l’idée est d’éliminer plusieurs tests répartis dans le code..

Vous pouvez télécharger ou insérer le code à partir de son dépôt GitHub: https://github.com/Dovyski/Achieve

J'espère que cette approche vous aidera à mettre en œuvre les réalisations de manière plus simple et meilleure. Merci pour la lecture! N'oubliez pas de vous tenir au courant en nous suivant sur Twitter, Facebook ou Google+.

Articles Similaires
  • Faites-leur travailler pour elle: Concevoir des réalisations pour vos jeux
  • Ne vous contentez pas de le donner: Concevoir des déverrouillage pour vos jeux