Maschinen mit endlichen Zuständen Kadiermuster mit Lenkverhalten

Maschinen mit endlichen Zuständen und Lenkverhalten passen perfekt zusammen: Aufgrund ihrer dynamischen Beschaffenheit können einfache Zustände und Kräfte kombiniert werden, um komplexe Verhaltensmuster zu erzeugen. In diesem Lernprogramm erfahren Sie, wie Sie a codieren Kader Muster mit einer stapelbasierten Finite-State-Maschine in Kombination mit Lenkverhalten.

Alle FSM-Icons von Lorc und verfügbar auf http://game-icons.net. Assets in der Demo: Top / Down Shoot 'Em Up Spritesheet von Takomogames und Alien Breed (esque) Top-Down Tilesheet von SpicyPixel.

Hinweis: Obwohl dieses Tutorial mit AS3 und Flash geschrieben wurde, sollten Sie in der Lage sein, in fast jeder Spieleentwicklungsumgebung dieselben Techniken und Konzepte anzuwenden.


Endergebnis

Nach Abschluss dieses Tutorials können Sie ein Kader-Muster implementieren, in dem eine Gruppe von Soldaten dem Anführer folgen, Feinde jagen und Gegenstände plündern:


Squad-Pattern mit stapelbasiertem FSM und Steuerungsverhalten implementiert. Bewegen Sie den Mauszeiger, um den Anführer zu führen, und klicken Sie, um zu schießen.

Stapelbasiertes FSM und Lenkverhalten kombinieren

Das vorherige Tutorial über Maschinen mit endlichen Zuständen beschreibt, wie nützlich sie für die Implementierung von Logik für künstliche Intelligenz sind: Anstatt einen sehr komplexen Stapel von KI-Code zu schreiben, kann die Logik auf eine Reihe einfacher Zustände verteilt werden, von denen jeder einzelne sehr spezifische Aufgaben ausführt, wie z Flucht vor einem Feind.

Die Kombination der Zustände führt zu einer hochentwickelten KI, die jedoch leicht zu verstehen, zu optimieren und zu warten ist. Diese Struktur ist auch eine der Säulen des Lenkverhaltens: die Kombination einfacher Kräfte, um komplexe Muster zu erzeugen.

Deshalb bilden FSMs und Lenkverhalten eine großartige Kombination. Die Zustände können verwendet werden, um zu steuern, welche Kräfte auf einen Charakter wirken, und die bereits mächtigen Muster, die durch Lenkverhalten erzeugt werden können, werden verbessert.


Steuern von Verhalten mithilfe eines stapelbasierten FSM

Um alle Verhaltensweisen zu organisieren, werden sie über die Staaten verteilt. Jeder Staat erzeugt eine bestimmte Verhaltenskraft oder eine Gruppe von ihnen, wie Suchen, Fliehen und Kollisionsvermeidung.

Wenn ein bestimmter Zustand aktiv ist, wird nur die resultierende Kraft auf den Charakter angewendet, sodass er sich entsprechend verhält. Zum Beispiel, wenn der derzeit aktive Status ist weglaufen und seine Kräfte sind eine Kombination aus fliehen und Kollisionsvermeidung, Der Charakter wird einen Ort fliehen und dabei Hindernissen ausweichen.

Die Lenkkräfte werden bei jedem Spielupdate berechnet und zum Geschwindigkeitsvektor des Charakters addiert. Wenn sich der aktive Zustand (und damit auch das Bewegungsmuster) ändert, wechselt der Charakter problemlos zu dem neuen Muster, wenn die neuen Kräfte nach jeder Aktualisierung hinzugefügt werden.

Die dynamische Natur des Lenkverhaltens gewährleistet diesen fließenden Übergang. Die Zustände koordinieren lediglich, welche Lenkkräfte zu einem bestimmten Zeitpunkt aktiv sind.


Die Code-Struktur

Die Struktur zum Implementieren eines Kader-Musters kapselt FSMs und Steuerungsverhalten in Eigenschaften einer Klasse. Für jede Klasse, die eine Einheit darstellt, die sich bewegt oder auf andere Weise durch Lenkkräfte beeinflusst wird, wird eine Eigenschaft aufgerufen boid, Das ist ein Beispiel für die Boid Klasse:

