Unity-KI-Entwicklung: Ein endliches Maschinen-Tutorial

Veröffentlicht: 2022-03-11

In der kompetitiven Gaming-Welt streben Entwickler danach, denjenigen, die mit den von uns erstellten Nicht-Spieler-Charakteren (NPCs) interagieren, ein unterhaltsames Benutzererlebnis zu bieten. Entwickler können diese Interaktivität bereitstellen, indem sie Finite-State-Maschinen (FSMs) verwenden, um KI-Lösungen zu erstellen, die Intelligenz in unseren NPCs simulieren.

KI-Trends haben sich zu Verhaltensbäumen verlagert, aber FSMs bleiben relevant. Sie sind – in der einen oder anderen Funktion – in praktisch jedes elektronische Spiel integriert.

Anatomie eines FSM

Ein FSM ist ein Berechnungsmodell, in dem nur einer einer endlichen Anzahl von hypothetischen Zuständen gleichzeitig aktiv sein kann. Ein FSM geht von einem Zustand in einen anderen über und reagiert auf Bedingungen oder Eingaben. Zu seinen Kernkomponenten gehören:

Komponente Beschreibung
Bundesland Eine aus einer endlichen Menge von Optionen, die den aktuellen Gesamtzustand einer FSM angibt; Jeder gegebene Zustand enthält einen zugeordneten Satz von Aktionen
Aktion Was ein Zustand tut, wenn der FSM ihn abfragt
Entscheidung Die Logik, die festlegt, wann ein Übergang stattfindet
Übergang Der Prozess der Zustandsänderung

Während wir uns auf FSMs aus der Perspektive der KI-Implementierung konzentrieren, fallen auch Konzepte wie Animationszustandsmaschinen und allgemeine Spielzustände unter das Dach von FSM.

Visualisierung eines FSM

Betrachten wir das Beispiel des klassischen Arcade-Spiels Pac-Man. Im Anfangszustand des Spiels (dem „Jagd“-Zustand) sind die NPCs farbenfrohe Geister, die den Spieler verfolgen und schließlich überholen. Die Geister wechseln in den Ausweichzustand, wenn der Spieler ein Power-Pellet isst und ein Power-Up erfährt, wodurch er die Fähigkeit erhält, die Geister zu essen. Die jetzt blauen Geister weichen dem Spieler aus, bis das Einschalten abläuft und die Geister in den Verfolgungszustand zurückkehren, in dem ihr ursprüngliches Verhalten und ihre ursprünglichen Farben wiederhergestellt werden.

Ein Pac-Man-Geist befindet sich immer in einem von zwei Zuständen: jagen oder ausweichen. Natürlich müssen wir zwei Übergänge bereitstellen – einen von der Verfolgung zum Ausweichen, den anderen vom Ausweichen zur Verfolgung:

Diagramm: Links ist der Chase-Zustand. Ein Pfeil (der anzeigt, dass der Spieler das Energiepellet gegessen hat) führt zum Ausweichzustand rechts. Ein zweiter Pfeil (der anzeigt, dass das Power-Pellet abgelaufen ist) führt zurück zum Chase-Zustand auf der linken Seite.
Übergänge zwischen Pac-Man-Geisterzuständen

Die Finite-State-Maschine fragt konstruktionsbedingt den aktuellen Zustand ab, der die Entscheidung(en) und Aktion(en) dieses Zustands abfragt. Das folgende Diagramm stellt unser Pac-Man-Beispiel dar und zeigt eine Entscheidung, die den Status des Einschaltens des Spielers überprüft. Wenn ein Power-Up begonnen hat, gehen die NPCs von der Jagd auf das Ausweichen über. Wenn ein Power-Up beendet ist, wechseln die NPCs von Ausweichen zu Jagen. Wenn es schließlich keine Einschaltänderung gibt, tritt kein Übergang auf.

