Hoe te coderen ontgrendelbare prestaties voor uw spel (een eenvoudige aanpak)

Prestaties zijn enorm populair bij gamers. Ze kunnen op verschillende manieren worden gebruikt, van lesgeven tot het meten van de voortgang, maar hoe kunnen we ze coderen? In deze tutorial zal ik een eenvoudige benadering presenteren voor het implementeren van prestaties.

Notitie: Hoewel deze tutorial geschreven is met behulp van AS3 en Flash, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.

Je kunt de definitieve code van de GitHub-repo downloaden of splitten: https://github.com/Dovyski/Achieve


Achivements Code: Trick or Treat?

Op het eerste gezicht lijkt het programmeren van een prestatiesysteem triviaal - en dat is gedeeltelijk waar. Ze worden over het algemeen geïmplementeerd met tellers, die elk een belangrijke gamestatus weergeven, zoals het aantal gedode vijanden of het leven van de speler.

Een prestatie is ontgrendeld als die tellers overeenkomen met specifieke tests:

 killedEnemies = killedEnemies + 1; if (killedEnemies> = 10 && lives> = 2) // ontgrendelen voltooiing

Er is niets mis met die aanpak, maar stel je een test voor met tien of meer fiches. Afhankelijk van het aantal prestaties (en tellers), kun je eindigen met spaghetti-code die overal wordt gerepliceerd:

 if (killedEnemies> 5 && lives> 2 && deaths <= 3 && perfectHits > 20 && ammo> = 100 && wrongHits <= 1)  // unlock achievement 

Een beter idee

Mijn aanpak is ook gebaseerd op tellers, maar ze worden gecontroleerd door eenvoudige regels:


Prestaties op basis van eigenschappen. Grijze rechthoeken zijn actieve eigenschappen / prestaties.

Een prestatie is ontgrendeld wanneer alle bijbehorende eigenschappen actief zijn. Een prestatie kan een of meer verwante eigenschappen hebben, waarbij elke eigenschap wordt beheerd door een enkele klasse, dus het is niet nodig om te schrijven als() verklaringen overal in de code.

Hier is het idee:

  • Identificeer elke interessante spelwaarde (dood, sterfgevallen, fouten, wedstrijden, enz.).
  • Elke meeteenheid wordt een eigendom, geleid door een updatebeperking. De constraint bepaalt of de eigenschap moet worden gewijzigd wanneer een nieuwe waarde arriveert.
  • De beperkingen zijn: "update alleen als de nieuwe waarde groter is dan de huidige waarde"; "update alleen als de nieuwe waarde kleiner is dan de huidige waarde"; en "update ongeacht wat de huidige waarde is".
  • Elke woning heeft een activering regel - bijvoorbeeld "kills is actief als de waarde groter is dan 10".
  • Controleer periodiek de geactiveerde eigenschappen. Als alle gerelateerde eigenschappen van een prestatie actief zijn, is de prestatie ontgrendeld.

Om een ​​prestatie te implementeren, moet men definiëren welke eigenschappen actief moeten zijn om die prestatie te ontsluiten. Daarna moeten de eigenschappen tijdens het spelen worden bijgewerkt en bent u klaar!

De volgende secties presenteren een implementatie voor dat idee.


Eigenschappen en prestaties beschrijven

De eerste implementatiestap is de weergave van eigenschappen en verwezenlijkingen. De klas Eigendom kan de volgende zijn:

 public class Property private var mName: String; private var mValue: int; private var mActivation: String; private var mActivationValue: int; private var mInitialValue: int; public function Eigenschap (theName: String, theInitialValue: int, theActivatie: String, theActivationValue: int) mName = theName; mActivation = theActivation; mActivationValue = theActivationValue; mInitialValue = theInitialValue; 

Een eigenschap heeft een naam (MNAME), een waarde (mValue, wat de teller is), een activeringswaarde (mActivationValue) en een activeringsregel (mActivation).

De activeringsregel is zoiets als "actief als groter dan" en het bepaalt of een eigenschap actief is (daarover later meer). Van een eigenschap wordt gezegd dat deze actief is wanneer de waarde ervan wordt vergeleken met de activeringswaarde en het resultaat voldoet aan de activeringsregel.

Een prestatie kan als volgt worden beschreven:

 public class Achievement private var mName: String; // prestatienaam private var mProps: Array; // array van gerelateerde eigenschappen private var mUnlocked: Boolean; // prestatie is ontsloten of niet publieke functie Achievement (theId: String, theRelatedProps: Array) mName = theId; mProps = theRelatedProps; mUnlocked = false; 