öffentliche Klasse Boid öffentliche var Position: Vector3D; public var Geschwindigkeit: Vector3D; öffentliche Var-Steuerung: Vector3D; öffentliche Var-Masse: Anzahl; Öffentliche Funktion suchen (Ziel: Vector3D, slowingRadius: Number = 0): Vector3D (…) öffentliche Funktion fliehen (Position: Vector3D): Vector3D (…) Aktualisierung der öffentlichen Funktion (): void (…) (… )

Das Boid Klasse wurde in der Lenkverhaltensreihe verwendet und bietet Eigenschaften als Geschwindigkeit und Position (beide mathematischen Vektoren) zusammen mit Methoden zum Hinzufügen von Lenkkräften, wie z suchen(), fliehen(), usw.

Eine Entität, die eine stapelbasierte FSM verwendet, hat dieselbe Struktur wie Ameise Klasse aus dem vorherigen FSM-Lernprogramm: Der stapelbasierte FSM wird vom verwaltet Gehirn Eigenschaft und jeder Zustand wird als Methode implementiert.

Unten ist der Soldat Klasse, die Lenkverhalten und FSM-Fähigkeiten besitzt:

öffentliche Klasse Soldat private var brain: StackFSM; // Steuert den FSM-Kram private var boid: Boid; // Steuert das Lenkverhalten öffentliche Funktion Soldat (posX: Number, posY: Number, totalMass: Number) (…) brain = new StackFSM (); // Drücke den "follow" -Zustand, damit der Soldat dem Anführer folgen wird brain.pushState (follow);  public function update (): void // Aktualisieren Sie das Gehirn. Es wird die aktuelle Statusfunktion ausgeführt. brain.update (); // Update des Lenkverhaltens boid.update (); 

Planung des "Gehirns"

Das Kadermuster wird unter Verwendung einer stapelbasierten Zustandsmaschine implementiert. Die Soldaten, die Mitglieder des Trupps sind, folgen dem (vom Spieler kontrollierten) Anführer und jagen alle in der Nähe befindlichen Feinde ab.

Wenn ein Feind stirbt, kann er einen Gegenstand fallen lassen, der gut oder schlecht sein kann (a Medkit oder ein Badkit, beziehungsweise). Ein Soldat unterbricht die Kaderformation und sammelt in der Nähe befindliche Gegenstände oder weicht dem Ort aus, um schlechte Gegenstände zu vermeiden.

Nachfolgend finden Sie eine grafische Darstellung des stapelbasierten FSM, der das "Gehirn" des Soldaten steuert:


FSM, die das Gehirn eines Soldaten darstellt.

In den nächsten Abschnitten wird die Implementierung jedes Staates vorgestellt. Alle Code-Snippets in diesem Lernprogramm beschreiben die Grundidee jedes Schritts und lassen dabei alle Details bezüglich der verwendeten Game-Engine aus (in diesem Fall Flixel)..


Dem Führer folgen

Der erste zu implementierende Zustand ist derjenige, der fast immer aktiv bleibt: folgen sie den Anführer. Der looting Teil wird später implementiert, also vorerst der Folgen state wird den Soldaten nur dazu bringen, dem Anführer zu folgen und den aktuellen Status auf zu setzen jagen Wenn es einen Feind in der Nähe gibt:

öffentliche Funktion follow (): void var aLeader: Boid = Game.instance.boids [0]; // einen Zeiger auf den Leader erhalten addSteeringForce (boid.followLeader (aLeader)); // folge dem Anführer // Gibt es ein Monster in der Nähe? if (getNearestEnemy ()! = null) // Ja, das gibt es! Jage es nieder! // Drücke den "Jagd" -Zustand. Der Soldat wird damit aufhören, dem Anführer zu folgen und // das Monster zu jagen. brain.pushState (Jagd);  private Funktion getNearestEnemy (): Monster // Hier wird die Implementierung ausgeführt, um den nächsten Feind zu erhalten.

Trotz des Vorhandenseins von Feinden wird der Staat, solange er aktiv ist, immer eine Kraft erzeugen, die dem Anführer folgt, indem er das Verhalten des Anführers verwendet.

Ob getNearestEnemy () Gibt etwas zurück, bedeutet das, dass ein Feind in der Nähe ist. In diesem Fall die jagen Zustand wird durch den Aufruf in den Stapel verschoben brain.pushState (Jagd), Der Soldat hört auf, dem Anführer zu folgen und beginnt, Feinde zu jagen.

Vorerst die Umsetzung der jagen() Staat kann sich einfach vom Stapel stürzen, so dass die Soldaten nicht im Jagdstaat stecken bleiben:

public function hunt (): void // Im Moment wollen wir nur den hunt () -Zustand aus dem Gehirn ziehen. brain.popState (); 

Beachten Sie, dass keine Informationen an die weitergegeben werden jagen Zustand, wie z Wer ist der nächste Feind. Diese Informationen müssen von der Kommission gesammelt werden jagen Zustand selbst, weil es bestimmt, ob die jagen sollte aktiv bleiben oder sich vom Stapel entfernen (das Steuerelement in den Speicher zurückgeben Folgen Zustand).

Das Ergebnis ist bisher ein Trupp Soldaten nach dem Anführer (beachten Sie, dass die Soldaten nicht wegen der Jagd auf Jagd gehen werden) jagen() Methode knallt sich gerade):


Kader-Muster mit "Folgen" und nicht funktionierenden "Jagd" -Zuständen. Bewegen Sie den Mauszeiger, um den Anführer zu führen, und klicken Sie, um zu schießen.

Spitze: Jeder Staat sollte dafür verantwortlich sein, seine Existenz zu beenden, indem er sich vom Stapel stürzt.


Formation brechen und jagen

Der nächste zu implementierende Zustand ist jagen, das wird Soldaten dazu bringen, jeden Feind in der Nähe zu jagen. Der Code für jagen() ist:

public function hunt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Haben wir ein Monster in der Nähe? if (aNearestEnemy! = null) // Ja, das tun wir. Lassen Sie uns berechnen, wie weit es ist. var aDistance: Number = berechneDistance (aNearestEnemy, this); // Ist das Monster nahe genug, um zu schießen? if (aDistance <= 80)  // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot();  else  // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation());   else  // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState();  

Der Zustand beginnt mit der Zuweisung aNähsterEnemie mit dem nächsten Feind. Ob aNähsterEnemie ist Null es bedeutet, dass es keinen Feind gibt, also muss der Staat aufhören. Der Anruf brain.popState () knallt die jagen State, den Soldaten auf den nächsten Status im Stack umschalten.

Ob aNähsterEnemie ist nicht Null, es bedeutet, dass ein Feind gejagt werden muss und der Staat aktiv bleiben sollte. Der Jagdalgorithmus basiert auf der Entfernung zwischen dem Soldaten und dem Feind: Wenn die Entfernung größer als 80 ist, sucht der Soldat die Position des Feindes. Wenn der Abstand weniger als 80 beträgt, wird der Soldat dem Feind gegenüberstehen und im Stillstand schießen.

Schon seit jagen() Wird bei jedem Spielupdate aufgerufen, wenn ein Gegner in der Nähe ist, wird der Soldat diesen Gegner suchen oder erschießen. Die Entscheidung, sich zu bewegen oder zu schießen, wird dynamisch durch die Entfernung zwischen dem Soldaten und dem Feind gesteuert.

Das Ergebnis ist ein Trupp Soldaten, die in der Lage sind, dem Anführer zu folgen und in der Nähe befindliche Feinde zu jagen:


Kadermuster mit "folgen" und "jagen". Bewegen Sie den Mauszeiger, um den Anführer zu führen, und klicken Sie, um zu schießen.

Plündern und weglaufen

Jedes Mal, wenn ein Gegner getötet wird, kann er einen Gegenstand fallen lassen. Der Soldat muss den Gegenstand abholen, wenn er gut ist, oder den Gegenstand fliehen, wenn er schlecht ist. Dieses Verhalten wird in der zuvor beschriebenen FSM durch zwei Zustände dargestellt:


FSM, der das hervorhebt CollectItem und weglaufen Zustände.

Das CollectItem Der Staat wird einen Soldaten dazu bringen, an dem abgelegten Gegenstand anzukommen, während der weglaufen Der Staat wird den Soldaten dazu bringen, sich vor dem Ort des schlechten Gegenstandes zu fliehen. Beide Zustände sind nahezu identisch, der einzige Unterschied ist die Ankunfts- oder Fliehkraft:

öffentliche Funktion runAway (): void var aItem: Item = getNearestItem (); if (aItem! = null && aItem.alive && aItem.type == Item.BADKIT) var aItemPos: Vector3D = new Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.flee (aItemPos));  else brain.popState ();  public function collectItem (): void var aItem: Item = getNearestItem (); if (aItem! = null && aItem.alive && aItem.type == Item.MEDKIT) var aItemPos: Vector3D = new Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.arrive (aItemPos, 50));  else brain.popState ();  private Funktion getNearestItem (): Item // Hier wird der Code verwendet, um das nächstgelegene Element zu erhalten.