Rautenförmiges Diagramm, das einen Zyklus darstellt: Links beginnend gibt es einen Chase-Zustand, der eine entsprechende Aktion impliziert. Der Verfolgungszustand zeigt dann nach oben, wo es eine Entscheidung gibt: Wenn der Spieler ein Power-Pellet gegessen hat, gehen wir weiter zum Ausweichzustand und weichen der Aktion rechts aus. Der Ausweichzustand weist auf eine Entscheidung ganz unten hin: Wenn das Energiepellet abgelaufen ist, gehen wir weiter zurück zu unserem Ausgangspunkt.
Komponenten des Pac-Man Ghost FSM

Skalierbarkeit

FSMs geben uns die Freiheit, modulare KI zu bauen. Beispielsweise können wir mit nur einer einzigen neuen Aktion einen NPC mit einem neuen Verhalten erstellen. So können wir einem unserer Pac-Man-Geister eine neue Aktion zuschreiben – das Essen eines Power-Pellets – und ihm die Fähigkeit geben, Power-Pellets zu essen, während er dem Spieler ausweicht. Wir können vorhandene Aktionen, Entscheidungen und Übergänge wiederverwenden, um dieses Verhalten zu unterstützen.

Da die Ressourcen, die für die Entwicklung eines einzigartigen NPCs erforderlich sind, minimal sind, sind wir gut positioniert, um die sich entwickelnden Projektanforderungen mehrerer einzigartiger NPCs zu erfüllen. Andererseits kann uns eine übermäßige Anzahl von Zuständen und Übergängen in eine Spaghetti-Zustandsmaschine verwickeln – eine FSM, deren Überfluss an Verbindungen das Debuggen und Warten erschwert.

Implementieren eines FSM in Unity

Um zu demonstrieren, wie ein endlicher Automat in Unity implementiert wird, erstellen wir ein einfaches Stealth-Spiel. Unsere Architektur wird ScriptableObject s enthalten, bei denen es sich um Datencontainer handelt, die Informationen in der gesamten Anwendung speichern und freigeben können, sodass wir sie nicht reproduzieren müssen. ScriptableObject s können nur eingeschränkt verarbeitet werden, wie z. B. das Aufrufen von Aktionen und das Abfragen von Entscheidungen. Neben der offiziellen Dokumentation von Unity bleibt der ältere Vortrag über die Spielarchitektur mit skriptfähigen Objekten eine hervorragende Ressource, wenn Sie tiefer eintauchen möchten.

Bevor wir KI zu diesem anfänglichen, fertig kompilierbaren Projekt hinzufügen, betrachten Sie die vorgeschlagene Architektur:

Diagramm: Sieben Kästchen, die miteinander verbunden sind, beschrieben in der Reihenfolge ihres Erscheinens, von links/oben: Das Kästchen mit der Bezeichnung BaseStateMachine enthält + CurrentState: BaseState. BaseStateMachine stellt mit einem bidirektionalen Pfeil eine Verbindung zu BaseState her. Das Feld mit der Bezeichnung BaseState enthält + Execute(BaseStateMachine): void. BaseState stellt mit einem bidirektionalen Pfeil eine Verbindung zu BaseStateMachine her. Monodirektionale Pfeile von State und RemainInState verbinden sich mit BaseState. Das Feld mit der Bezeichnung State enthält + Execute(BaseStateMachine): void, + Actions: List<Action> und + Transition: List<Transition>. State verbindet sich mit BaseState mit einem unidirektionalen Pfeil, mit Action mit einem unidirektionalen Pfeil mit der Bezeichnung „1“ und mit Transition mit einem unidirektionalen Pfeil mit der Bezeichnung „1“. Das Feld mit der Bezeichnung RemainInState enthält + Execute(BaseStateMachine): void. RemainInState verbindet sich mit BaseState mit einem monodirektionalen Pfeil. Das Feld mit der Bezeichnung Aktion enthält + Execute(BaseStateMachine): void. Ein monodirektionaler Pfeil mit der Bezeichnung „1“ von State verbindet mit Action. Das Feld mit der Bezeichnung Transition enthält + Decide(BaseStateMachine): void, + TransitionDecision: Decision, + TrueState: BaseState und + FalseState: BaseState. Der Übergang ist mit einem monodirektionalen Pfeil mit der Entscheidung verbunden. Ein monodirektionaler Pfeil mit der Bezeichnung „1“ von State verbindet mit Transition. Das Feld mit der Bezeichnung Decision enthält + Decide(BaseStateMachine): bool.
Vorgeschlagene FSM-Architektur

