Spielen, aber prüfen: Wie der Motor den Designer kurzschließt



Bei der Entwicklung eines Multiplayer-Spiels ist die Balance fast die wichtigste Komponente. Die Arbeit eines Spieledesigners ähnelt in dieser Hinsicht der Arbeit eines Geheimdienstanalysten: Wenn er gut arbeitet, merkt es niemand. Es lohnt sich zu stolpern und die Spieler nutzen den Fehler schamlos aus. Das Interessanteste passiert jedoch, wenn sich neben dem Spieledesigner auch der Programmierer irrt ...


In diesem Artikel werden wir ein Element der Cossacks 3- Strategie betrachten. Das Spiel enthält verschiedene Arten von Musketieren und anderen Schützen des 17. und 18. Jahrhunderts sowie die Möglichkeit, Technologien zu erforschen, die die Nachladezeit von Musketen verkürzen. Insgesamt gibt es zwei Verbesserungen, von denen jede + 30% der Feuerrate erhöht - je nach Spieloberfläche.


Aber selbst auf den ersten Blick ist klar, dass einige Kampfeinheiten nach der Untersuchung der Verbesserungen nicht nur mit 60%, sondern sogar mehrmals häufiger schießen. Wenn Sie die Feuerrate direkt mit dem eingebauten Spieltimer messen, entstehen völlig seltsame Zahlen, die nichts mit den angegebenen Prozentsätzen zu tun haben.


Unter der Haube der "Kosaken"


Glücklicherweise ist das Spiel für Modder sehr freundlich gestaltet, sodass alle benötigten Skripte als Textdateien im Ordner data / scripts / verfügbar sind. Gemessen an der Syntax sind die Skripte in Delphi oder in einer sehr ähnlichen Sprache geschrieben. Werfen wir einen Blick auf die Mechanik der Berechnung der Intervalle zwischen den Aufnahmen.


Anmerkungen
  • Die Analyse wurde mit dem Spiel "Cossacks 3" Version 2.1.4 durchgeführt.
  • Alle folgenden Skriptabschnitte enthalten einen vereinfachten Pseudocode.

  1. Wenn das Spiel beginnt, werden alle Kampfeinheiten initialisiert. Das Verfahren gibt die Werte für Vitalität, Kosten und Waffen für jeden Typ an. Bei Kleinwaffen wird ein Parameter übergeben, der das Intervall zwischen den Schüssen in Spielrahmen angibt:


    //lib/unit.script procedure _unit_InitBase() 'musketeer' : maxhp := 70; SetObjBaseWeapon( x,x,x,x, 150, ... ); SetObjBasePrice( ... ); //lib/unit.script procedure SetObjBaseWeapon( x,x,x,x, pause, ... ) weapon.pause := _misc_FramesToTime( pause ); 

    Den Kommentaren nach zu urteilen, ist die Zeiteinheit „Spielrahmen“ Atavismus der ersten „Kosaken“, deren Spielprozess bei der Erstellung des dritten Teils kopiert wurde. Die Frames werden jedoch sofort in Spielsekunden mit einem Verhältnis von 1:32 nachgezählt, und wir begegnen ihnen nicht mehr:


     //lib/misc.script function _misc_FramesToTime( val ) Result := ( val * gc_frames_to_time ); //dmscript.global gc_frames_to_time := 0.03125; gc_time_to_frames := 32; 

  2. Außerdem werden zu Beginn des Spiels die Daten der Spielnationen einschließlich der verfügbaren Verbesserungen initialisiert. Für jeden von ihnen wird die Wertvariable angezeigt und gespeichert, was sich bei der Untersuchung dieser Verbesserung auf die Neuberechnung der erforderlichen Parameter des Spiels auswirkt:


     //lib/country.script procedure _country_Init() _country_AddUpgrade( x,x,x,x, type_attpauseperc, -30, ... ); procedure _country_AddUpgrade( x,x,x,x, upgrade_type, value, ... ); 

    In unserem Fall bedeutet dies, dass die Intervalle der Militäreinheiten nach jeder Verbesserung mit 0,7 multipliziert werden und dann ... aufgerundet werden ?!


     //lib/player.script procedure _player_ApplyUpgrade() type_attpauseperc : weapon.pause := Round( weapon.pause * (1 + value/100) ); 

    Angesichts der Tatsache, dass die Intervalle der Schützen anfangs Gleitkommazahlen im Bereich von 3,125 bis 5,0 sind, erscheint die Entscheidung, das Ergebnis der Neuberechnung abzurunden, ziemlich seltsam, wenn nicht sogar wichtig.


  3. Nach jedem Schuss wird die Verzögerung vor dem nächsten Schuss angezeigt. Der Modifikator idividual.attackrate wird auf Turmstrukturen angewendet und ist in unserem Fall immer 1.


     //lib/unit.script procedure _unit_ApplyAttackPause() attackdelay := weapon.pause * idividual.attackrate; 


