Hallo! Ich bin Vladimir Popov, Client-Entwickler beim War Robots Project. Zum Zeitpunkt des Verfassens dieses Artikels gibt es Kriegsroboter schon seit mehreren Jahren, und in dieser Zeit sind Dutzende neuer Mechs im Spiel aufgetaucht. Natürlich sind die vielfältigen Fähigkeiten der Roboter wichtig, denn ohne sie verlieren die Roboter ihre Einzigartigkeit, was das Spiel interessanter macht.
In diesem Beitrag erzähle ich, wie das Gameplay-Fähigkeitssystem in unserem Spiel funktioniert – und wie es sich entwickelt hat. Und um die Dinge leichter zugänglich zu machen, erkläre ich die Dinge in einfachen Worten und ohne zu viele technische Details.
Lassen Sie uns zunächst in den Projektverlauf eintauchen und uns eine ältere Implementierung ansehen, die nicht mehr verwendet wird.
Bisher wurden Fähigkeiten auf sehr triviale Weise konzipiert: Sie bestanden aus einer Komponente, die am Roboter befestigt war. Dies war eine Konstruktion, bei der der Programmierer vollständig beschrieb, wie die Fähigkeit funktioniert: sowohl ihren Ablauf als auch wie sie mit anderen Fähigkeiten interagiert. Die gesamte Logik wird in einer Komponente beschrieben, und der Spieleentwickler könnte diese einfach an den Roboter anschließen und die Parameter nach Bedarf konfigurieren. Erwähnenswert ist, dass es nicht möglich war, den Fluss der Fähigkeiten zu ändern – die Spieleentwickler konnten nur Parameter und Timings ändern.
Eine alte Fähigkeit konnte nur in zwei Zuständen existieren: aktiv und inaktiv, und jedem Zustand konnte seine Aktion zugewiesen werden.
Schauen wir uns ein Beispiel für die Fähigkeit „Jammer“ an, die zuvor der Roboter „Stalker“ besaß; es hat so funktioniert:
Diese Funktionalität reichte uns lange Zeit aus, aber mit der Zeit waren sowohl die Spieleentwickler als auch die Programmierer mit diesem Ansatz nicht mehr zufrieden: Es war für die Programmierer schwierig, diese Fähigkeiten zu unterstützen, weil der Code monströs geworden war; Dabei handelte es sich um eine sehr lange Hinterlassenschaftskette, in der jede Situation beschrieben werden musste. Darüber hinaus mangelte es den Spieleentwicklern an Flexibilität – um Änderungen an einer Fähigkeit vorzunehmen, mussten sie Modifikationen beim Programmierer anordnen, selbst wenn eine benachbarte Fähigkeit dieselbe Funktionalität hatte.
Also wurde uns klar, dass sich etwas ändern musste. Deshalb haben wir ein neues System entwickelt, bei dem jede Fähigkeit als eine Menge mehrerer zusammengehöriger Objekte dargestellt wurde. Die Funktionalität wurde in Zustandsfähigkeiten und Zustandskomponenten unterteilt.
Wie funktioniert es? Jede Fähigkeit hat ein Hauptziel. Dieses zentrale Objekt verbindet andere Fähigkeitsobjekte mit der Außenwelt und umgekehrt; Es trifft auch alle wichtigen Entscheidungen.
Es kann beliebig viele Zustände geben. Im Wesentlichen unterscheidet sich der Zustand in dieser Iteration nicht wesentlich von den aktiven/inaktiven Zuständen in der alten Version, aber jetzt kann es eine beliebige Anzahl davon geben, und ihr Zweck ist abstrakter geworden. Wir werden darauf hinweisen, dass eine Fähigkeit jeweils nur einen aktiven Zustand haben kann.
Die wesentliche Neuerung im Vergleich zum alten System waren die Komponenten: Eine Komponente beschreibt eine Aktion, und jeder Zustand kann beliebig viele Komponenten haben.
Wie funktionieren die neuen Fähigkeiten? Eine Fähigkeit kann nur in einem der Zustände vorhanden sein; Das Hauptobjekt ist für die Umschaltung verantwortlich. Die mit einem Zustand verknüpften Komponenten reagieren auf die Aktivierung/Deaktivierung des Zustands und können abhängig davon entweder mit der Ausführung einer Aktion beginnen oder diese nicht mehr ausführen.
Alle Objekte sind anpassbar; Ein Spieleentwickler kann Zustände und Komponenten nach Belieben mischen und so aus vorinstallierten Blöcken eine neue Fähigkeit zusammenstellen. Jetzt müssen Programmierer nur noch das Bild eingeben, um eine neue Komponente oder einen neuen Zustand zu erstellen, was das Schreiben von Code viel einfacher macht: Sie arbeiten mit kleinen Entitäten, beschreiben einige einfache Elemente und bauen die Fähigkeit nicht mehr selbst auf – das erledigen jetzt die Spieleentwickler.
Der Ablauf ist so geworden:
Anschließend wird dieser Vorgang immer wieder wiederholt. Aus Gründen der Benutzerfreundlichkeit dient ein Status nicht nur als Komponentencontainer, sondern bestimmt auch, wann in einen anderen Status gewechselt werden soll, und fordert das Hauptobjekt auf, den Wechsel vorzunehmen. Im Laufe der Zeit stellte sich heraus, dass uns dies immer noch nicht ausreichte, und das Fähigkeitsdiagramm wurde wie folgt umgewandelt:
Das Hauptobjekt, der Zustand und die Komponenten blieben an ihrem Platz, aber es wurden auch neue Elemente hinzugefügt.
Das erste, was Ihnen ins Auge fällt, ist, dass wir jedem Staat und jeder Komponente Bedingungen hinzugefügt haben: Für Staaten definieren diese zusätzliche Anforderungen für das Verlassen des Staates; Bei Komponenten bestimmen sie, ob die Komponente ihre Aktion ausführen kann.
Der Ladungscontainer enthält Ladungen, lädt sie wieder auf, stoppt bei Bedarf den Ladevorgang und stellt Ladungen zur Nutzung durch Staaten bereit.
Ein Timer wird verwendet, wenn mehrere Zustände eine gemeinsame Ausführungszeit haben müssen, ihre eigene Ausführungszeit jedoch nicht definiert ist.
Es ist wichtig zu beachten, dass alle Fähigkeitsobjekte optional sind. Technisch gesehen sind für eine Arbeitsfähigkeit nur ein Hauptobjekt und ein Zustand erforderlich.
Obwohl es nicht mehr so viele Fähigkeiten gibt, die vollständig ohne die Beteiligung von Programmierern erstellt werden, ist die Entwicklung im Allgemeinen merklich kostengünstiger geworden, da Programmierer jetzt nur noch sehr kleine Dinge schreiben müssen: zum Beispiel einen neuen Zustand oder zwei Komponenten – den Rest wird wiederverwendet.
Fassen wir die Komponenten unserer Fähigkeiten zusammen:
Das Hauptobjekt übernimmt die Funktionen einer Zustandsmaschine. Es versorgt Zustände und Komponenten mit Informationen über die Welt und versorgt die Welt mit Informationen über Fähigkeiten. Das Hauptobjekt dient als Verbindung zwischen Zuständen, Komponenten und Serviceteilen der Fähigkeit: Gebühren und externe Timer.
Der Zustand hört auf Aktivierungs- und Deaktivierungsbefehle vom Hauptobjekt und aktiviert und deaktiviert dementsprechend Komponenten und fordert das Hauptobjekt außerdem auf, in einen anderen Zustand zu wechseln. Der Staat bestimmt, wann zum nächsten gewechselt werden muss. Dazu nutzt es seine interne Bedingung: ob der Spieler auf die Fähigkeitsschaltfläche geklickt hat, ob seit der Aktivierung des Zustands eine bestimmte Zeit vergangen ist usw. sowie externe Bedingungen, die mit dem Zustand verknüpft sind.
Die Komponente hört auf Aktivierungs- und Deaktivierungsbefehle vom Staat und führt eine Aktion aus: diskret oder langfristig. Aktionen können völlig unterschiedlich sein: Sie können Schaden verursachen, einen Verbündeten heilen, eine Animation einschalten und so weiter.
Die Bedingung prüft, in welchem Zustand sich das gewünschte Element befindet und meldet dies an den Zustand bzw. die Komponente. Die Bedingungen können komplex sein. Ein Staat fordert keinen Übergang in einen anderen Staat, wenn die Bedingung nicht erfüllt ist. Die Komponente führt auch keine Aktion aus, wenn die Bedingung nicht erfüllt ist. Bedingungen sind eine optionale Entität; nicht jede Fähigkeit hat sie.
Der Ladungscontainer speichert Ladungen, lädt sie wieder auf, stoppt den Ladevorgang bei Bedarf und stellt Ladungen an Staaten bereit. Es wird bei Mehrfachladungsfähigkeiten verwendet, wenn Sie dem Spieler erlauben müssen, es mehrmals, jedoch nicht mehr als n- mal hintereinander, zu verwenden.
Der Timer wird verwendet, wenn mehrere Zustände eine gemeinsame Dauer haben, aber nicht bekannt ist, wie lange jeder von ihnen andauern wird. Jeder Staat kann einen Timer für n Sekunden starten. Alle relevanten Staaten abonnieren das Timer-Ende-Ereignis und unternehmen etwas, wenn es endet.
Kehren wir nun zum Fähigkeitsdiagramm zurück. Wie hat sich seine Funktionalität verändert?
Staaten können Gebühren als zusätzliche Übergangsbedingung verwenden. Tritt ein solcher Übergang auf, verringert sich die Anzahl der Ladungen. Staaten können auch einen gemeinsamen Timer verwenden. In diesem Fall wird die Gesamtzeit für ihre Ausführung durch einen Timer bestimmt und jeder Zustand kann einzeln jederzeit andauern.
Wir haben das Rad für die neuen Fähigkeiten-Benutzeroberflächen nicht völlig neu erfunden.
Das Hauptobjekt verfügt über eine eigene Benutzeroberfläche. Es definiert einige Elemente, die immer in der Benutzeroberfläche vorhanden sein sollten und nicht vom aktuell aktiven Status abhängen.
Jeder Zustand hat sein eigenes Paar in der Benutzeroberfläche und die Zustands-Benutzeroberfläche wird nur angezeigt, wenn sein Zustand aktiv ist. Es empfängt Daten über seinen Zustand und kann diese auf die eine oder andere Weise anzeigen. Beispielsweise verfügen Dauerstatus in der Regel über eine Leiste und einen Text in ihrer Benutzeroberfläche, der die verbleibende Zeit anzeigt.
In einem Fall, in dem der Staat auf einen externen Befehl wartet, um eine Fähigkeit fortzusetzen, zeigt seine Benutzeroberfläche eine Schaltfläche an und durch Drücken wird der Befehl an den Staat gesendet.
Wir werden uns anhand konkreter Beispiele ansehen, wie die Fähigkeiten funktionieren. Schauen wir uns zunächst einen Roboter namens „Inquisitor“ an. Wir haben vier Zustände, die aufeinander folgen – über den Zuständen können Sie deren Anzeige in der Benutzeroberfläche sehen. Bei zwei davon sehen wir auch die dazugehörigen Komponenten; Die anderen beiden Staaten haben einfach keine Komponenten.
Hier ist der Ablauf der Fähigkeit:
Alles beginnt mit dem Zustand „WaitForClick“. Zu diesem Zeitpunkt bewirkt die Fähigkeit nichts; es wartet nur auf Befehle.
Sobald ein solcher Befehl empfangen wird, wechselt das Hauptobjekt den Zustand. Der nächste aktive Zustand ist „WaitForGrounded“.
Dieser Zustand hat einige Komponenten und daher springt der Roboter, wenn er aktiviert wird, und spielt einen Ton und eine Animation ab. Während der Zustand aktiv ist, ist der Roboter unter anderem vom Jammer-Effekt betroffen, der das Zielen auf den Roboter verbietet.
Wenn der Roboter landet, wechselt seine Fähigkeit in den nächsten Zustand.
Dieser Zustand besteht aus drei Komponenten: dem bereits bekannten Sound und Jammer sowie Shake, das bei allen Spielern im Umkreis von n zum Wackeln der Kamera führt.
Da dieser Zustand eine Dauer hat, funktioniert er n Sekunden lang, dann geht die Fähigkeit in den nächsten Zustand über.
Der letzte Zustand verfügt ebenfalls über eine Dauer, enthält jedoch keine Komponenten: Er hat eine normale Abklingzeit.
Nach Abschluss kehrt die Fähigkeit in den ersten Zustand zurück.
Ein weiteres Beispiel ist „Phantom“. Es ist Inquisitor sehr ähnlich, aber es gibt einige Nuancen:
Wir beginnen mit WaitForClick.
Dann werden die Dauer, in der der Teleport installiert wird, die Statistiken des Mechs geändert und Sound und Animation abgespielt.
Danach: DurationOrClick, in dem die Statistiken des Mechs geändert werden, Animationen und Effekte abgespielt werden.
Wenn ein Klick gemacht wurde, gehen wir zu einer anderen Dauer, in der sich der Mech teleportiert, Statistiken ändern und Animationen, Effekte und Sounds abgespielt werden.
Nach diesem Zustand (oder nachdem die Zeit für „DurationOrClick“ abgelaufen ist) wechseln wir zu „Duration“.
Der Hauptunterschied besteht darin, dass wir Zustände mit Verzweigungen sehen: DurationOrClick wechselt zu Zustand A, wenn die angegebene Zeit verstrichen ist, oder zu Zustand B, wenn der Spieler zuvor die Fähigkeitstaste gedrückt hat.
Obwohl es den Anschein hat, dass sich unser System von etwas Einfachem zu etwas ziemlich Komplexem entwickelt hat, hat diese Änderung das Leben sowohl von Programmierern als auch von Spieledesignern vereinfacht. Die Unterstützung der Programmierer wird nun vor allem beim Hinzufügen kleinerer Komponenten benötigt, während die letztgenannte Gruppe von Teammitgliedern größere Autonomie erlangt hat und nun selbständig neue Fähigkeiten aus vorhandenen Zuständen und Komponenten zusammenstellen kann. Als weiteren Bonus erhielten die Spieler gleichzeitig auch einen Gewinn in Form vielfältigerer und komplexerer Fähigkeiten der Mechs.