Wie implementiere ich Verhalten in einer komponentenbasierten Spielearchitektur?

21

Ich fange an, Spieler- und Feind-KI in einem Spiel zu implementieren, bin jedoch verwirrt, wie dies am besten in einer komponentenbasierten Spielarchitektur implementiert werden kann.

Angenommen, ich habe einen folgenden Spielercharakter, der stationär sein, ein Schwert führen und schwingen kann. Ein Spieler kann sowohl aus dem stationären als auch aus dem laufenden Zustand in den Schwungschwertzustand übergehen, aber dann muss der Schwung abgeschlossen sein, bevor der Spieler wieder stehen oder herumlaufen kann. Während des Schwungs kann der Spieler nicht herumlaufen.

Aus meiner Sicht habe ich zwei Implementierungsansätze:

  • Erstellen Sie eine einzelne KI-Komponente mit der gesamten Player-Logik (entweder von der eigentlichen Komponente abgekoppelt oder als PlayerAIComponent eingebettet). Ich kann auf einfache Weise die staatlichen Beschränkungen durchsetzen, ohne eine Kopplung zwischen den einzelnen Komponenten herzustellen, aus denen sich die Player-Entität zusammensetzt. Die AI-Komponente kann jedoch nicht aufgelöst werden. Wenn ich zum Beispiel einen Feind habe, der nur stehen und herumlaufen kann oder nur herumläuft und gelegentlich ein Schwert schwingt, muss ich neue KI-Komponenten erschaffen.
  • Teilen Sie das Verhalten in Komponenten auf, von denen jede einen bestimmten Status angibt. Ich bekomme dann eine StandComponent, WalkComponent und SwingComponent. Um die Übergangsregeln durchzusetzen, muss ich jede Komponente koppeln. SwingComponent muss StandComponent und WalkComponent für die Dauer des Swings deaktivieren. Wenn ich einen Gegner habe, der nur herumsteht und gelegentlich ein Schwert schwingt, muss ich sicherstellen, dass SwingComponent WalkComponent nur deaktiviert, wenn es vorhanden ist. Dies ermöglicht zwar ein besseres Mischen und Anpassen von Komponenten, kann jedoch zu einem Albtraum der Wartbarkeit führen, da die vorhandenen Komponenten jedes Mal aktualisiert werden müssen, wenn eine Abhängigkeit hinzugefügt wird, um den neuen Anforderungen gerecht zu werden, die die Abhängigkeit an den Charakter stellt.

Die ideale Situation wäre, dass ein Designer neue Feinde / Spieler aufbauen kann, indem er Komponenten in einen Container zieht, ohne eine einzelne Zeile des Engine- oder Skriptcodes berühren zu müssen. Obwohl ich nicht sicher bin, ob eine Skriptcodierung vermieden werden kann, möchte ich sie so einfach wie möglich halten.

Alles in allem: Soll ich die gesamte KI-Logik in eine Komponente unterteilen oder jeden Logikzustand in separate Komponenten aufteilen, um Entitätsvarianten einfacher zu erstellen?

edit : Ich vermute, es gibt einige Verwirrung darüber, was ich mit der ersten und zweiten Situation gemeint habe. Ich habe versucht, es in der folgenden Abbildung zu erklären.

Komponentendiagramm

Beachten Sie die Beziehung zwischen den einzelnen Staaten und der Entität. In der ersten Situation wird eine AI-Komponente vorab erstellt, bevor sie in die Entität eingefügt wird. Ein Designer kann nur aus einem bestimmten Satz von AIComponents auswählen, die vom Programmierer zur Verfügung gestellt werden. Die zweite Situation hat die verschiedenen Zustände auf der gleichen Ebene wie andere Komponenten. Ein Designer kann jetzt eine Entität mit eindeutiger KI erstellen, ohne dass ein Programmierer eingreift.

Die Frage ist, ob dies die einzigen beiden Optionen für die Strukturierung von KI in einer komponentenbasierten Entität sind. Wenn ja, welche bieten die maximale Flexibilität?

Geist
quelle
Ich denke, eine gute Antwort hängt davon ab, wo Sie die Exklusivität von Handlungen durchsetzen möchten. Wenn Sie möchten, dass es sich in den Objekten selbst befindet, ist das Design im Vergleich zu etwa der Erzwingung durch die Drag-and-Drop-Schnittstelle sehr unterschiedlich einen zeitbasierten Endzustand usw. oder was auch immer).
James
2 ist keine praktikable Option.
Kojote

Antworten:

6

Wenn Sie mehr mögliche Gegner oder Spieler haben möchten, die Sie sich derzeit nicht vorstellen können, sollten Sie dies definitiv auflösen. Was Sie in Ihrem zweiten Punkt beschreiben, ist im Grunde das Zustandsmuster .

Ich glaube, ich stimme Gregory zu, dass Sie keine separaten Stand- und Gehzustandskomponenten haben sollten. Es ist nur eine Bewegungskomponente mit Geschwindigkeit 0. Wenn Sie dagegen Objekte haben, die sich nicht bewegen können, müssen Sie diese entweder aufteilen oder einfach eine Art boolesche Einschränkung in den Bewegungszustand einfügen, die verhindert, dass die Geschwindigkeit nicht Null ist .

Für den Spieler glaube ich nicht, dass es völlig getrennt sein muss. Es können weiterhin alle anderen Komponenten verwendet werden, wobei eine Eingabekomponente hinzugefügt wird. Diese Komponente steuert die Übergänge zwischen Zuständen, während sie im Feind von einer Standard-KI gesteuert wird, oder wenn Sie möchten, von verschiedenen KI-Unterklassen, aus denen Ihre feindlichen Konstrukteure auswählen können.