Zusätzlich zu dem mathematischen Fehler in den Berechnungen, dessen Details unter dem Spoiler unten gelesen werden können, gibt es eine unangemessene Rundung von Gleitkommazahlen. Ich frage mich, wie sich dieses leichte Versehen auf den ersten Blick auf die Spielmechanik auswirkt.


Ein bisschen Mathe

Die Feuerrate ist umgekehrt proportional zur Größe des Intervalls zwischen den Schüssen. Und wenn es auf die Anzahl der Runden pro Minute ankommt, verwendet die Spiel-Engine in der Regel Intervalle, um die Pause zu berechnen. Der Haken dabei ist, dass „das Intervall um 30% reduzieren“ und „die Feuerrate um 30% erhöhen“ völlig unterschiedliche Dinge sind. Das Verhältnis r zwischen den Intervallen t und der Anzahl der Schüsse n wird durch eine einfache Formel beschrieben:

 fract1t2=r= fracn2n1


Wenn wir zum Beispiel ein Intervall von 6 Sekunden (10 Runden pro Minute) nehmen und es um 30% reduzieren, erhalten wir keine 13 Runden pro Minute:

6 space mathrms cdot0.7=4.2 space mathrms; quad frac6 space mathrms4.2 space mathrms ca.1.43 neq frac1310


Um den gewünschten Wert zu erhalten, sollten Sie das aktuelle Intervall durch das gewünschte Verhältnis der neuen Feuerrate zur alten teilen:

t2= fract1r= frac6 space mathrms1.3 ca.4.62 space mathrms



Messmethode

Um die Werte zu erhalten, mit denen die Spiel-Engine arbeitet, können Sie die Protokollierungsfunktionen verwenden. Dazu müssen Sie zuerst die Protokollierung aktivieren:


  //cossacks.ini & editor.ini LogFileEnabled = true LogFileRoot = true 

Fügen Sie dann am Ende der Prozedur _unit_ApplyAttackPause () einen Aufruf der Funktion Log () hinzu :


  //data/scripts/lib/unit.script procedure _unit_ApplyAttackPause(const goHnd, weapind : Integer); begin //... if (attpause<>0) then Log(TObjProp(pobjprop).sid+' '+FloatToStr(attpause)); end; 

Jetzt können Sie mit verschiedenen Pfeilen und Verbesserungen im Karteneditor herumspielen (um den Angriffsmodus zu aktivieren, drücken Sie Strg + W ). Das Protokoll wird in eine Textdatei im Ordner / log geschrieben . Nach jedem Schuss werden die Kennung der Kampfeinheit und der Wert ihres aktuellen Intervalls aufgezeichnet.


Wer ist wer?


Anfänglich unterscheiden die Spielskripte zwischen 35 Arten von Schützen (ohne Söldner, die nicht von Verbesserungen betroffen sind). Wenn wir sie alle nach der Größe des Intervalls gruppieren, können wir zehn Kategorien unterscheiden. Ich beschloss, sie nach der relativen Erhöhung der Feuerrate zu sortieren, um die Schützen herauszusuchen, die am meisten von Verbesserungen profitieren. Die Ergebnisse der Analyse:


AngriffsintervallSchüsse / minFeuerrate
Kategorie  backslashVerbesserungen0+1+20+1+2+1+2
Ich5.004.03.012.01520+ 25%+ 67%
II6.885,04.08.71215+ 38%+ 72%
III5.314.03.011.31520+ 33%+ 77%
IV5.634.03.010.71520+ 41%+ 88%
V.3,753.02.016.02030+ 25%+ 88%
VI5.944.03.010.11520+ 48%+ 98%
VII4.063.02.014.82030+ 35%+ 103%
VIII4.383.02.013.72030+ 46%+ 119%
IX4.693.02.012.82030+ 56%+ 134%
X.3.132.01,019.23060+ 56%+ 213%

In der folgenden Abbildung entsprechen die Spalten den Kategorien I - X von links nach rechts. Die letzte gestrichelte Spalte des Diagramms entspricht der in der Spieloberfläche angegebenen Steigerungsrate. Die linke Gruppe von Spalten zeigt eine Erhöhung der Feuerrate nach einer Verbesserung, die rechte - nach beiden.


Liste der Kategorien und Einheiten

Das Spiel hat verschiedene Nationen - 17 europäische und vier einzigartige (Ukraine, Türkei, Algerien und Schottland). Europäische Fraktionen sind sich von Anfang an sehr ähnlich und haben Musketiere und Dragoner des 17. und 18. Jahrhunderts sowie Grenadiere. Aber manchmal unterscheiden sich die Pfeile einiger Nationen von der Vorlage oder werden vollständig durch einen eindeutigen Typ ersetzt.


KategorieKampfeinheiten
IchMusketier 17. Jahrhundert (Österreich)
Szekej (Ungarn)
Schottischer Schütze (England)
Polnisch-litauisches Commonwealth (Polen)
Dragoner 18. Jahrhundert (Niederlande und Piemont)
IIJäger (Schweiz)
Königlicher Musketier (Frankreich)
IIIGrenadier (Europa außer Dänemark und Preußen)
Dragoner 18. Jahrhundert (Europa außer Frankreich, den Niederlanden und dem Piemont)
Leichter Kavallerist (verschiedene Länder)
IVDragoner 17. Jahrhundert (Europa)
V.Musketier 17. Jahrhundert (Niederlande)
VIMusketier 17. Jahrhundert (Spanien)
Musketier 18. Jahrhundert (Bayern und Dänemark)
Grenadier (Dänemark)
Freiwilliger (Portugal)
Jäger (Frankreich)
VIISerdyuk (Ukraine)
VIIIMusketier 18. Jahrhundert (Sachsen)
Grenadier (Preußen)
IXMusketier 17. Jahrhundert (Europa außer Österreich, Polen, den Niederlanden und Spanien)
Musketierbund (Schottland)
Schütze (Russland)
Janitschar (Türkei)
Musketier 18. Jahrhundert (Europa außer Dänemark, Bayern und Sachsen)
Pandur (Österreich)
Dragoner 18. Jahrhundert (Frankreich)
X.Musketier 17. Jahrhundert (Polen)
Hajduk (Ungarn)

Anmerkungen:


  • Die Namen der Militäreinheiten werden von der russischen Oberfläche des Spiels kopiert.
  • Pfeile aus dem 18. Jahrhundert sind kursiv dargestellt .
  • Pferdepfeile sind fett hervorgehoben .

Es stellt sich heraus, dass der polnische Musketier des 17. Jahrhunderts und der ungarische Entführer am meisten von Verbesserungen der Feuerrate profitieren: Statt der versprochenen + 60% schießen sie mehr als dreimal häufiger. Aufgrund des niedrigen Anfangswertes des Intervalls schießen sie letztendlich zwei-, drei- oder sogar viermal schneller als alle anderen Schützen.


Unter den Kavalleristen sind die französischen Dragoner des 18. Jahrhunderts am besten besiedelt: Sie erhalten eine mehr als verdoppelte Feuerrate. Infolgedessen schießen sie 50% mehr Schüsse pro Minute als ihre Kollegen aus anderen europäischen Ländern.