In unserem Beispielspiel patrouilliert der Feind (ein NPC, dargestellt durch eine blaue Kapsel). Wenn der Feind den Spieler sieht (dargestellt durch eine graue Kapsel), beginnt der Feind, dem Spieler zu folgen:

Diagramm: Fünf Kästchen, die miteinander verbunden sind, beschrieben in der Reihenfolge ihres Erscheinens, von links/oben: Das Kästchen mit der Aufschrift Patrol ist mit dem Kästchen mit der Aufschrift IF player is in line of view mit einem monodirektionalen Pfeil und mit dem Kästchen mit der Aufschrift Patrol Action with verbunden ein monodirektionaler Pfeil, der mit "Zustand" beschriftet ist. Das Kästchen mit der Aufschrift IF-Spieler befindet sich in Sichtweite, mit einem zusätzlichen Etikett „Entscheidung“ direkt unter dem Kästchen. Das Kästchen mit der Aufschrift IF player is in line of view verbindet sich mit dem Kästchen mit der Aufschrift Chase mit einem monodirektionalen Pfeil. Ein monodirektionaler Pfeil aus dem Kästchen mit der Aufschrift Patrouille verbindet sich mit dem Kästchen mit der Aufschrift WENN der Spieler in Sichtlinie ist. Das Kästchen mit der Bezeichnung Chase ist mit dem Kästchen mit der Bezeichnung Chase Action mit einem monodirektionalen Pfeil verbunden, der mit "Status" bezeichnet ist. Ein monodirektionaler Pfeil aus dem Kästchen mit der Aufschrift IF player is in line of view verbindet sich mit dem Kästchen mit der Aufschrift Chase. Ein monodirektionaler Pfeil aus dem Kästchen mit der Bezeichnung Patrouille verbindet sich mit dem Kästchen mit der Bezeichnung Patrouillenaktion. Ein monodirektionaler Pfeil Pfeil aus dem Feld mit der Bezeichnung Chase verbindet sich mit dem Feld mit der Bezeichnung Chase Action.
Kernkomponenten unseres Beispiel-Stealth-Spiels FSM

Im Gegensatz zu Pac-Man kehrt der Feind in unserem Spiel nicht in den Standardzustand („Patrouille“) zurück, sobald er dem Spieler folgt.

Klassen erstellen

Beginnen wir mit der Erstellung unserer Klassen. In einem neuen scripts fügen wir alle vorgeschlagenen Architekturbausteine ​​als C#-Skripts hinzu.

Implementieren der BaseStateMachine Klasse

Die BaseStateMachine -Klasse ist das einzige MonoBehavior , das wir hinzufügen werden, um auf unsere KI-fähigen NPCs zuzugreifen. Der Einfachheit halber wird unsere BaseStateMachine nackt sein. Wenn wir wollten, könnten wir jedoch einen geerbten benutzerdefinierten FSM hinzufügen, der zusätzliche Parameter und Verweise auf zusätzliche Komponenten speichert. Beachten Sie, dass der Code nicht ordnungsgemäß kompiliert wird, bis wir unsere BaseState -Klasse hinzugefügt haben, was wir später in unserem Tutorial tun werden.

