NPC-Verhalten: Zustandsmaschinen, Verhaltensbäume, GOAP und AI-gesteuerte NPCs
Erstelle dein eigenes NPC-Verhalten
Wie entscheiden NPCs, was sie tun sollen? Zustandsmaschinen, Verhaltensbäume, GOAP, Utility-AI und LLM-gesteuerte NPCs — erklärt mit Live-3D-Beispielen im Browser.
NPC-Verhalten ist die Logik, die aus einem Modell, einer Lebensanzeige und einer Kollisionskapsel etwas macht, das den Anschein erweckt, Dinge zu wollen. Sie beantwortet in einer Schleife immer wieder eine Frage, meist in jedem Frame oder in einem festen Tick: Was soll ich als Nächstes tun, angesichts dessen, was ich über die Welt weiß? Alles andere auf dieser Seite — endliche Zustandsmaschinen, Verhaltensbäume, GOAP, Utility-AI, LLM-gesteuerte Absicht — ist eine andere Antwort auf diese Frage, mit anderen Kosten und anderen Fehlerarten.
Wir bauen NPCs, die in einem Browser-Tab innerhalb des 3D Game Builders von Neta Studio laufen. Diese Einschränkung prägt die folgenden Ratschläge. Es gibt keinen dedizierten Server, keinen separaten Physik-Thread und kein Budget für einen Planer, der in jedem Tick 40 Millisekunden lang sucht. Wenn sich eine Technik nicht günstig genug machen lässt, um neben dem Rendering auf einem Mittelklasse-Laptop zu laufen, stufen wir sie entweder herunter oder verlagern sie aus dem Hot Path. Die folgenden Abschnitte beschreiben, was wir tatsächlich ausliefern, und wo jeder Ansatz bricht.
Kurzantwort: Wie ein NPC entscheidet
Ein NPC entscheidet, indem er eine Entscheidungsfunktion nach einem Zeitplan ausführt, einen kleinen Ausschnitt des Weltzustands liest (Entfernung zum Ziel, Sichtlinie, aktuelle Gesundheit, zuletzt bekannte Position) und eine Aktion zurückgibt, die die Bewegungs- und Animationssysteme ausführen können. Der Zeitplan ist genauso wichtig wie die Logik. Entscheidungen in einem Tick von 100 ms bis 250 ms, mit Bewegung und Kollision in jedem Frame, kosten einen Bruchteil dessen, was Entscheidungslogik pro Frame kostet, und sind für einen Spieler in den meisten Fällen nicht unterscheidbar.
Die vier klassischen Modelle unterscheiden sich darin, wo die Komplexität sitzt. Eine Zustandsmaschine kodiert Entscheidungen als Übergänge zwischen benannten Zuständen. Ein Verhaltensbaum kodiert sie als Hierarchie von Selektor- und Sequenzknoten. GOAP kodiert sie als ein Ziel plus eine Menge von Aktionen mit Vorbedingungen und Effekten und sucht einen Plan. Utility-AI kodiert sie als eine Bewertungsfunktion über jede Kandidatenaktion. LLM-gesteuerte NPCs kodieren die Absicht auf hoher Ebene in natürlicher Sprache und geben das Ergebnis an eines der günstigeren Modelle zurück.
In der Praxis kombiniert man sie. Ein Utility-Bewerter wählt das Ziel, ein Verhaltensbaum oder eine Zustandsmaschine führt es aus, ein Navmesh bewegt den Körper, und das Sprachmodell berührt nur Dialog und Absicht. Der Rest dieser Seite nimmt jede Ebene der Reihe nach durch und benennt die konkrete Fehlerart, auf die du stoßen wirst.
Zustandsmaschinen (FSM)
Eine endliche Zustandsmaschine ist das günstigste nützliche Modell und die richtige erste Wahl für die meisten NPCs. Du definierst eine Menge von Zuständen — Idle, Patrouille, Verfolgung, Angriff, Flucht, Betäubt — und eine Menge von Übergangsbedingungen zwischen ihnen. In jedem Tick wertest du die Übergänge aus dem aktuellen Zustand aus und wechselst, wenn einer auslöst. Die Kosten sind proportional zur Anzahl der geprüften Übergänge, die klein ist. Der Zustand ist eine einzelne Variable, was ihn trivial zu serialisieren, zu debuggen und in einem Editor-Overlay anzuzeigen macht.
Die Fehlerart ist Oszillation. Ein NPC in 5,1 Metern Entfernung zum Spieler wechselt von Patrouille zu Verfolgung, bewegt sich auf 4,9 Meter, und ein symmetrischer Übergang schickt ihn zurück zur Patrouille. Er vibriert für immer an der Grenze. Die Lösung ist Hysterese: Wechsle bei 5,0 Metern in die Verfolgung und verlasse sie bei 6,5 Metern. Dasselbe Problem tritt bei Sichtlinienprüfungen auf, die flackern, wenn der Spieler hinter eine Säule ausweicht, und dieselbe Lösung gilt — verlange, dass die Bedingung N aufeinanderfolgende Ticks lang gilt, oder füge einen Erinnerungstimer hinzu, der den NPC nach der letzten Sichtung zwei Sekunden lang in der Verfolgung hält.
Die zweite Fehlerart ist das Übergangschaos. Fünf Zustände brauchen zehn Übergänge; zehn Zustände brauchen eher fünfundvierzig. Jeder neue Zustand muss mit jedem Zustand verdrahtet werden, von dem aus er erreichbar ist, und die Bedingungen überschneiden sich auf eine Weise, über die schwer nachzudenken ist. Wenn deine Übergangstabelle nicht mehr auf einen Bildschirm passt, ist das das Signal, zu einem Verhaltensbaum zu wechseln.
Eine konkrete FSM für eine patrouillierende Wache: Idle (2 s warten) → Patrouille (zum nächsten Wegpunkt gehen) → Alarm (sich zum Geräusch drehen, 1,5 s) → Verfolgung (sich mit Laufgeschwindigkeit zur zuletzt bekannten Position bewegen) → Angriff (wenn innerhalb von 2 m und Abklingzeit abgelaufen) → Suche (8 s lang in der Nähe der zuletzt bekannten Position umherwandern) → Patrouille. Sieben Zustände, etwa ein Dutzend Übergänge, und der Erinnerungstimer in der Suche ist das, was es zielgerichtet statt zittrig wirken lässt.
- Zustand als einzelne VariableDer aktuelle Zustand ist ein einzelnes Enum. Debuggen heißt, einen Wert zu lesen, nicht die Absicht aus einem Stack zu rekonstruieren.
- Hysterese bei jedem SchwellenwertJede Entfernung oder jeder Timer, der in beide Richtungen wechseln kann, braucht unterschiedliche Ein- und Austrittswerte, sonst oszilliert der NPC an der Grenze.
- ÜbergangsbudgetHalte die Zustände unter etwa zehn, bevor die Tabelle nicht mehr wartbar ist. Darüber hinaus wechsle zu einem Verhaltensbaum oder teile sie in Untermaschinen auf.
Verhaltensbäume
Ein Verhaltensbaum ersetzt die Übergangstabelle durch eine Hierarchie. Blattknoten sind Aktionen (MoveTo, PlayAnimation, Wait) und Bedingungen (IsPlayerVisible, HealthBelow). Zwei zusammengesetzte Knotentypen erledigen den Großteil der Arbeit. Ein Selektor probiert seine Kinder der Reihe nach aus und liefert Erfolg, sobald eines erfolgreich ist — das ist deine Prioritätsliste. Eine Sequenz führt ihre Kinder der Reihe nach aus und scheitert, sobald eines scheitert — das ist deine Schritt-für-Schritt-Prozedur.
Der Skalierungsgewinn ist die Komponierbarkeit. Ein neues Verhalten hinzuzufügen bedeutet, einen Unterbaum unter einem Selektor hinzuzufügen, nicht einen neuen Zustand mit jedem Zustand zu verdrahten, von dem aus er erreichbar ist. Prioritäten sind in der Reihenfolge der Kinder explizit, was weitaus leichter zu lesen ist als eine Menge sich überschneidender boolescher Bedingungen. Unterbäume lassen sich wiederverwenden: Derselbe Zweig „Geräusch untersuchen" funktioniert für eine Wache, einen Hund und einen Zivilisten.
Bäume sind nicht automatisch schneller. Eine naive Implementierung wertet bei jedem Tick den gesamten Baum neu aus, weshalb du weiterhin nach dem Entscheidungszeitplan von 100–250 ms tickst und die aktuell laufende Aktion die Bewegung in jedem Frame steuern lässt. Lang laufende Aktionen brauchen einen Running-Zustand, den sich der Baum merkt, sonst startet eine Aktion in jedem Tick von vorn und der NPC rutscht an den Anfang seines Pfades zurück. Die meisten Engines, die einen Verhaltensbaum mitliefern — Unitys Behavior-Paket, der integrierte Baum von Unreal oder ein kleiner eigener in TypeScript — lösen das mit einem Running-Rückgabewert, aber genau das ist das Erste, was du prüfen solltest, wenn ein NPC stockt.
Eine Selektorstruktur für einen Kampf-NPC, in Prioritätsreihenfolge: (1) wenn die Gesundheit unter 25 Prozent liegt, Sequenz [Flucht zur Deckung, Heilung]; (2) wenn der Spieler sichtbar und innerhalb von 2 m ist, Sequenz [Zum Spieler drehen, Angriff]; (3) wenn der Spieler sichtbar ist, Sequenz [Zur Ausweichposition bewegen, Angriff]; (4) wenn die zuletzt bekannte Position gesetzt ist, Suche; (5) Patrouille. Der Selektor wählt den ersten Zweig, dessen Bedingung erfüllt ist. Diese Kinder umzusortieren ist der Weg, die Aggressivität einzustellen, ohne Code anzufassen.
- SelektorknotenLiefert Erfolg beim ersten Kind, das erfolgreich ist. Verwende ihn für Fallback-Prioritäten: Probiere das spezifischste Verhalten, dann das nächste, dann das Standardverhalten.
- SequenzknotenScheitert beim ersten Kind, das scheitert. Verwende ihn für geordnete Prozeduren, bei denen Schritt zwei sinnlos ist, wenn Schritt eins nicht stattgefunden hat.
- Running-ZustandLange Aktionen müssen Running zurückgeben und dort fortsetzen, wo sie aufgehört haben. Wenn sie sofort Success zurückgeben, startet der Baum sie in jedem Tick neu, und die Bewegung bricht.
- BlackboardEin gemeinsam genutzter Schlüssel-Wert-Speicher für Fakten, die der Baum liest und schreibt — Zielposition, Alarmstufe, Ablauf der Abklingzeit. Halte ihn klein und typisiert.
GOAP (zielorientierte Aktionsplanung)
GOAP dreht das Modell um. Statt zu beschreiben, wann übergegangen wird, beschreibst du, was Aktionen tun und was sie erfordern, und ein Planer sucht eine Sequenz, die ein Ziel erreicht. Jede Aktion hat Vorbedingungen, Effekte und Kosten. „Attack" erfordert HasWeapon und InRange und setzt TargetDead. „GetWeapon" erfordert WeaponAvailable und setzt HasWeapon. „MoveToTarget" erfordert TargetKnown und setzt InRange. Der Planer beginnt beim Ziel, arbeitet rückwärts und findet die günstigste Aktionskette, deren Effekte es erfüllen.
Das zahlt sich aus, wenn der Weltzustand reichhaltig und die richtige Sequenz nicht offensichtlich ist. Ein NPC, der die Sichtlinie unterbrechen, nachladen, sich neu positionieren und dann angreifen muss, erzeugt einen Plan, den der Designer nie explizit geschrieben hat, weil der Planer die Reihenfolge aus dem Vorbedingungsgraphen entdeckt hat. Es zahlt sich auch in der Produktion aus, denn das Hinzufügen eines neuen Gegenstands oder einer neuen Fähigkeit bedeutet, eine Aktionsdefinition hinzuzufügen, statt jede Zustandsmaschine zu bearbeiten, die davon wissen sollte.
Die Kosten sind die Suche. Ein naiver Planer erkundet Aktionskombinationen, bis er einen Plan findet, was explodieren kann, wenn du Aktionen und Zustandsvariablen hinzufügst. Die übliche Abhilfe ist eine Heuristik — A* über den Zustandsgraphen mit einer Entfernungsschätzung zum Ziel — plus ein striktes Expansionslimit. In unseren eigenen Browser-Kontexten werden Pläne im Entscheidungs-Tick mit einer Obergrenze von einigen hundert Knotenexpansionen berechnet, und alles, was sie überschreitet, fällt auf einen zwischengespeicherten Plan oder einen einfachen Verhaltensbaum-Zweig zurück. Die ehrliche Grenze ist diese: GOAP ist die Suchkosten wert, wenn die Anzahl bedeutungsvoller Aktionssequenzen groß ist, und verschwendeter Aufwand, wenn der NPC immer nur drei Dinge tut.
- Vorbedingungen und EffekteJede Aktion erklärt, was wahr sein muss, um ausgeführt zu werden, und was sie ändert. Der Planer schließt über diese Deklarationen, nicht über Code.
- Rückwärtssuche vom ZielBeginne beim gewünschten Zustand und finde Aktionen, deren Effekte ihn erzeugen, und recurse dann auf ihre Vorbedingungen. Meist günstiger als die Vorwärtssuche.
- ExpansionslimitBegrenze die Knotenexpansionen pro Planungsaufruf. Überschreite die Obergrenze und falle zurück — lass einen Planer niemals ein Frame blockieren.
- Plan-CachingPlane nur neu, wenn sich der Weltzustand bedeutsam ändert. Statische Situationen verwenden den vorherigen Plan kostenlos wieder.
Utility-AI
Utility-AI bewertet jede Option und wählt die höchste. Jede Kandidatenaktion hat eine Funktion, die den Weltzustand auf eine Zahl abbildet, meist auf den Bereich null bis eins normalisiert. „Flucht" erzielt einen hohen Wert, wenn die Gesundheit niedrig und Feinde in der Nähe sind. „Angriff" erzielt einen hohen Wert, wenn das Ziel in Reichweite und die Munition über null ist. „Umherwandern" erzielt einen mittleren Wert, wenn nichts anderes drängt. Der NPC nimmt das Argmax, optional mit einem kleinen zufälligen Rauschen, damit Gleichstände unvorhersehbar aufgelöst werden.
Das ist das Modell, das die überraschendsten Charaktere hervorbringt, weil Verhalten aus dem Zusammenspiel von Kurven statt aus expliziten Regeln entsteht. Zwei NPCs mit leicht unterschiedlichen Bewertungsgewichten verhalten sich merklich unterschiedlich, ohne jeglichen Verzweigungscode, was es zur natürlichen Wahl für Persönlichkeitsvariation macht. Es ist auch das am einfachsten zur Laufzeit abstimmbare Modell: Lege die Gewichte in einem Debug-Panel offen und ziehe sie, während das Spiel läuft.
Die Fehlerart ist eine flache oder verrauschte Utility. Wenn jede Aktion 0,4 erreicht, wirkt der NPC unentschlossen und wechselt ständig. Wenn der höchste Wert in jedem Tick zwischen zwei nahen Werten umschlägt, erhältst du dieselbe Oszillation wie bei einer schlechten Zustandsmaschine. Die übliche Lösung ist ein Bindungsfenster: Sobald eine Aktion gewählt ist, binde sie mindestens für eine feste Dauer — 500 ms ist ein vernünftiger Standard — es sei denn, der höchste Wert übersteigt den Wert der gebundenen Aktion um eine bedeutsame Spanne. Auch Kurven sind wichtig. Lineare Antwortkurven bei der Gesundheit erzeugen einen NPC, der in jedem Kampf genau im selben Moment flieht. Exponentielle oder logistische Kurven erzeugen Schwellenwerte, die sich mit der Situation verschieben.
- Bewertungsfunktionen pro AktionJede Aktion bildet den Weltzustand auf eine Utility von null bis eins ab. Halte sie unabhängig, damit eine neu abgestimmt werden kann, ohne die anderen anzufassen.
- BindungsfensterSobald gewählt, halte eine Aktion für eine Mindestdauer. Verhindert das Umschlagen pro Tick zwischen nahezu gleichen Werten.
- Abstimmbare GewichteLege die Gewichte live offen. Zwei NPCs, die sich nur in den Gewichten unterscheiden, wirken wie verschiedene Charaktere, was das günstigste verfügbare Persönlichkeitssystem ist.
- AntwortkurvenLineare Kurven erzeugen feste Schwellenwerte. Logistische Kurven lassen den NPC auf die Situation reagieren statt auf eine magische Zahl.
Navigation und Kollision
Die Entscheidungslogik wählt ein Ziel. Die Navigation bringt den Körper dorthin. Ein Navmesh ist eine triangulierte begehbare Oberfläche; die Pfadfindung führt A* über ihren Polygongraphen aus, und das Ergebnis ist eine Liste von Polygonkanten, denen der NPC folgt. Wir bauen unseren zur Ladezeit aus der Levelgeometrie und speichern ihn im Cache, denn das Neuerstellen in jedem Frame ist der einfachste Weg, die Bildrate in einem Browserspiel zu ruinieren.
Steering übernimmt die letzten paar Meter und die Bewegung von Moment zu Moment. Seek und Arrive erzeugen den Vorwärtsvektor, Separation schiebt den NPC von nahen Agenten weg, und Hindernisvermeidung biegt den Pfad um dynamische Blocker wie eine Tür oder einen anderen Charakter. Führe Steering in jedem Frame aus; führe die Pfadfindung nur aus, wenn sich das Ziel ändert oder der Pfad ungültig wird. Ein in jedem Frame für jeden NPC neu berechneter Pfad ist ein häufiger und vermeidbarer Kostenfaktor.
Kollision ist der Durchgang, der einen NPC davon abhält, durch eine Wand zu laufen. Das Navmesh hält den Pfad auf begehbarem Boden, aber Steering und Physik können einen Charakter trotzdem in die Geometrie drücken, also löst der Charakter-Controller die Durchdringung nach der Bewegung gegen Collider auf. In unseren isometrischen Welten — Mini World und The Cyclops' Island — entstehen hier die meisten sichtbaren NPC-Bugs: ein Pfad, der sich durch eine Lücke zwängt, die schmaler ist als der Charakterradius, oder ein Steering-Vektor, der einen Agenten an einer Ecke in eine Wand drückt. Den Agentenradius auf dem Navmesh korrekt einzustellen und die Separationskraft unter der Ausschubstärke des Controllers zu halten, behebt den Großteil davon.
01
Backe das Navmesh zur Ladezeit aus der statischen Geometrie und speichere es im Cache. Erstelle es nicht pro Frame neu.
02
Setze Agentenradius und -höhe passend zur tatsächlichen Kollisionskapsel des Charakters, nicht zur Bounding-Box des Modells.
03
Führe die Pfadfindung nur aus, wenn sich das Ziel ändert, der Pfad blockiert ist oder der NPC um mehr als einen Schwellenwert vom Pfad abgewichen ist.
04
Führe Steering, Separation und Hindernisvermeidung in jedem Frame mit einer begrenzten Separationskraft aus.
05
Löse die Kollision nach der Bewegung gegen Collider auf und raste den Charakter dann wieder auf das Navmesh ein.
06
Protokolliere und visualisiere den Pfad und das Navmesh in einem Debug-Overlay — die Bugs sind meist räumlich, nicht logisch.
LLM-gesteuerte NPCs
Ein Sprachmodell verändert das Bild auf der Ebene von Absicht und Dialog, nicht auf der Navigationsebene. Pfadfindung, Steering und Kollision bleiben genau das, was sie waren, denn ein Modell, das Hunderte Millisekunden zum Antworten braucht, kann keinen Körper steuern, der sich in jedem Frame bewegen muss. Was das Modell tun kann, ist zu entscheiden, welches Ziel angesichts der Worte eines Spielers Sinn ergibt, Antworttext in der Rolle zu erzeugen und zwischen Optionen auf hoher Ebene zu wählen, wenn die Situation mehrdeutig ist und die handgeschriebenen Regeln etwas Flaches hervorbringen würden.
Eine funktionierende Architektur trennt die beiden Geschwindigkeiten. Unmittelbare Reaktionen laufen auf der Zustandsmaschine oder dem Utility-Bewerter — Angriff, Flucht, in Deckung gehen —, weil sie innerhalb von ein oder zwei Frames geschehen müssen. Die überlegte Absicht läuft in einer viel langsameren Kadenz durch das Modell, vielleicht einmal pro Interaktion oder alle paar Sekunden, und ihre Ausgabe ist ein strukturierter Befehl, den die schnelle Ebene ausführt: Ziel auf Folgen setzen, Haltung gegenüber dem Spieler auf Feindselig setzen, Dialogzeile setzen. Das Modell berührt niemals direkt die Position.
Die ehrlichen Grenzen sind Latenz, Kosten und Determinismus. Die Antwortzeiten sind lang genug, dass der NPC die Eingabe sofort mit einer Geste oder einer kurzen vorgefertigten Zeile bestätigen muss, sonst wirkt er kaputt. Die Kosten skalieren damit, wie oft du aufrufst, also bündelst und cachest du. Und weil die Ausgabe nicht deterministisch ist, musst du sie gegen ein Schema validieren, bevor sie das Spiel erreicht — eine Freitextantwort, die sich nicht parsen lässt, sollte auf eine Standardzeile zurückfallen und nicht das Dialogsystem zum Absturz bringen. Behandle das Modell als einen unzuverlässigen Dienst hinter einer typisierten Schnittstelle, dann hält die Integration.
- Typisierte BefehlsausgabeDas Modell gibt strukturierte Absicht zurück, keine Prosa, die der Spielcode parst. Validiere gegen ein Schema und weise alles Fehlerhafte zurück.
- Zwei-Geschwindigkeiten-SchleifeSchnelle lokale Logik für Reaktionen, langsame Modellaufrufe für die Überlegung. Lege niemals einen Netzwerk-Roundtrip auf den Bewegungspfad.
- Unmittelbare BestätigungSpiele eine Geste oder eine kurze vorgefertigte Zeile ab, sobald die Eingabe eintrifft, und ersetze sie, wenn das Modell antwortet. Verbirgt die Latenz.
- Sanfter FallbackWenn der Aufruf fehlschlägt oder ausläuft, falle auf die Standardantwort der Zustandsmaschine zurück. Der NPC darf niemals warten und hängen bleiben, während er auf ein Modell wartet.
Betritt die Welt, zu der es gehört
Ein Sprite ist ein Charakter ohne Platz zum Stehen. Der letzte Schritt ist der, den sonst niemand bietet: Nimm das gerade erstellte Sheet und setze es in eine Welt, in der jemand herumlaufen kann — eine kleine Insel, einen Planeten, eine regnerische Straßenecke. Jede Welt auf dieser Seite wurde aus einer einzigen geschriebenen Beschreibung erstellt und als spielbare Seite veröffentlicht.
Zurück zum 3D Game Builder
Alle 31 fertigen 3D-Welten, Spiele und Szenen versammeln sich auf einer Seite — jede einzelne spielbar.