Bearbeiten: Geben Sie Ihren stationären Gegnern, anstatt die Bewegungskomponente einzuschränken, einfach eine stationäre KI-Komponente, die sie niemals bewegt.

Tesserex
quelle
Bedeutet ein Zustandsmuster nicht die erste Situation? Dies führt dazu, dass eine einzelne AIComponent eine Zustandsmaschine implementiert, die verschiedene Zustandsobjekte enthält. Was ich mit der zweiten Option meinte, war, dass WalkComponent und SwingComponent vom selben Typ sind wie beispielsweise RenderComponent und PhysicsComponent.
Geist
@ghostonline Soweit die Idee geht, irgendwie. In der Umsetzung nicht wirklich. AIComponent wäre wie im zweiten Diagramm getrennt. Es würde nicht die anderen Komponenten enthalten. Die wichtigere Frage für Ihre zweite Situation ist: Wenn der Designer nur Komponenten ohne Programmierer auswählt, woher weiß die Entität, wann der Status geändert werden muss? Unterschiedliche Zustände bedeuten unterschiedliche Zustandsübergänge - jemand muss diese noch angeben.
Tesserex
Meinen Sie damit, der Entität in Diagramm 2 eine AIComponent hinzuzufügen, die die Stand / Walk / Swing-Komponente steuert? Meine Idee war, dass die Komponenten unter bestimmten Bedingungen Block- oder Aktivierungssignale senden. Zum Beispiel würde SwingComponent generische Signale ausgeben, zum Beispiel "bound_feet" -Signal beim Starten und "release_feet" beim Beenden des Swings. WalkComponent würde sich basierend auf diesen Signalen selbst deaktivieren und aktivieren. Da die Zustandsübergänge in den Komponenten selbst gekapselt sind, benötigt der Entwickler keinen Programmierer, der die Komponenten miteinander verbindet.
Geist
@ghostonline Das funktioniert gut für Dinge, die feste Regeln haben, wie "beim Schwingen nicht laufen können", aber was ist mit Übergängen zwischen Stehen und Gehen? Wenn das Stehen die Kontrolle hat, woher weiß es, dass es versuchen soll, zu gehen? Die Stehlogik möchte entweder gehen oder schwingen, was durch das völlige Fehlen einer Gehfähigkeit beeinflusst wird. In diesem Fall sollte sie immer schwingen. Aber ich denke, Sie sind auf dem richtigen Weg.
Tesserex
2

Ich würde zumindest die Player-KI (oder was ich als Player-Controller bezeichnen würde) als eigene Komponente beibehalten. Bei den meisten Spielen unterscheidet sich der Spieler grundlegend genug von den NPCs, dass Sie nur in Grundlagen wie Trefferpunkten von einem zum anderen generalisieren können.

Für NPCs sehe ich StandComponent und WalkComponent als Aspekte derselben Sache. Wirst du jemals eine WalkComponent ohne StandComponent haben? Ich bezweifle das. Ebenso wäre eine RunComponent nur eine WalkComponent mit einer höheren Geschwindigkeit und unterschiedlichen Animationen. Ich kann den Wert erkennen, wenn ich eine NPCMovementComponent und eine separate NPCSwordFighterComponent habe, aber selbst das scheint mir ein Überentwicklungsprozess zu sein.

Gregory Avery-Weir
quelle
Ich würde die NPC-Bewegung und die Spielerbewegung nicht so stark voneinander trennen. Die Bewegungsaktionen, die die Animationen und die Physik antreiben, könnten definitiv geteilt werden; es ist das, was die Aktionen oder Übergänge auswählt, die unterschiedlich sind (der Spieler nimmt Eingaben vor, während AI ... AI ist). Ich bin damit einverstanden, dass Sie einen PlayerController, aber auch einen AIController haben. Beide könnten Bewegungskomponenten / Swing-Komponenten verwenden, um die eigentliche Animation / Physik-Arbeit zu erledigen.
Homebrew
Wahr. Ich gehe davon aus, dass alle sich bewegenden Objekte eine PhysicsComponent oder MovementComponent haben, die ihre Bewegung handhabt, und dass der PlayerController und der AIController dies verwenden würden, um die Bewegung zu handhaben. Bewegung sollte definitiv eine separate Komponente sein, da es Dinge geben kann, die bewegt werden müssen, die keine KI oder eine möglichst einfache KI haben (dumme physikalische Objekte wie Kisten oder Müll).
Gregory Avery-Weir
2

Zuerst würde ich eine Zustandskomponente erstellen und dann eine Zustandsmaschine, um die Übergänge zu handhaben. Machen Sie es so allgemein, dass Sie es für Ihre Spieler und Ihre KI verwenden können. Dies stellt sicher, dass die KI nach den gleichen Regeln spielt und dass Sie Ihre Logik nicht ändern müssen, wenn Sie die Funktionsweise der Player-Zustände im Vergleich zu den KI-Zuständen ändern.

Finite State Machine C ++

Das obige Beispiel zeigt ein konkretes Beispiel für eine Zustandsmaschine in c ++, die von Spielern und KI gleichermaßen verwendet werden kann.

Kyle C
quelle
1

Was Sie wollen, ist eine Komponente, die die Bewegung von Charakteren (Spieler und NPCs) handhabt. Die KI-Komponente oder eine Spielerkomponente sendet Befehle an diese Bewegungskomponente und prüft, ob die Aktion ausgelöst werden kann. Dadurch werden Ihre Bewegungseinschränkungen in einer einzigen Komponente zusammengefasst. Ihr KI-Code und der Spielercode müssen nicht wissen, wie das Swing-Schwert ausgeführt wird. Die KI würde interne Zustände haben, z. B. Leerlauf, Angriff, Flucht.

Stephen
quelle
1
TYPO: "Es wird ..." was von der KI-Komponente?
Welpe