Der Code für BaseStateMachine bezieht sich auf den aktuellen Zustand und führt ihn aus, um die Aktionen auszuführen und zu prüfen, ob ein Übergang gerechtfertigt ist:

 using UnityEngine; namespace Demo.FSM { public class BaseStateMachine : MonoBehaviour { [SerializeField] private BaseState _initialState; private void Awake() { CurrentState = _initialState; } public BaseState CurrentState { get; set; } private void Update() { CurrentState.Execute(this); } } }

Implementieren der BaseState Klasse

Unser Zustand ist vom Typ BaseState , den wir von einem ScriptableObject ableiten. BaseState enthält eine einzelne Methode, Execute , die BaseStateMachine als Argument verwendet und Aktionen und Übergänge an sie übergibt. So sieht BaseState aus:

 using UnityEngine; namespace Demo.FSM { public class BaseState : ScriptableObject { public virtual void Execute(BaseStateMachine machine) { } } }

Implementieren der State und RemainInState Klassen

Wir leiten nun zwei Klassen von BaseState ab. Erstens haben wir die State -Klasse, die Verweise auf Aktionen und Übergänge speichert, zwei Listen enthält (eine für Aktionen, die andere für Übergänge) und die Execute von Aktionen und Übergängen überschreibt und aufruft:

 using System.Collections.Generic; using UnityEngine; namespace Demo.FSM { [CreateAssetMenu(menuName = "FSM/State")] public sealed class State : BaseState { public List<FSMAction> Action = new List<FSMAction>(); public List<Transition> Transitions = new List<Transition>(); public override void Execute(BaseStateMachine machine) { foreach (var action in Action) action.Execute(machine); foreach(var transition in Transitions) transition.Execute(machine); } } }

Zweitens haben wir die RemainInState -Klasse, die dem FSM mitteilt, wann kein Übergang durchgeführt werden soll:

 using UnityEngine; namespace Demo.FSM { [CreateAssetMenu(menuName = "FSM/Remain In State", fileName = "RemainInState")] public sealed class RemainInState : BaseState { } }

Beachten Sie, dass diese Klassen nicht kompiliert werden, bis wir die Klassen FSMAction , Decision und Transition hinzugefügt haben.

Implementieren der FSMAction Klasse

Im Diagramm der vorgeschlagenen FSM-Architektur ist die Basis- FSMAction -Klasse mit „Action“ gekennzeichnet. Wir erstellen jedoch die Basisklasse FSMAction und verwenden den Namen FSMAction (da Action bereits vom .NET System Namespace verwendet wird).

FSMAction , ein ScriptableObject , kann Funktionen nicht unabhängig verarbeiten, daher definieren wir es als abstrakte Klasse. Während unsere Entwicklung fortschreitet, benötigen wir möglicherweise eine einzige Aktion, um mehr als einem Staat zu dienen. Glücklicherweise können wir FSMAction mit so vielen Zuständen von so vielen FSMs verknüpfen, wie wir möchten.

Die abstrakte Klasse FSMAction sieht folgendermaßen aus:

 using UnityEngine; namespace Demo.FSM { public abstract class FSMAction : ScriptableObject { public abstract void Execute(BaseStateMachine stateMachine); } }

Implementieren der Decision und Transition

Um unsere FSM abzuschließen, werden wir zwei weitere Klassen definieren. Zuerst haben wir Decision , eine abstrakte Klasse, von der aus alle anderen Entscheidungen ihr benutzerdefiniertes Verhalten definieren würden:

 using UnityEngine; namespace Demo.FSM { public abstract class Decision : ScriptableObject { public abstract bool Decide(BaseStateMachine state); } }

Die zweite Klasse, Transition , enthält das Decision und zwei Zustände:

  • Ein Zustand, in den übergegangen werden soll, wenn die Decision wahr ergibt.
  • Ein anderer Zustand, in den übergegangen werden kann, wenn die Decision falsch ergibt.

