GOAP (Goal-Oriented Action Planning) ist ein KI-System, das Ihren Agenten die Wahlmöglichkeiten und die Tools gibt, mit denen sie kluge Entscheidungen treffen können, ohne einen großen und komplexen Finite-State-Rechner warten zu müssen.
In dieser Demo gibt es vier Zeichenklassen, die jeweils Werkzeuge verwenden, die nach einer Weile abgebrochen werden:
Jede Klasse ermittelt anhand einer zielorientierten Handlungsplanung automatisch, welche Maßnahmen sie ausführen müssen, um ihre Ziele zu erreichen. Wenn ihr Werkzeug bricht, gehen sie zu einem Vorratsstapel, den der Schmied gemacht hat.
Zielorientierte Aktionsplanung ist ein künstliches Intelligenzsystem für Agenten, mit dem sie eine Aktionssequenz planen können, um ein bestimmtes Ziel zu erreichen. Die bestimmte Reihenfolge der Handlungen hängt nicht nur vom Ziel ab, sondern auch vom aktuellen Zustand der Welt und dem Agenten. Das heißt, wenn dasselbe Ziel für verschiedene Agenten oder Weltzustände angegeben wird, können Sie eine völlig andere Reihenfolge von Aktionen erhalten, wodurch die KI dynamischer und realistischer wird. Schauen wir uns ein Beispiel an, wie in der obigen Demo gezeigt.
Wir haben einen Agenten, einen Holzhacker, der Holzstämme entnimmt und sie in Brennholz zerhackt. Der Häcksler kann mit dem Ziel geliefert werden MakeFirewood
, und hat die Aktionen ChopLog
, GetAxe
, und CollectBranches
.
Das ChopLog
Durch diese Aktion wird ein Stamm in Brennholz umgewandelt, jedoch nur, wenn der Holzfäller eine Axt hat. Das GetAxe
Aktion wird dem Holzfäller eine Axt geben. Endlich, das CollectBranches
Bei der Aktion wird auch Brennholz erzeugt, ohne dass eine Axt erforderlich ist. Das Brennholz ist jedoch nicht so hochwertig.
Wenn wir dem Agenten das geben MakeFirewood
Ziel erhalten wir diese zwei verschiedenen Aktionssequenzen:
GetAxe
-> ChopLog
= macht BrennholzCollectBranches
= macht BrennholzWenn der Agent eine Axt bekommen kann, kann er einen Stamm zerkleinern, um Brennholz herzustellen. Aber vielleicht bekommen sie keine Axt. dann können sie einfach Äste sammeln. Jede dieser Sequenzen wird das Ziel von erfüllen MakeFirewood
.
GOAP kann die beste Reihenfolge basierend auf den verfügbaren Voraussetzungen auswählen. Wenn keine Axt zur Hand ist, muss der Holzfäller Äste abholen. Das Aufheben von Ästen kann sehr lange dauern und Brennholz von schlechter Qualität liefern. Wir möchten nicht, dass es ständig läuft, nur wenn es nötig ist.
Sie sind inzwischen wahrscheinlich mit Finite State Machines (FSM) vertraut, aber wenn nicht, dann schauen Sie sich dieses großartige Tutorial an.
Für einige Ihrer FSM-Agenten sind möglicherweise sehr große und komplexe Zustände aufgetreten, an denen Sie schließlich zu einem Punkt gelangen, an dem Sie keine neuen Verhaltensweisen hinzufügen möchten, da diese zu viele Nebenwirkungen und Lücken in der KI verursachen.
GOAP dreht das:
Zustandsautomatenzustände: überall verbunden.Das sehr gut finden:
GOAP: nett und handlich.Durch die Entkopplung der Aktionen voneinander können wir uns nun auf jede Aktion einzeln konzentrieren. Dies macht den Code modular und einfach zu testen und zu warten. Wenn Sie eine weitere Aktion hinzufügen möchten, können Sie sie einfach einplanen, und es müssen keine weiteren Aktionen geändert werden. Versuchen Sie das mit einem FSM!
Sie können auch spontan Aktionen hinzufügen oder entfernen, um das Verhalten eines Agenten so zu verändern, dass er noch dynamischer wird. Hat ein Oger plötzlich angefangen zu toben? Gib ihnen eine neue Aktion "Wutangriff", die entfernt wird, wenn sie sich beruhigt haben. Sie müssen lediglich die Aktion zur Liste der Aktionen hinzufügen. Der GOAP-Planer kümmert sich um den Rest.
Wenn Sie feststellen, dass Sie eine sehr komplexe FSM für Ihre Agenten haben, sollten Sie es mit GOAP versuchen. Ein Zeichen dafür, dass Ihr FSM zu komplex wird, ist, dass jeder Staat eine Vielzahl von if-else-Anweisungen hat, in denen getestet wird, zu welchem Status er als nächstes wechseln soll. Wenn Sie einen neuen Status hinzufügen, werden Sie bei allen möglichen Folgen stöhnen.
Wenn Sie über einen sehr einfachen Agenten verfügen, der nur eine oder zwei Aufgaben ausführt, ist GOAP möglicherweise etwas schwerfällig und ein FSM reicht aus. Es lohnt sich jedoch, sich die Konzepte hier anzusehen und zu prüfen, ob sie für Ihren Agenten einfach genug sind.
Ein Aktion ist der Agent. Normalerweise wird nur eine Animation und ein Sound abgespielt und ein bisschen Status geändert (z. B. Hinzufügen von Brennholz). Das Öffnen einer Tür ist eine andere Aktion (und Animation) als das Aufnehmen eines Stiftes. Eine Aktion ist gekapselt und sollte sich nicht darum kümmern, was die anderen Aktionen sind.
Damit GOAP feststellen kann, welche Aktionen verwendet werden sollen, erhält jede Aktion ein Kosten. Eine Aktion mit hohen Kosten wird nicht für eine Aktion mit niedrigeren Kosten gewählt. Wenn wir die Aktionen zusammenstellen, addieren wir die Kosten und wählen dann die Reihenfolge mit den niedrigsten Kosten aus.
Lassen Sie uns den Aktionen einige Kosten zuordnen:
GetAxe
Kosten: 2ChopLog
Kosten: 4CollectBranches
Kosten: 8Wenn wir uns die Reihenfolge der Aktionen noch einmal ansehen und die Gesamtkosten addieren, sehen wir die günstigste Reihenfolge:
GetAxe
(2) -> ChopLog
(4) = macht Brennholz(gesamt: 6)CollectBranches
(8) = macht Brennholz(gesamt: 8)Eine Axt zu bekommen und einen Stamm zu hacken, produziert Brennholz zu niedrigeren Kosten von 6, während das Sammeln der Äste Holz zu höheren Kosten von 8 produziert. Daher entscheidet sich unser Agent für eine Axt und hackt Holz.
Aber wird diese Sequenz nicht immer laufen? Nicht wenn wir uns vorstellen Vorbedingungen…
Aktionen haben Vorbedingungen und Auswirkungen. Voraussetzung ist der Status, der für die Ausführung der Aktion erforderlich ist, und die Auswirkungen sind die Änderung des Status, nachdem die Aktion ausgeführt wurde.
Zum Beispiel die ChopLog
Aktion erfordert, dass der Agent eine Axt griffbereit hat. Wenn der Agent keine Axt hat, muss er eine andere Aktion finden, die diese Voraussetzung erfüllen kann, um die Axt zuzulassen ChopLog
Aktionslauf. Zum Glück die GetAxe
Aktion tut das - das ist die Wirkung der Aktion.
Der GOAP-Planer ist ein Code, der die Vorbedingungen und Auswirkungen von Aktionen untersucht und Warteschlangen von Aktionen erstellt, die ein Ziel erreichen. Dieses Ziel wird vom Agenten zusammen mit einem Weltzustand und einer Liste von Aktionen bereitgestellt, die der Agent ausführen kann. Mit diesen Informationen kann der GOAP-Planer die Aktionen anordnen, feststellen, welche ausgeführt werden dürfen und welche nicht, und dann entscheiden, welche Aktionen am besten ausgeführt werden können. Zum Glück für Sie habe ich diesen Code geschrieben, so dass Sie nicht müssen.
Um dies einzurichten, fügen wir den Aktionen unseres Holzhackers Vorbedingungen und Effekte hinzu:
GetAxe
Kosten: 2. Voraussetzungen: "eine Axt ist vorhanden", "hat keine Axt". Wirkung: "hat eine Axt".ChopLog
Kosten: 4. Voraussetzungen:"hat eine Axt". Wirkung: "Brennholz machen"CollectBranches
Kosten: 8. Voraussetzungen: (keine). Wirkung: "Brennholz machen".Der GOAP-Planer verfügt nun über die erforderlichen Informationen, um die Reihenfolge der Aktionen zur Herstellung von Brennholz anzuordnen (unser Ziel)..
Wir versorgen den GOAP-Planer zunächst mit dem aktuellen Stand der Welt und dem Zustand des Agenten. Dieser kombinierte Weltstaat ist:
Im Hinblick auf unsere derzeit verfügbaren Aktionen ist der einzige Teil der für sie relevanten Zustände der Status "Keine Axt" und "Eine Axt ist verfügbar". der andere kann für andere Agenten mit anderen Aktionen verwendet werden.
Okay, wir haben unseren aktuellen Weltzustand, unsere Handlungen (mit ihren Voraussetzungen und Auswirkungen) und das Ziel. Lass uns planen!
ZIEL: "Brennholz machen" Aktueller Status: "Keine Axt", "Eine Axt ist verfügbar" Kann Action ChopLog ausgeführt werden? NO - erfordert Vorbedingung "hat eine Axt" Kann sie jetzt nicht verwenden, versuchen Sie es mit einer anderen Aktion. Kann Action GetAxe laufen? JA, Vorbedingungen "Eine Axt ist verfügbar" und "Keine Axt" sind wahr. PUSH-Aktion in Warteschlange, Aktualisierungsstatus mit Aktionswirkung Neuer Status "hat eine Axt" Status entfernen "eine Axt ist verfügbar", weil wir nur eine genommen haben. Kann Action ChopLog ausgeführt werden? JA, Voraussetzung "hat eine Axt" ist wahr PUSH-Aktion in die Warteschlange, Aktualisierungsstatus mit Wirkung der Aktion Neuer Status "hat eine Axt", "macht Brennholz" Wir haben unser ZIEL erreicht "Brennholz machen" Aktionssequenz: GetAxe -> ChopLog
Der Planer durchläuft auch die anderen Aktionen und hört nicht auf, wenn er eine Lösung für das Ziel findet. Was ist, wenn eine andere Sequenz kostengünstiger ist? Es wird alle Möglichkeiten durchlaufen, um die beste Lösung zu finden.
Wenn es plant, baut es eine auf Baum. Jedes Mal, wenn eine Aktion angewendet wird, wird sie aus der Liste der verfügbaren Aktionen entfernt, sodass wir keine Zeichenfolge von 50 haben GetAxe
Aktionen hintereinander. Der Zustand wird mit dem Effekt dieser Aktion geändert.
Der Baum, den der Planer aufbaut, sieht folgendermaßen aus:
Wir können sehen, dass es tatsächlich drei Wege zum Ziel mit ihren Gesamtkosten finden wird:
GetAxe
-> ChopLog
(gesamt: 6)GetAxe
-> CollectBranches
(gesamt: 10)CollectBranches
(gesamt: 8)Obwohl GetAxe
-> CollectBranches
funktioniert, der billigste Weg ist GetAxe
-> ChopLog
, also wird dieser zurückgegeben.
Wie sehen die Vorbedingungen und Effekte im Code aus? Nun, es liegt an Ihnen, aber ich finde es am einfachsten, sie als Schlüssel-Wert-Paar zu speichern, wobei der Schlüssel immer ein String ist und der Wert ein Objekt oder ein primitiver Typ ist (float, int, Boolean oder ähnliches). In C # könnte das so aussehen:
HashSet< KeyValuePair> Voraussetzungen; HashSet< KeyValuePair > Wirkungen;
Wie sehen diese Effekte aus und was tun sie, wenn die Aktion ausgeführt wird? Nun, sie müssen nichts tun - sie werden wirklich nur für die Planung verwendet und wirken sich nicht auf den Status des realen Agenten aus, bis sie wirklich laufen.
Dies ist hervorzuheben: Die Planung von Aktionen ist nicht dasselbe wie das Ausführen von Aktionen. Wenn ein Agent das ausführt GetAxe
Action, es wird sich wahrscheinlich in der Nähe eines Haufens von Werkzeugen befinden, eine Animation mit Biegung und Abholung ausführen und dann ein Axtobjekt in seinem Rucksack aufbewahren. Dadurch wird der Status des Agenten geändert. Aber während GOAP Planung, Die Zustandsänderung ist nur vorübergehend, so dass der Planer die optimale Lösung finden kann.
Manchmal müssen Aktionen etwas mehr unternommen werden, um festzustellen, ob sie ausgeführt werden können. Zum Beispiel die GetAxe
Aktion setzt die Voraussetzung voraus, dass "eine Axt verfügbar ist", die die Welt oder die unmittelbare Umgebung durchsuchen muss, um zu sehen, ob es eine Axt gibt, die der Agent annehmen kann. Es könnte sich herausstellen, dass die nächste Axt zu weit weg oder hinter den feindlichen Linien liegt, und wird sagen, dass sie nicht laufen kann. Diese Voraussetzung ist prozedural und muss Code ausführen. Es ist kein einfacher boolescher Operator, den wir einfach umschalten können.
Offensichtlich können einige dieser prozeduralen Voraussetzungen einige Zeit in Anspruch nehmen und sollten für etwas anderes als den Renderthread ausgeführt werden, idealerweise als Hintergrundthread oder als Coroutines (in Unity)..
Sie können auch verfahrenstechnische Auswirkungen haben, wenn Sie dies wünschen. Und wenn Sie noch dynamischere Ergebnisse einführen möchten, können Sie das ändern Kosten von Aktionen im Fluge!
Unser GOAP-System muss in einer kleinen Finite-State-Machine (FSM) leben, nur weil in vielen Spielen Aktionen nah an einem Ziel sein müssen, damit sie ausgeführt werden können. Wir landen mit drei Staaten:
Im Leerlauf
Ziehen nach
PerformAction
Im Leerlauf wird der Agent herausfinden, welches Ziel er erreichen möchte. Dieser Teil wird außerhalb von GOAP behandelt. GOAP sagt Ihnen nur, welche Aktionen Sie ausführen können, um dieses Ziel zu erreichen. Wenn ein Ziel ausgewählt wird, wird es zusammen mit dem Startzustand der Welt und des Agenten an den GOAP-Planer übergeben, und der Planer gibt eine Liste der Aktionen zurück (falls dieses Ziel erreicht werden kann)..
Wenn der Planer fertig ist und der Agent eine Liste mit Aktionen hat, versucht er, die erste Aktion auszuführen. Alle Aktionen müssen wissen, ob sie sich in Reichweite eines Ziels befinden müssen. Wenn dies der Fall ist, drückt der FSM den nächsten Status: Ziehen nach
.
Das Ziehen nach
state teilt dem Agenten mit, dass er zu einem bestimmten Ziel wechseln muss. Der Agent führt das Verschieben aus (und spielt die Walk-Animation ab) und teilt dem FSM mit, wenn er sich innerhalb der Reichweite des Ziels befindet. Dieser Status wird dann deaktiviert und die Aktion kann ausgeführt werden.
Das PerformAction
state führt die nächste Aktion in der Warteschlange der vom GOAP Planner zurückgegebenen Aktionen aus. Die Aktion kann sofort oder über viele Frames dauern, aber wenn sie fertig ist, wird sie abgebrochen und die nächste Aktion wird ausgeführt (erneut, nachdem geprüft wurde, ob diese nächste Aktion innerhalb des Objektbereichs ausgeführt werden muss)..
Dies wiederholt sich so lange, bis keine weiteren Aktionen mehr ausgeführt werden müssen. Dann kehren wir zurück zum Im Leerlauf
Zustand, erhalten Sie ein neues Ziel und planen Sie erneut.
Es ist Zeit, ein echtes Beispiel zu betrachten! Mach dir keine Sorgen; Es ist nicht so kompliziert und ich habe eine Arbeitskopie in Unity und C # zur Verfügung gestellt, die Sie ausprobieren können. Ich werde hier nur kurz darüber sprechen, damit Sie ein Gefühl für die Architektur bekommen. Der Code verwendet einige der oben genannten WoodChopper-Beispiele.
Wenn Sie direkt reingreifen möchten, finden Sie hier den Code: http://github.com/sploreg/goap
Wir haben vier Arbeiter:
Werkzeuge nehmen mit der Zeit ab und müssen ersetzt werden. Glücklicherweise stellt der Schmied Werkzeuge her. Eisenerz wird jedoch benötigt, um Werkzeuge herzustellen; Hier kommt der Miner ins Spiel (der auch Werkzeuge braucht). Der Holzfäller benötigt Protokolle, und diese kommen vom Logger. Beide brauchen auch Werkzeuge.
Werkzeuge und Ressourcen werden auf Vorratshalden gespeichert. Die Agenten sammeln die benötigten Materialien oder Werkzeuge von den Stapeln und geben ihr Produkt auch ab.
Der Code verfügt über sechs Hauptklassen von GOAP:
GoapAgent
: versteht den Zustand und verwendet die FSM und GoapPlanner
zu bedienen.GoapAction
: Aktionen, die Agenten ausführen können.GoapPlanner
: plant die Aktionen für die GoapAgent
.FSM
: die endliche Zustandsmaschine.FSMState
: ein Staat in der FSM.IGoap
: die Schnittstelle, die unsere echten Arbeiter-Schauspieler verwenden. Bindet an Veranstaltungen für GOAP und den FSM.Schauen wir uns das an GoapAction
Klasse, da es sich um die Unterklasse handelt,
öffentliche abstrakte Klasse GoapAction: MonoBehaviour private HashSet> Voraussetzungen; private HashSet > Wirkungen; private bool inRange = false; / * Die Kosten für die Durchführung der Aktion. * Finden Sie ein Gewicht heraus, das zur Aktion passt. Eine Änderung wirkt sich darauf aus, welche Aktionen während der Planung ausgewählt werden. * / Public float cost = 1f; / ** * Eine Aktion muss häufig für ein Objekt ausgeführt werden. Das ist das Objekt. Kann null sein. * / öffentliches GameObject-Ziel; public GoapAction () Vorbedingungen = neues HashSet > (); effects = new HashSet > (); public void doReset () inRange = false; Ziel = Null; reset (); / ** * Setzen Sie alle Variablen zurück, die zurückgesetzt werden müssen, bevor die Planung erneut erfolgt. * / public abstract void reset (); / ** * Wird die Aktion ausgeführt? * / public abstract bool isDone (); / ** * Überprüfe, ob diese Aktion ausgeführt werden kann. Nicht alle Aktionen * benötigen dies, aber einige können dies tun. * / public abstract bool checkProceduralPrecondition (GameObject-Agent); / ** * Führe die Aktion aus. * Gibt "True" zurück, wenn die Aktion erfolgreich ausgeführt wurde, oder "False". * Wenn etwas passiert ist und keine Aktion mehr ausgeführt werden kann. In diesem Fall * sollte die Aktionswarteschlange gelöscht werden und das Ziel kann nicht erreicht werden. * / public abstract bool perform (GameObject-Agent); / ** * Muss diese Aktion in Reichweite eines Zielspielobjekts liegen? * Wenn nicht, muss der moveTo-Status für diese Aktion nicht ausgeführt werden. * / public abstract bool RequiresInRange (); / ** * Befinden wir uns in Reichweite des Ziels? * Der MoveTo-Status setzt dies und wird bei jeder Ausführung dieser Aktion zurückgesetzt. * / public bool isInRange () return inRange; public void setInRange (bool inRange) this.inRange = inRange; public void addPrecondition (Zeichenfolgenschlüssel, Objektwert) Vorbedingungen.Add (new KeyValuePair.) (Schlüsselwert) ); public void removePrecondition (Zeichenfolgenschlüssel) KeyValuePair remove = default (KeyValuePair.) ); foreach (KeyValuePair kvp in Vorbedingungen) if (kvp.Key.Equals (key)) remove = kvp; if (! default (KeyValuePair.) ) .Equals (remove)) Vorbedingungen .Entfernen (Entfernen); public void addEffect (Zeichenfolgenschlüssel, Objektwert) effects.Add (new KeyValuePair.) (Schlüsselwert) ); public void removeEffect (Zeichenfolgenschlüssel) KeyValuePair remove = default (KeyValuePair.) ); foreach (KeyValuePair kvp in effects) if (kvp.Key.Equals (key)) remove = kvp; if (! default (KeyValuePair.) ) .Equals (entfernen)) Effekte.Entfernen (Entfernen); public HashSet > Vorbedingungen get Vorbedingungen zurückgeben; public HashSet > Effekte get return effekte;
Nichts Besonderes: Hier werden Vorbedingungen und Effekte gespeichert. Es weiß auch, ob es sich in Reichweite eines Ziels befinden muss, und wenn ja, dann weiß die FSM, das zu drücken Ziehen nach
Zustand bei Bedarf. Es weiß auch, wann es fertig ist. das wird von der implementierenden Aktionsklasse bestimmt.
Hier ist eine der Aktionen:
öffentliche Klasse MineOreAction: GoapAction private bool mined = false; private IronRockComponent targetRock; // wo wir das Erz vom privaten Float bekommen startTime = 0; public float miningDuration = 2; // Sekunden public MineOreAction () addPrecondition ("hasTool", true); // Wir brauchen ein Werkzeug, um dies zu tun addPrecondition ("hasOre", false); // wenn wir ore haben, wollen wir nicht mehr addEffect ("hasOre", true); public override void reset () mined = false; targetRock = null; startTime = 0; public override bool isDone () return mined; public override bool requiredInRange () return true; // Ja, wir müssen in der Nähe eines Felsens sein public override bool checkProceduralPrecondition (GameObject-Agent) // Finde den nächsten Stein, den wir abbauen können IronRockComponent [] rocks = FindObjectsOfType (typeof (IronRockComponent)) als IronRockComponent []; IronRockComponent close = null; float nextDist = 0; foreach (IronRockComponent Rocks in Rocks) if (nächste == null) // erste, also wählen Sie es jetzt close = rock; nextDist = (rock.gameObject.transform.position - agent.transform.position) .magnitude; else // Ist dieser näher als der letzte? float dist = (rock.gameObject.transform.position - agent.transform.position) .magnitude; if (dist < closestDist) // we found a closer one, use it closest = rock; closestDist = dist; targetRock = closest; target = targetRock.gameObject; return closest != null; public override bool perform (GameObject agent) if (startTime == 0) startTime = Time.time; if (Time.time - startTime > miningDuration) // Bergbau beendet BackpackComponent backpack = (BackpackComponent) agent.GetComponent (typeof (BackpackComponent)); backpack.numOre + = 2; vermint = wahr; ToolComponent Werkzeug = backpack.tool.GetComponent (typeof (ToolComponent)) als ToolComponent; tool.use (0.5f); if (tool.destroyed ()) Zerstöre (backpack.tool); backpack.tool = null; return true;
Der größte Teil der Aktion ist die checkProceduralPreconditions
Methode. Es sucht nach dem nächstgelegenen Spielobjekt mit einem IronRockComponent
, und rettet dieses Zielgestein. Wenn er auftritt, erhält er den gespeicherten Zielgestein und führt die Aktion darauf aus. Wenn die Aktion erneut in der Planung verwendet wird, werden alle Felder zurückgesetzt, damit sie erneut berechnet werden können.
Dies sind alle Komponenten, die dem hinzugefügt werden Bergmann
Entitätsobjekt in Unity:
Damit Ihr Agent funktionieren kann, müssen Sie ihm folgende Komponenten hinzufügen:
GoapAgent
.IGoap
(im obigen Beispiel ist das Miner.cs
).Hier ist die Demo wieder in Aktion:
Jeder Arbeiter geht zu dem Ziel, das er zur Erfüllung seiner Aktion benötigt (Baum, Stein, Hackblock oder was auch immer), führt die Aktion aus und kehrt häufig zum Vorratsstapel zurück, um seine Waren abzulegen. Der Schmied wartet eine Weile, bis sich Eisenerz in einem der Vorratshalden befindet (hinzugefügt vom Bergmann). Der Schmied geht dann los und stellt Werkzeuge her und legt die Werkzeuge am nächstgelegenen Vorratsstapel ab. Wenn das Werkzeug eines Arbeiters bricht, geht es zum Versorgungsstapel in der Nähe des Schmiedes, wo sich die neuen Werkzeuge befinden.
Sie können den Code und die vollständige App hier herunterladen: http://github.com/sploreg/goap.