
Lors de la conception d'un jeu multijoueur, l'équilibre est presque l'élément le plus important. Le travail d'un concepteur de jeux à cet égard est similaire au travail d'un analyste du renseignement: s'il fonctionne bien, personne ne le remarque. Cela vaut la peine de trébucher, et les joueurs profitent sans vergogne de l'erreur. Mais la chose la plus intéressante se produit lorsque, en plus du concepteur de jeu, le programmeur se trompe également ...
Dans cet article, nous considérerons un élément de la stratégie des Cosaques 3 . Le jeu contient différents types de mousquetaires et autres tireurs des 17e et 18e siècles, ainsi que la possibilité d'explorer des technologies qui réduisent le temps de rechargement des mousquets. Il y a deux améliorations au total, chacune apportant + 30% à la cadence de tir - selon l'interface de jeu.
Mais même à vue, il est clair que certaines unités de combat, après avoir recherché les améliorations, tirent non seulement à 60%, mais même plusieurs fois plus souvent. Lorsque vous mesurez la cadence de tir directement à l'aide du chronomètre de jeu intégré, des nombres complètement étranges sortent qui n'ont rien à voir avec les pourcentages indiqués.
Sous le capot des "Cosaques"
Heureusement, le jeu est fait de manière très conviviale pour les moddeurs, donc tous les scripts dont nous avons besoin sont disponibles sous forme de fichiers texte dans le dossier data / scripts / . A en juger par la syntaxe, les scripts sont écrits en Delphi ou dans un langage très similaire. Jetons un œil à la mécanique de calcul des intervalles entre les prises de vue.
Remarques- L'analyse a été réalisée sur le jeu "Cossacks 3" version 2.1.4.
- Toutes les sections de script ci-dessous contiennent un pseudocode simplifié.
Au début du jeu, toutes les unités de combat sont initialisées. La procédure indique les valeurs de vitalité, de coût et d'armes pour chaque type. Pour les armes légères, un paramètre est transmis qui indique l'intervalle entre les tirs dans les cadres de jeu:
//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 );
A en juger par les commentaires, l'unité de temps «cadre de jeu» est l'atavisme des premiers «Cosaques», dont le processus de jeu a été copié lors de la création de la troisième partie. Cependant, les images sont immédiatement recomptées en secondes de jeu avec un rapport de 1:32, et nous ne les rencontrons plus:
//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;
De plus, au début du jeu, les données des nations de jeu sont initialisées, y compris les améliorations disponibles. Pour chacun d'eux, la variable de valeur est indiquée et stockée, ce qui, lors de l'étude de cette amélioration, affecte le recalcul des paramètres nécessaires du jeu:
//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, ... );
Dans notre cas, cela signifie que les intervalles des unités militaires après chaque amélioration sont multipliés par 0,7 puis ... sont arrondis?!
//lib/player.script procedure _player_ApplyUpgrade() type_attpauseperc : weapon.pause := Round( weapon.pause * (1 + value/100) );
Étant donné qu'au départ, les intervalles des tireurs sont des nombres à virgule flottante compris entre 3,125 et 5,0, la décision d'arrondir le résultat du recalcul semble plutôt étrange, sinon importante.
Après chaque coup tiré, le délai avant le coup suivant est indiqué. Le modificateur idividual.attackrate est appliqué aux structures des tours et dans notre cas est toujours 1.
Ainsi, en plus de l'erreur mathématique dans les calculs, dont les détails peuvent être lus sous le spoiler ci-dessous, il y a un arrondi inapproprié des nombres à virgule flottante. Je me demande quel effet sur la mécanique du jeu a ce léger oubli à première vue?
Un peu de mathsLa cadence de tir est inversement proportionnelle à la taille de l'intervalle entre les tirs. Et si c'est le nombre de tours par minute qui compte pour le joueur, le moteur de jeu, en règle générale, utilise des intervalles pour calculer la pause. Le problème ici est que «réduire l'intervalle de 30%» et «augmenter la cadence de tir de 30%» sont des choses complètement différentes. Le rapport r entre les intervalles t et le nombre de tirs n est décrit par une formule simple:
Si, par exemple, nous prenons un intervalle de 6 secondes (10 coups par minute) et le réduisons de 30%, alors nous n'obtiendrons pas 13 coups par minute:
Pour obtenir la valeur souhaitée, vous devez diviser l'intervalle actuel par le rapport souhaité entre la nouvelle cadence de tir et l'ancienne:
Méthode de mesurePour obtenir les valeurs avec lesquelles le moteur de jeu fonctionne, vous pouvez utiliser les fonctions de journalisation. Pour ce faire, vous devez d'abord activer la journalisation:
//cossacks.ini & editor.ini LogFileEnabled = true LogFileRoot = true
Et puis ajoutez à la fin de la procédure _unit_ApplyAttackPause () un appel à la fonction Log () :
//data/scripts/lib/unit.script procedure _unit_ApplyAttackPause(const goHnd, weapind : Integer); begin //... if (attpause<>0) then Log(TObjProp(pobjprop).sid+' '+FloatToStr(attpause)); end;
Maintenant, vous pouvez jouer avec diverses flèches et améliorations dans l'éditeur de carte (pour activer le mode d'attaque, appuyez sur Ctrl + W ). Le protocole sera écrit dans un fichier texte dans le dossier / log . Après chaque tir tiré, l'identifiant de l'unité de combat et la valeur de son intervalle actuel seront enregistrés.
Qui est qui
Initialement, les scripts du jeu distinguent 35 types de tireurs (sans compter les mercenaires qui ne sont pas affectés par les améliorations). Si nous les groupons tous selon la taille de l'intervalle, nous pouvons distinguer dix catégories. J'ai décidé de les trier par augmentation relative de la cadence de tir afin de distinguer les tireurs qui bénéficient le plus des améliorations. Ainsi, les résultats de l'analyse:
| Intervalle d'attaque | Coups / min | Cadence de tir |
Catégorie Améliorations | 0 | +1 | +2 | 0 | +1 | +2 | +1 | +2 |
Je | 5,00 | 4,0 | 3.0 | 12,0 | 15 | 20 | + 25% | + 67% |
II | 6,88 | 5,0 | 4,0 | 8.7 | 12 | 15 | + 38% | + 72% |
III | 5.31 | 4,0 | 3.0 | 11,3 | 15 | 20 | + 33% | + 77% |
IV | 5.63 | 4,0 | 3.0 | 10,7 | 15 | 20 | + 41% | + 88% |
V | 3,75 | 3.0 | 2.0 | 16,0 | 20 | 30 | + 25% | + 88% |
VI | 5,94 | 4,0 | 3.0 | 10.1 | 15 | 20 | + 48% | + 98% |
VII | 4,06 | 3.0 | 2.0 | 14,8 | 20 | 30 | + 35% | + 103% |
VIII | 4.38 | 3.0 | 2.0 | 13,7 | 20 | 30 | + 46% | + 119% |
IX | 4,69 | 3.0 | 2.0 | 12,8 | 20 | 30 | + 56% | + 134% |
X | 3.13 | 2.0 | 1,0 | 19,2 | 30 | 60 | + 56% | + 213% |
Dans le diagramme ci-dessous, les colonnes correspondent aux catégories I - X, de gauche à droite. La dernière colonne en pointillés du diagramme correspond au taux d'augmentation déclaré dans l'interface de jeu. Le groupe de colonnes de gauche montre une augmentation de la cadence de tir après une amélioration, la droite - après les deux. 
Liste des catégories et unitésLe jeu a plusieurs nations - 17 européennes et quatre uniques (Ukraine, Turquie, Algérie et Ecosse). Les factions européennes sont très similaires dès le début et ont des mousquetaires et des dragons des XVIIe et XVIIIe siècles, ainsi que des grenadiers. Mais parfois, les flèches de certaines nations diffèrent du modèle ou sont complètement remplacées par un type unique.
Catégorie | Unités de combat |
---|
Je | Mousquetaire 17e siècle (Autriche) Szekej (Hongrie) Tireur écossais (Angleterre) Commonwealth polono-lituanien (Pologne) Dragoon XVIIIe siècle (Pays-Bas et Piémont) |
II | Huntsman (Suisse) Mousquetaire royal (France) |
III | Grenadier (Europe sauf Danemark et Prusse) Dragoon XVIIIe siècle (Europe sauf France, Pays-Bas et Piémont) Cavalier léger (différents pays) |
IV | Dragoon XVIIe siècle (Europe) |
V | Mousquetaire 17e siècle (Pays-Bas) |
VI | Mousquetaire 17e siècle (Espagne) Mousquetaire XVIIIe siècle (Bavière et Danemark) Grenadier (Danemark) Bénévole (Portugal) Huntsman (France) |
VII | Serdyuk (Ukraine) |
VIII | Mousquetaire XVIIIe siècle (Saxe) Grenadier (Prusse) |
IX | Mousquetaire 17e siècle (Europe sauf Autriche, Pologne, Pays-Bas et Espagne) Pacte des mousquetaires (Écosse) Sagittaire (Russie) Janissar (Turquie) Mousquetaire XVIIIe siècle (Europe sauf Danemark, Bavière et Saxe) Pandur (Autriche) Dragoon XVIIIe siècle (France) |
X | Mousquetaire 17e siècle (Pologne) Hajduk (Hongrie) |
Remarques:
- Les noms des unités militaires sont copiés à partir de l'interface russe du jeu.
- Les flèches en italique du XVIIIe siècle sont en italique .
- Les flèches de cheval sont mises en évidence en gras .
Il s'avère que le mousquetaire polonais du XVIIe siècle et le détournement hongrois gagnent le plus grâce à l'amélioration de la cadence de tir: au lieu des 60% promis, ils tirent plus de trois fois plus souvent. En raison de la faible valeur initiale de l'intervalle, ils tirent finalement plus vite que tous les autres tireurs deux, trois ou même quatre fois.
Parmi la cavalerie, les dragons français du XVIIIe siècle sont les mieux installés: ils reçoivent une cadence de tir plus que doublée. En conséquence, ils tirent 50% de tirs de plus par minute que leurs homologues d'autres pays européens.
Naturellement, les dégâts d'un tir ou les dégâts par seconde ne sont pas pris en compte ici, mais même sans ces données, il est évident que les unités militaires ne se comportent pas comme prévu.
Comment réparerLa solution la plus rapide et non invasive au problème consiste à réécrire la formule pour appliquer l'amélioration. En plus de rejeter l'arrondi, au lieu de multiplier l'intervalle par 0,3, divisez-le par 1,3. Pour ce faire, remplacez simplement la formule par la procédure d' amélioration gc_upg_type_attpauseperc par
//lib/player.script Round(weapon.pause*(1+value/100));
sur
weapon.pause/(1+(-value)/100);
Étant donné que les améliorations sont appliquées de manière cohérente, au final, au lieu des + 60% déclarés, nous obtenons + 69%. Mais c'est quand même mieux que + 213%.
Postface
Afin d'identifier de manière fiable les erreurs de calcul dans la balance dans ce cas, deux autres aspects des mécanismes de jeu doivent être analysés - les dégâts des tireurs et la valeur économique ainsi que le temps requis pour créer une unité de combat. Cependant, le bon sens vous dit d'attendre d'abord la prochaine mise à jour ...
J'ai eu l'idée de l'étude dans la vidéo « Pourquoi les taux d'attaque en AoE2 sont souvent erronés », qui traite d'un problème similaire dans la stratégie Age of Empires II .
UPD: erreur partiellement corrigée
Pas même une semaine ne s'est écoulée depuis la publication de l'article, car les développeurs de la mise à jour 2.2.1 ont corrigé l'erreur d'arrondi. Dans le même temps, la formule elle-même est restée la même - la cadence de tir augmente à 43% par mise à niveau. Comme le calcul est incrémentiel, après avoir examiné les deux améliorations, toutes les flèches fonctionnent 104% plus rapidement.
TableTaux d'unités de tir en tirs par minute après avoir recherché les deux améliorations, dans l'ordre croissant:
Unités de combat | Shots |
---|
Huntsman (Suisse) Mousquetaire royal (France) | 17,8 |
Mousquetaire 17e siècle (Espagne) Mousquetaire XVIIIe siècle (Bavière et Danemark) Grenadier (Danemark) Bénévole (Portugal) Huntsman (France) | 20,6 |
Dragoon XVIIe siècle (Europe) | 21,8 |
Grenadier (Europe sauf Danemark et Prusse) Dragoon XVIIIe siècle (Europe sauf France, Pays-Bas et Piémont) Cavalier léger (différents pays) | 23,0 |
Mousquetaire 17e siècle (Autriche) Szekej (Hongrie) Tireur écossais (Angleterre) Commonwealth polonais- lituanien (Pologne) Dragoon XVIIIe siècle (Pays-Bas et Piémont) | 24,5 |
Mousquetaire 17e siècle (Europe sauf Autriche, Pologne, Pays-Bas et Espagne) Pacte des mousquetaires (Écosse) Sagittaire (Russie) Janissar (Turquie) Mousquetaire XVIIIe siècle (Europe sauf Danemark, Bavière et Saxe) Pandur (Autriche) Dragoon XVIIIe siècle (France) | 26,1 |
Mousquetaire XVIIIe siècle (Saxe) Grenadier (Prusse) | 28,0 |
Serdyuk (Ukraine) | 30,1 |
Mousquetaire 17e siècle (Pays-Bas) | 32,7 |
Mousquetaire 17e siècle (Pologne) Hajduk (Hongrie) | 39,2 |