Es sieht aus wie das:

 using UnityEngine; namespace Demo.FSM { [CreateAssetMenu(menuName = "FSM/Transition")] public sealed class Transition : ScriptableObject { public Decision Decision; public BaseState TrueState; public BaseState FalseState; public void Execute(BaseStateMachine stateMachine) { if(Decision.Decide(stateMachine) && !(TrueState is RemainInState)) stateMachine.CurrentState = TrueState; else if(!(FalseState is RemainInState)) stateMachine.CurrentState = FalseState; } } }

Alles, was wir bis zu diesem Punkt aufgebaut haben, sollte ohne Fehler kompilieren. Wenn Probleme auftreten, überprüfen Sie Ihre Unity-Editor-Version, die zu Fehlern führen kann, wenn sie veraltet ist. Stellen Sie sicher, dass alle Dateien ordnungsgemäß aus dem ursprünglichen Projektordner geklont wurden und dass alle öffentlich zugänglichen Variablen nicht als privat deklariert sind.

Erstellen benutzerdefinierter Aktionen und Entscheidungen

Jetzt, nachdem die schwere Arbeit erledigt ist, sind wir bereit, benutzerdefinierte Aktionen und Entscheidungen in einem neuen scripts zu implementieren.

Implementieren der Patrol und Chase -Klassen

Wenn wir die Kernkomponenten unseres Beispiel-Stealth-Game-FSM-Diagramms analysieren, sehen wir, dass sich unser NPC in einem von zwei Zuständen befinden kann:

  1. Patrouillenstaat - Mit dem Staat verbunden sind:
    • Eine Aktion: NPC besucht zufällige Patrouillenpunkte auf der ganzen Welt.
    • Ein Übergang: Der NPC prüft, ob der Spieler in Sicht ist und wechselt gegebenenfalls in den Verfolgungszustand.
    • Eine Entscheidung: NPC prüft, ob der Spieler in Sicht ist.
  2. Chase-Zustand – Mit dem Zustand verbunden ist:
    • Eine Aktion: NPC jagt den Spieler.

Wir können unsere vorhandene Übergangsimplementierung über die GUI von Unity wiederverwenden, wie wir später besprechen werden. Damit bleiben zwei Aktionen ( PatrolAction und ChaseAction ) und eine Entscheidung für uns, zu codieren.

Die Patrouillenzustandsaktion (die von der Basis- FSMAction ) überschreibt die Execute -Methode, um zwei Komponenten zu erhalten:

  1. PatrolPoints , das Patrouillenpunkte verfolgt.
  2. NavMeshAgent , Unitys Implementierung für die Navigation im 3D-Raum.

Der Override prüft dann, ob der AI-Agent sein Ziel erreicht hat und bewegt sich gegebenenfalls zum nächsten Ziel. Es sieht aus wie das:

 using Demo.Enemy; using Demo.FSM; using UnityEngine; using UnityEngine.AI; namespace Demo.MyFSM { [CreateAssetMenu(menuName = "FSM/Actions/Patrol")] public class PatrolAction : FSMAction { public override void Execute(BaseStateMachine stateMachine) { var navMeshAgent = stateMachine.GetComponent<NavMeshAgent>(); var patrolPoints = stateMachine.GetComponent<PatrolPoints>(); if (patrolPoints.HasReached(navMeshAgent)) navMeshAgent.SetDestination(patrolPoints.GetNext().position); } } }

Wir sollten in Betracht ziehen, die PatrolPoints und NavMeshAgent Komponenten zwischenzuspeichern. Caching würde es uns ermöglichen, ScriptableObject s für Aktionen zwischen Agenten zu teilen, ohne dass die Leistung durch die Ausführung von GetComponent bei jeder Abfrage des endlichen Automaten beeinträchtigt wird.