Natürlich wird der Schaden eines Schusses oder der Schaden pro Sekunde hier nicht berücksichtigt, aber auch ohne diese Daten ist es offensichtlich, dass sich Militäreinheiten nicht wie beabsichtigt verhalten.


Wie zu beheben

Die schnellste und nicht-invasive Lösung des Problems besteht darin, die Formel für die Anwendung der Verbesserung neu zu schreiben. Teilen Sie das Intervall nicht nur mit 0,3, sondern auch mit 1,3, anstatt das Runden abzulehnen. Ersetzen Sie dazu einfach die Formel durch die Verbesserungsprozedur gc_upg_type_attpauseperc mit


  //lib/player.script Round(weapon.pause*(1+value/100)); 

auf


  weapon.pause/(1+(-value)/100); 

Da die Verbesserungen konsequent angewendet werden, erhalten wir am Ende anstelle der deklarierten + 60% + 69%. Aber es ist immer noch besser als + 213%.


Nachwort


Um in diesem Fall Fehlkalkulationen im Gleichgewicht zuverlässig zu identifizieren, sollten zwei weitere Aspekte der Spielmechanik analysiert werden - der Schaden von Schützen und der wirtschaftliche Wert sowie die Zeit, die für die Erstellung einer Kampfeinheit erforderlich ist. Der gesunde Menschenverstand fordert Sie jedoch auf, zuerst auf das nächste Update zu warten ...


Die Idee für die Studie kam mir aus dem Video „ Warum Angriffsraten in AoE2 oft falsch sind “, das ein ähnliches Problem in der Age of Empires II- Strategie behandelt.


UPD: Fehler teilweise behoben


Seit der Veröffentlichung des Artikels ist nicht einmal eine Woche vergangen, da die Entwickler in Update 2.2.1 den Fehler durch Rundung behoben haben. Gleichzeitig blieb die Formel selbst unverändert - die Feuerrate steigt um 43% pro Upgrade. Da die Berechnung inkrementell ist, arbeiten alle Pfeile nach Prüfung beider Verbesserungen um 104% schneller.


Tabelle

Rate der Feuereinheiten in Schüssen pro Minute nach Untersuchung beider Verbesserungen in aufsteigender Reihenfolge:


KampfeinheitenSchüsse
Jäger (Schweiz)
Königlicher Musketier (Frankreich)
17.8
Musketier 17. Jahrhundert (Spanien)
Musketier 18. Jahrhundert (Bayern und Dänemark)
Grenadier (Dänemark)
Freiwilliger (Portugal)
Jäger (Frankreich)
20.6
Dragoner 17. Jahrhundert (Europa)21.8
Grenadier (Europa außer Dänemark und Preußen)
Dragoner 18. Jahrhundert (Europa außer Frankreich, den Niederlanden und dem Piemont)
Leichter Kavallerist (verschiedene Länder)
23.0
Musketier 17. Jahrhundert (Österreich)
Szekej (Ungarn)
Schottischer Schütze (England)
Polnisch- Litauisches Commonwealth (Polen)
Dragoner 18. Jahrhundert (Niederlande und Piemont)
24.5
Musketier 17. Jahrhundert (Europa außer Österreich, Polen, den Niederlanden und Spanien)
Musketierbund (Schottland)
Schütze (Russland)
Janitschar (Türkei)
Musketier 18. Jahrhundert (Europa außer Dänemark, Bayern und Sachsen)
Pandur (Österreich)
Dragoner 18. Jahrhundert (Frankreich)
26.1
Musketier 18. Jahrhundert (Sachsen)
Grenadier (Preußen)
28.0
Serdyuk (Ukraine)30.1
Musketier 17. Jahrhundert (Niederlande)32,7
Musketier 17. Jahrhundert (Polen)
Hajduk (Ungarn)
39,2

Source: https://habr.com/ru/post/de414853/


All Articles