Hier ist eine Optimierung der Übergänge hilfreich. Der Code für den Übergang von der Folgen Zustand zum CollectItem oder der weglaufen Status ist derselbe: Überprüfen Sie, ob sich ein Artikel in der Nähe befindet, und drücken Sie dann den neuen Status.

Der zu schiebende Status hängt vom Typ des Elements ab. Als Konsequenz der Übergang zu CollectItem oder weglaufen kann als einzelne Methode implementiert werden checkItemsNearby ():

private Funktion checkItemsNearby (): void var aItem: Item = getNearestItem (); if (aItem! = null) brain.pushState (aItem.type == Item.BADKIT? runAway: collectItem); 

Diese Methode prüft den nächstgelegenen Artikel. Wenn es eine gute ist, die CollectItem Zustand wird ins Gehirn geschoben; wenn es eine schlechte ist, die weglaufen Zustand ist gedrückt. Wenn kein Gegenstand zu sammeln ist, führt die Methode nichts aus.

Diese Optimierung erlaubt die Verwendung von checkItemsNearby () den Übergang von einem beliebigen Zustand zu steuern CollectItem oder weglaufen. Nach Angaben des Soldaten-FSM gibt es diesen Übergang in zwei Staaten: Folgen und jagen.

Ihre Implementierung kann leicht geändert werden, um den neuen Übergang zu berücksichtigen:

öffentliche Funktion follow (): void var aLeader: Boid = Game.instance.boids [0]; // einen Zeiger auf den Leader erhalten addSteeringForce (boid.followLeader (aLeader)); // folge dem Anführer // Überprüfe, ob es ein Objekt gibt, das abgeholt werden kann (oder weglaufen kann) checkItemsNearby (); // Gibt es ein Monster in der Nähe? if (getNearestEnemy ()! = null) // Ja, das gibt es! Jage es nieder! // Drücke den "Jagd" -Zustand. Der Soldat wird damit aufhören, dem Anführer zu folgen und // das Monster zu jagen. brain.pushState (Jagd);  public function hunt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Prüfen Sie, ob ein Element zum Sammeln vorhanden ist (oder davonlaufen kann) checkItemsNearby (); // Haben wir ein Monster in der Nähe? if (aNearestEnemy! = null) // Ja, das tun wir. Lassen Sie uns berechnen, wie weit es ist. var aDistance: Number = berechneDistance (aNearestEnemy, this); // Ist das Monster nahe genug, um zu schießen? if (aDistance <= 80)  // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot();  else  // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation());   else  // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState();  

Während er dem Anführer folgt, wird ein Soldat nach Gegenständen in der Nähe suchen. Wenn Sie einen Feind jagen, sucht ein Soldat auch nach Gegenständen in der Nähe.

Das Ergebnis ist die Demo unten. Beachten Sie, dass ein Soldat versucht, einen Gegenstand zu sammeln oder ihm auszuweichen, wenn sich ein Gegenstand in der Nähe befindet, auch wenn Gegner zu jagen und der Anführer zu folgen ist.


Squad-Muster mit "follow", "hunt", "collectItem" und "runAway". Bewegen Sie den Mauszeiger, um den Anführer zu führen, und klicken Sie, um zu schießen.

Staaten und Übergänge priorisieren

Ein wichtiger Aspekt in Bezug auf Zustände und Übergänge ist der Priorität unter ihnen. Abhängig von der Zeile, in der ein Übergang in die Implementierung eines Bundesstaates eingefügt wird, ändert sich die Priorität dieses Übergangs.

Verwendung der Folgen Zustand und der Übergang von checkItemsNearby () Sehen Sie sich als Beispiel die folgende Implementierung an:

öffentliche Funktion follow (): void var aLeader: Boid = Game.instance.boids [0]; // einen Zeiger auf den Leader erhalten addSteeringForce (boid.followLeader (aLeader)); // folge dem Anführer // Überprüfe, ob es ein Objekt gibt, das abgeholt werden kann (oder weglaufen kann) checkItemsNearby (); // Gibt es ein Monster in der Nähe? if (getNearestEnemy ()! = null) // Ja, das gibt es! Jage es nieder! // Drücke den "Jagd" -Zustand. Der Soldat wird damit aufhören, dem Anführer zu folgen und // das Monster zu jagen. brain.pushState (Jagd); 