Um es klarzustellen, wir können keine Komponenteninstanzen in der Execute Methode zwischenspeichern. Stattdessen fügen wir BaseStateMachine eine benutzerdefinierte GetComponent Methode BaseStateMachine . Unsere benutzerdefinierte GetComponent würde die Instanz beim ersten Aufruf zwischenspeichern und die zwischengespeicherte Instanz bei aufeinanderfolgenden Aufrufen zurückgeben. Als Referenz ist dies die Implementierung von BaseStateMachine mit Caching:

 using System; using System.Collections.Generic; using UnityEngine; namespace Demo.FSM { public class BaseStateMachine : MonoBehaviour { [SerializeField] private BaseState _initialState; private Dictionary<Type, Component> _cachedComponents; private void Awake() { CurrentState = _initialState; _cachedComponents = new Dictionary<Type, Component>(); } public BaseState CurrentState { get; set; } private void Update() { CurrentState.Execute(this); } public new T GetComponent<T>() where T : Component { if(_cachedComponents.ContainsKey(typeof(T))) return _cachedComponents[typeof(T)] as T; var component = base.GetComponent<T>(); if(component != null) { _cachedComponents.Add(typeof(T), component); } return component; } } }

Wie ihr Gegenstück PatrolAction überschreibt die ChaseAction -Klasse die Execute -Methode, um PatrolPoints und NavMeshAgent Komponenten abzurufen. Im Gegensatz dazu setzt die ChaseAction nach der Überprüfung, ob der KI-Agent sein Ziel erreicht hat, das Ziel auf Player.position :

 using Demo.Enemy; using Demo.FSM; using UnityEngine; using UnityEngine.AI; namespace Demo.MyFSM { [CreateAssetMenu(menuName = "FSM/Actions/Chase")] public class ChaseAction : FSMAction { public override void Execute(BaseStateMachine stateMachine) { var navMeshAgent = stateMachine.GetComponent<NavMeshAgent>(); var enemySightSensor = stateMachine.GetComponent<EnemySightSensor>(); navMeshAgent.SetDestination(enemySightSensor.Player.position); } } }

Implementieren der InLineOfSightDecision Klasse

Das letzte Stück ist die InLineOfSightDecision -Klasse, die die Basisentscheidung erbt und die Decision -Komponente dazu EnemySightSensor , zu überprüfen, ob sich der Spieler in der Sichtlinie des NPC befindet:

 using Demo.Enemy; using Demo.FSM; using UnityEngine; namespace Demo.MyFSM { [CreateAssetMenu(menuName = "FSM/Decisions/In Line Of Sight")] public class InLineOfSightDecision : Decision { public override bool Decide(BaseStateMachine stateMachine) { var enemyInLineOfSight = stateMachine.GetComponent<EnemySightSensor>(); return enemyInLineOfSight.Ping(); } } }

Anhängen von Verhaltensweisen an Zustände

Endlich sind wir bereit, dem Enemy Agenten Verhaltensweisen zuzuordnen. Diese werden im Projektfenster des Unity-Editors erstellt.

Hinzufügen der Patrol und Chase

Lassen Sie uns zwei Zustände erstellen und sie „Patrouille“ und „Verfolgung“ nennen:

  • Rechtsklick > Erstellen > FSM > Status

Lassen Sie uns hier auch ein RemainInState Objekt erstellen:

  • Rechtsklick > Erstellen > FSM > Im Zustand bleiben

Jetzt ist es an der Zeit, die Aktionen zu erstellen, die wir gerade codiert haben:

  • Rechtsklick > Erstellen > FSM > Aktion > Patrouille
  • Rechtsklick > Erstellen > FSM > Aktion > Verfolgung

Um die Decision zu codieren:

  • Rechtsklick > Erstellen > FSM > Entscheidungen > In Sichtlinie

Um einen Übergang von PatrolState zu ChaseState zu ermöglichen, erstellen wir zunächst das skriptfähige Übergangsobjekt:

  • Rechtsklick > Erstellen > FSM > Übergang
  • Wählen Sie einen Namen, der Ihnen gefällt. Ich habe meinen Spotted Enemy genannt.

Wir werden das resultierende Inspektorfenster wie folgt füllen:

Der Bildschirm „Sichtbarer Feind (Übergang)“ enthält vier Zeilen: Der Wert des Skripts ist auf „Übergang“ gesetzt und ausgegraut. Der Wert von Decision wird auf „LineOfSightDecision (In Line Of Sight)“ gesetzt. Der Wert von True State ist auf „ChaseState (State)“ gesetzt. Der Wert von False State ist auf „RemainInState (Remain In State)“ gesetzt.
Ausfüllen des Inspektorfensters „Sichtbarer Feind (Übergang)“.

Dann vervollständigen wir das Chase State Inspector-Dialogfeld wie folgt:

Der Chase State (State)-Bildschirm beginnt mit einem Label "Open". Neben dem Label „Script“ ist „State“ ausgewählt. Neben der Beschriftung „Aktion“ ist „1“ ausgewählt. Aus der Dropdown-Liste „Aktion“ wird „Element 0 Chase Action (Chase Action)“ ausgewählt. Es folgt ein Pluszeichen und ein Minuszeichen. Neben dem Label „Übergänge“ ist „0“ ausgewählt. In der Dropdown-Liste „Übergänge“ wird „Liste ist leer“ angezeigt. Es folgt ein Pluszeichen und ein Minuszeichen.
Ausfüllen des Chase State Inspector-Fensters

Als Nächstes vervollständigen wir das Dialogfeld Patrouillenstatus:

Der Bildschirm Patrouillenstatus (Status) beginnt mit einem Label "Offen". Neben dem Label „Script“ ist „State“ ausgewählt. Neben der Beschriftung „Aktion“ ist „1“ ausgewählt. Aus der Dropdown-Liste „Aktion“ wird „Element 0 Patrol Action (Patrol Action)“ ausgewählt. Es folgt ein Plus- und ein Minuszeichen. Neben dem Label „Übergänge“ ist „1“ ausgewählt. Aus der Dropdown-Liste „Übergänge“ wird „Element 0 SpottedEnemy (Übergang)“ angezeigt. Es folgt ein Pluszeichen und ein Minuszeichen.
Ausfüllen des Patrol State Inspector-Fensters

Schließlich fügen wir dem feindlichen Objekt die BaseStateMachine Komponente hinzu: Öffnen Sie im Projektfenster des Unity-Editors das SampleScene -Asset, wählen Sie das feindliche Objekt im Hierarchiebereich aus und wählen Sie im Inspektorfenster Komponente hinzufügen > Basiszustandsmaschine aus:

Der Bildschirm „Base State Machine (Script)“: Neben der ausgegrauten Beschriftung „Script“ ist „BaseStateMachine“ ausgewählt und ausgegraut. Neben dem Label „Initial State“ ist „PatrolState (State)“ ausgewählt.
Hinzufügen der Base State Machine (Skript)-Komponente

Überprüfen Sie bei Problemen, ob Ihre Spielobjekte korrekt konfiguriert sind. Bestätigen Sie beispielsweise, dass das Enemy-Objekt die PatrolPoints -Skriptkomponente und die Objekte Point1 , Point2 usw. enthält. Diese Informationen können bei falscher Versionierung des Editors verloren gehen.

Jetzt können Sie das Beispielspiel spielen und beobachten, dass der Feind dem Spieler folgt, wenn der Spieler in die Sichtlinie des Feindes tritt.

Verwenden von FSMs zum Erstellen einer unterhaltsamen, interaktiven Benutzererfahrung

In diesem Tutorial zu endlichen Zustandsautomaten haben wir eine hochgradig modulare FSM-basierte KI (und das entsprechende GitHub-Repo) erstellt, die wir in zukünftigen Projekten wiederverwenden können. Dank dieser Modularität können wir unsere KI immer leistungsfähiger machen, indem wir neue Komponenten einführen.

Aber unsere Architektur ebnet auch den Weg für ein grafisches FSM-Design, das unsere Entwicklererfahrung auf eine neue Ebene der Professionalität heben würde. Wir könnten dann FSMs für unsere Spiele schneller erstellen – und mit besserer kreativer Genauigkeit.