Een prestatie heeft een naam (MNAME) en een vlag om aan te geven of deze al is ontgrendeld (mUnlocked). De array mProps bevat een vermelding voor elke eigenschap die nodig is om de prestatie te ontgrendelen. Wanneer al deze eigenschappen actief zijn, moet de prestatie worden ontgrendeld.


Beheer van eigenschappen en prestaties

Alle eigenschappen en prestaties worden beheerd door een gecentraliseerde klasse met de naam Bereiken. Deze klasse zou zich moeten gedragen als een zwarte doos die updates over onroerend goed ontvangt en vertelt of een prestatie is ontgrendeld. De basisstructuur is:

 public class Achieve // activeringsregels public static const ACTIVE_IF_GREATER_THAN: String = ">"; public static const 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 =  ;  

Omdat alle eigenschappen worden bijgewerkt met hun naam als opzoekindex, is het handig om ze op te slaan in een woordenboek (de mProps attribuut in de klas). De resultaten zullen op dezelfde manier worden behandeld, dus ze worden op dezelfde manier opgeslagen (mAchievements attribuut).

Om de toevoeging van eigenschappen en prestaties te verwerken, maken we de methoden defineProperty () en defineAchievement ():

 public function defineProperty (theName: String, theInitialValue: int, theaActivationMode: String, theValue: int): void mProps [theName] = new Property (theName, theInitialValue, theaActivationMode, theValue);  public function defineAchievement (theName: String, theRelatedProps: Array): void mAchievements [theName] = new Achievement (theName, theRelatedProps); 

Beide methoden voegen eenvoudig een item toe aan de eigenschap of het prestatiewoordenboek.


Eigenschappen bijwerken

Nu dat het Bereiken klasse kan eigenschappen en prestaties aan, het is tijd om het mogelijk te maken om eigenschapswaarden bij te werken. Een eigenschap wordt tijdens de game bijgewerkt en zal als een teller fungeren. Bijvoorbeeld het eigendom killedEnemies moet worden verhoogd telkens wanneer een vijand wordt vernietigd.

Hiervoor zijn slechts twee methoden nodig: de ene om te lezen en de andere om een ​​eigenschapswaarde in te stellen. Beide methoden behoren tot de Bereiken klasse en kan als volgt worden geïmplementeerd:

 openbare functie getValue (theProp: String): int return mProps [theProp] .value;  persoonlijke functie setValue (theProp: String, theValue: int): void mProps [theProp] .value = theValue; 

Het is ook handig om een ​​methode te hebben om een ​​waarde aan een groep eigenschappen toe te voegen, bijvoorbeeld een batchverhoging / -verlaging:

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

Controleren op prestaties

Zoeken naar niet-voltooide prestaties is eenvoudig en eenvoudig: herhaal het prestatiewoordenboek en controleer of alle gerelateerde eigenschappen van een prestatie actief zijn.

Om die iteratie uit te voeren, hebben we eerst een methode nodig om te controleren of een eigenschap actief is:

 eigenschap van openbare klasse // // de rest van de klassencode is weggelaten ... // openbare functie isActive (): Boolean var aRet: Boolean = false; switch (mActivation) case Achieve.ACTIVE_IF_GREATER_THAN: aRet = mValue> mActivationValue; breken; case Achieve.ACTIVE_IF_LESS_THAN: aRet = mValue < mActivationValue; break; case Achieve.ACTIVE_IF_EQUALS_TO: aRet = mValue == mActivationValue; break;  return aRet;  