Diese Version von Folgen() macht einen Soldaten zu wechseln CollectItem oder weglaufen Vor Überprüfen, ob ein Feind in der Nähe ist. Infolgedessen sammelt der Soldat einen Gegenstand (oder flieht vor ihm), selbst wenn sich Gegner in der Nähe befinden, die von der Polizei gejagt werden sollten jagen Zustand.

Hier ist eine weitere Implementierung:

öffentliche Funktion follow (): void var aLeader: Boid = Game.instance.boids [0]; // einen Zeiger auf den Leader erhalten addSteeringForce (boid.followLeader (aLeader)); // folge dem Anführer // Gibt es ein Monster in der Nähe? if (getNearestEnemy ()! = null) // Ja, das gibt es! Jage es nieder! // Drücke den "Jagd" -Zustand. Der Soldat wird damit aufhören, dem Anführer zu folgen und // das Monster zu jagen. brain.pushState (Jagd);  else // Prüfen Sie, ob ein Element zum Sammeln vorhanden ist (oder von dort weggelaufen ist) checkItemsNearby (); 

Diese Version von Folgen() macht einen Soldaten zu wechseln CollectItem oder weglaufen nur nach dem Er findet heraus, dass es keine Feinde gibt, die man töten kann.

Die aktuelle Implementierung von Folgen(), jagen() und collectItem () leiden an vorrangigen Fragen. Der Soldat wird versuchen, einen Gegenstand abzuholen, selbst wenn es wichtigere Dinge zu erledigen gibt. Um dies zu beheben, sind einige Verbesserungen erforderlich.

Hinsichtlich der Folgen Status kann der Code aktualisiert werden:

(folge () mit Prioritäten)

öffentliche Funktion follow (): void var aLeader: Boid = Game.instance.boids [0]; // einen Zeiger auf den Leader erhalten addSteeringForce (boid.followLeader (aLeader)); // folge dem Anführer // Gibt es ein Monster in der Nähe? if (getNearestEnemy ()! = null) // Ja, das gibt es! Jage es nieder! // Drücke den "Jagd" -Zustand. Der Soldat wird damit aufhören, dem Anführer zu folgen und // das Monster zu jagen. brain.pushState (Jagd);  else // Prüfen Sie, ob ein Element zum Sammeln vorhanden ist (oder von dort weggelaufen ist) checkItemsNearby (); 

Das jagen Zustand muss geändert werden in:

public function hunt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Haben wir ein Monster in der Nähe? if (aNearestEnemy! = null) // Ja, das tun wir. Lassen Sie uns berechnen, wie weit es ist. var aDistance: Number = berechneDistance (aNearestEnemy, this); // Ist das Monster nahe genug, um zu schießen? if (aDistance <= 80)  // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot();  else  // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation());   else  // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState(); // Check if there is an item to collect (or run away from) checkItemsNearby();  

Endlich, das CollectItem Der Staat muss geändert werden, um ein Plündern abzubrechen, wenn sich ein Feind in der Nähe befindet:

öffentliche Funktion collectItem (): void var aItem: Item = getNearestItem (); var aMonsterNearby: Boolean = getNearestEnemy ()! = null; if (! aMonsterNearby && aItem! = null && aItem.alive && aItem.type == Item.MEDKIT) var aItemPos: Vector3D = new Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.arrive (aItemPos, 50));  else brain.popState (); 

Das Ergebnis all dieser Änderungen ist die Demo von Beginn des Tutorials:


Squad-Pattern mit stapelbasiertem FSM und Steuerungsverhalten implementiert. Bewegen Sie den Mauszeiger, um den Anführer zu führen, und klicken Sie, um zu schießen.

Fazit

In diesem Tutorial haben Sie gelernt, wie Sie ein Kader-Muster programmieren, bei dem eine Gruppe Soldaten einem Anführer folgen, in der Nähe befindliche Feinde jagen und plündern. Die KI wird mithilfe eines stapelbasierten FSM in Kombination mit mehreren Lenkverhalten implementiert.

Wie sich gezeigt hat, sind Automaten mit endlichen Zuständen und Lenkverhalten eine starke Kombination und eine großartige Kombination. Durch die Verbreitung der Logik über die FSM-Zustände kann dynamisch ausgewählt werden, welche Lenkkräfte auf einen Charakter wirken, wodurch komplexe KI-Muster erzeugt werden können.

Kombinieren Sie das bereits bekannte Steuerungsverhalten mit FSMs und erstellen Sie neue und herausragende Muster!