Laten we nu het checkAchievements () methode in de Bereiken klasse:

 public function checkAchievements (): Vector var aRet: Vector = new Vector (); for (var n: String in mAchievements) var aAchivement: Achievement = mAchievements [n]; if (aAchivement.unlocked == false) var aActiveProps: int = 0; voor (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; 

De checkAchievements () methode itereert over alle prestaties. Aan het einde van elke iteratie wordt getest of het aantal actieve eigenschappen voor die specifieke prestatie gelijk is aan het aantal gerelateerde eigenschappen. Als dat waar is, dan is 100% van de gerelateerde eigenschappen voor die prestatie actief, dus heeft de speler een nieuwe prestatie ontgrendeld.

Voor het gemak retourneert de methode a Vector (die werkt als een getypte matrix of lijst) met alle prestaties die tijdens de controle zijn ontgrendeld. De methode markeert alle resultaten die zijn gevonden als "ontgrendeld", dus ze zullen in de toekomst niet opnieuw worden geanalyseerd.


Het toevoegen van beperkingen aan eigenschappen

Tot nu toe hebben eigenschappen geen beperkingen, wat betekent dat elke waarde wordt doorgegeven setValue () zal de woning updaten. Stel je een eigenschap voor met de naam killedWithASingleBomb, waarin het aantal vijanden wordt opgeslagen dat de speler met een enkele bom heeft gedood.

Als de activeringsregel 'groter dan 5' is en de speler zes vijanden doodt, moet deze de prestatie ontgrendelen. Ga echter uit van de checkAchievements () methode werd niet opgeroepen direct nadat de bom ontplofte. Als de speler een andere bom laat ontploffen en drie vijanden doodt, wordt de eigenschap bijgewerkt naar 3.

Die verandering zorgt ervoor dat de speler de prestatie mist. Om dit op te lossen, kunnen we de activeringsregel gebruiken als een beperking. Dit betekent dat een eigenschap met "indien groter dan 5" alleen wordt bijgewerkt als de nieuwe waarde groter is dan de huidige:

 private function setValue (theProp: String, theValue: int): void // Welke activeringsregel? switch (mProps [theProp] .activation) case Achieve.ACTIVE_IF_GREATER_THAN: theValue = theValue> mProps [theProp] .value? theValue: mProps [theProp] .value; breken; case Achieve.ACTIVE_IF_LESS_THAN: theValue = theValue < mProps[theProp].value ? theValue : mProps[theProp].value; break;  mProps[theProp].value = theValue; 

Eigenschappen voor opnieuw instellen en labelen

Vaak zijn prestaties niet gerelateerd aan het hele spel, maar specifiek voor perioden zoals niveaus. Iets als "een niveau verslaan waarbij 40 of meer vijanden worden gedood" moet tijdens het level worden geteld en vervolgens opnieuw worden ingesteld, zodat de speler het volgende level opnieuw kan proberen.

Een mogelijke oplossing voor dat probleem is de toevoeging van labels naar eigenschappen. Het gebruik van tags maakt de manipulatie van een groep eigenschappen mogelijk. Gebruikmakend van het vorige voorbeeld, de killedEnemies eigenschap kan worden gelabeld als levelStuff, bijvoorbeeld.

Als gevolg hiervan is het mogelijk om te controleren op prestaties en eigenschappen opnieuw in te stellen op basis van tags:

 // Definieer de eigenschap met een tag defineProperty ("killedEnemies", ..., "levelStuff"); if (levelIsOver ()) // Zoek naar prestaties, maar alleen die op basis van eigenschappen // getagd met "levelStuff". Alle andere eigenschappen worden genegeerd // zodat deze niet interfereert met andere prestaties. checkAchievements ( "levelStuff"); // Reset alle panden getagd met 'levelStuff' resetProperties ("levelStuff"); 

De methode checkAchievements () wordt veel veelzijdiger met tags. Het kan elk moment worden opgeroepen, zolang het maar de juiste groep eigenschappen beheert.


Gebruik Demonstratie

Hieronder vindt u een codefragment waarin wordt uitgelegd hoe u deze implementatie-implementatie gebruikt:

 var a: Achieve = new Achieve (); function initGame (): void a.defineProperty ("killedEnemies", 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 ("sterfgevallen", 0, Achieve.ACTIVE_IF_EQUALS_TO, 0); a.defineAchievement ("masterKill", ["killedEnemies"]); // Dood 10+ vijanden. a.defineAchievement ("cantTouchThis", ["lives"]); // Voltooi een niveau en sterf niet. a.defineAchievement ("nothingElse", ["completedLevels"]); // Versla alle 5 niveaus. a.defineAchievement ("hero", ["completedLevels", "deaths"]); // Versla alle 5 niveaus, sterf niet tijdens het proces function gameLoop (): void if (enemyWasKilled ()) a.addValue (["killedEnemies"], 1);  if (playerJustDied ()) a.addValue (["lives"], -1); a.addValue (["sterfgevallen"], 1);  function levelUp (): void a.addValue (["completedLevels"], 1); a.checkAchievements (); // Reset alle panden getagd met 'levelStuff' a.resetProperties ("levelStuff"); 

Conclusie

Deze zelfstudie toonde een eenvoudige aanpak voor het implementeren van prestaties in code. Gericht op beheerde tellers en tags, probeert het idee verschillende tests te elimineren, verspreid over de code.

Je kunt de code downloaden of uit de GitHub-repo splitsen: https://github.com/Dovyski/Achieve

Ik hoop dat deze aanpak je helpt om prestaties op een eenvoudiger en betere manier te implementeren. Bedankt voor het lezen! Vergeet niet om op de hoogte te blijven door ons te volgen op Twitter, Facebook of Google+.

gerelateerde berichten
  • Laat ze er voor werken: het ontwerpen van prestaties voor uw spellen
  • Do not Give It Away: Ontwerpen van ontgrendelingen voor je games