Verhaltensbäume für Unity3D -Projekte. Geschrieben mit einem Code -gesteuerten Ansatz, um die Wartbarkeit bei großen Projekten mit dem Builder -Muster zu maximieren. Inspiriert von einem fließenden Verhaltensbaum.
Merkmale
Unterstützung
Treten Sie der Discord -Community bei, wenn Sie Fragen haben oder Hilfe benötigen.
Siehe bevorstehende Funktionen und Entwicklungsfortschritte im Trello Board.
Beim Erstellen von Bäumen müssen Sie sie in einer Variablen speichern, um alle erforderlichen Daten ordnungsgemäß zu speichern.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Trees ;
public class MyCustomAi : MonoBehaviour {
[ SerializeField ]
private BehaviorTree _tree ;
private void Awake ( ) {
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Condition ( " Custom Condition " , ( ) => {
return true ;
} )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
}
private void Update ( ) {
// Update our tree every frame
_tree . Tick ( ) ;
}
}
Je nachdem, was Sie für einen Aufgabenstatus zurückgeben, passieren verschiedene Dinge.
tree.Tick()
startet den Baum neu, wenn keine anderen Knoten ausgeführt werden sollentree.Tick()
weiterleiten. Eine Zeigerreferenz wird vom Baum verfolgt und kann nur gelöscht werden, wenn tree.Reset()
aufgerufen wird. Solange Ihre Baumspeichervariable auf public
eingestellt ist oder über ein SerializeField
-Attribut verfügt. Sie können eine Visualisierung Ihres Baumes drucken, während das Spiel im Editor ausgeführt wird. Beachten Sie, dass Sie Bäume nicht anzeigen können, während das Spiel nicht läuft. Da der Baum gebaut werden muss, um sich zu visualisieren.
Sie können Ihren Verhaltensbäumen mit mehreren Zeilen sicher neuer Code hinzufügen. Ermöglichen Sie, BTS anzupassen und zukünftige Versionen zu unterstützen.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
using CleverCrow . Fluid . BTs . Trees ;
public class CustomAction : ActionBase {
protected override TaskStatus OnUpdate ( ) {
Debug . Log ( Owner . name ) ;
return TaskStatus . Success ;
}
}
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction ( this BehaviorTreeBuilder builder , string name = " My Action " ) {
return builder . AddNode ( new CustomAction { Name = name } ) ;
}
}
public class ExampleUsage : MonoBehaviour {
public void Awake ( ) {
var bt = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. CustomAction ( )
. End ( ) ;
}
}
Fluid Verhaltensbaum wird über den Paketmanager von Unity verwendet. Um es zu verwenden, müssen Sie Ihren Packages/manifest.json
-Datei die folgenden Zeilen hinzufügen. Danach können Sie in der Einheit visuell steuern, welche spezifische Version des Fluid -Verhaltensbaums Sie aus dem Fenster Package Manager verwenden. Dies muss erledigt werden, damit Ihr Unity -Editor eine Verbindung zur Paketregistrierung von NPM herstellen kann.
{
"scopedRegistries" : [
{
"name" : " NPM " ,
"url" : " https://registry.npmjs.org " ,
"scopes" : [
" com.fluid "
]
}
],
"dependencies" : {
"com.fluid.behavior-tree" : " 2.2.0 "
}
}
Archive mit bestimmten Versionen und Versionshinweise finden Sie auf der Seite "Releases".
Möglicherweise möchten Sie sich das Capture the Flag -Beispielprojekt ansehen, um ein funktionierendes Beispiel dafür zu erhalten, wie flüssiges Verhaltensbaum in Ihrem Projekt verwendet werden kann. Es zeigt in der Echtzeit -Nutzung bei Einheiten, die versuchen, die Flagge zu erfassen, während sie Power -Ups ergreifen, um zu versuchen, die Oberhand zu gewinnen.
Möglicherweise möchten Sie sich das Capture the Flag -Beispielprojekt ansehen, um ein funktionierendes Beispiel dafür zu erhalten, wie flüssiges Verhaltensbaum in Ihrem Projekt verwendet werden kann. Es zeigt in der Echtzeit -Nutzung bei Einheiten, die versuchen, die Flagge zu erfassen, während sie Power -Ups ergreifen, um zu versuchen, die Oberhand zu gewinnen.
Fluid Verhaltensbaum verfügt über eine robuste Bibliothek vorgefertigter Aktionen, Bedingungen, Verbundwerkstoffe und anderen Knoten, um Ihren Entwicklungsprozess zu beschleunigen.
Sie können eine generische Aktion im laufenden Fliegen erstellen. Wenn Sie dieselben Aktionen wiederverwenden möchten, möchten Sie möglicherweise den Abschnitt zum Schreiben Ihrer eigenen Maßnahmen untersuchen.
. Sequence ( )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
Überspringen Sie eine Reihe von Zecken auf den Verhaltensbaum.
. Sequence ( )
// Wait for 1 tick on the tree before continuing
. Wait ( 1 )
. Do ( MyAction )
. End ( )
Waits, bis die bestandene Anzahl von Sekunden in deltaTime
abgelaufen ist.
. Sequence ( )
. WaitTime ( 2.5f )
. Do ( MyAction )
. End ( )
Sie können im laufenden Fliegen einen generischen Zustand erstellen. Wenn Sie dieselben Aktionen wiederverwenden möchten, möchten Sie möglicherweise den Abschnitt zum Schreiben Ihrer eigenen benutzerdefinierten Bedingungen untersuchen.
. Sequence ( )
. Condition ( " Custom Condtion " , ( ) => {
return true ;
} )
. Do ( MyAction )
. End ( )
Bewerten Sie zufällig einen Knoten als wahr oder falsch, basierend auf der bestandenen Chance.
. Sequence ( )
// 50% chance this will return success
. RandomChance ( 1 , 2 )
. Do ( MyAction )
. End ( )
Fährt jeden untergeordneten Knoten in Ordnung und erwartet, dass ein Erfolgsstatus den nächsten Knoten ankreuzt. Wenn ein Fehler zurückgegeben wird, hört die Sequenz auf, untergeordnete Knoten auszuführen und den übergeordneten Versagen zurückzugeben.
Beachten Sie, dass es wichtig ist, dass auf jeden Verbund eine Erklärung von A .End()
folgt. Dies stellt sicher, dass Ihre Knoten beim Bau des Baumes ordnungsgemäß verschachtelt sind.
. Sequence ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
// All tasks after this will not run and the sequence will exit
. Do ( ( ) => { return TaskStatus . Failure ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Läuft jeden Kinderknoten, bis der Erfolg zurückgegeben ist.
. Selector ( )
// Runs but fails
. Do ( ( ) => { return TaskStatus . Failure ; } )
// Will stop here since the node returns success
. Do ( ( ) => { return TaskStatus . Success ; } )
// Does not run
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Wählt zufällig einen untergeordneten Knoten mit einem Shuffle -Algorithmus aus. Sieht aus, bis Success
zurückgegeben wird oder jeder Knoten fehlschlägt. Jedes Mal, wenn der Baum anfänglich anfängt, ihn zu laufen.
. SelectorRandom ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. Do ( ( ) => { return TaskStatus . Success ; } )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
Fährt alle Kinderknoten gleichzeitig aus, bis sie alle den Erfolg zurückgeben. Beendet und stoppt alle laufenden Knoten, wenn einer von ihnen einen Fehler zurückgibt.
. Parallel ( )
// Both of these tasks will run every frame
. Do ( ( ) => { return TaskStatus . Continue ; } )
. Do ( ( ) => { return TaskStatus . Continue ; } )
. End ( )
Dekorateure sind übergeordnete Elemente, die einen Knoten einwickeln, um den Rückgabewert zu ändern (oder spezielle Logik auszuführen). Sie sind extrem mächtig und ein großes Kompliment für Aktionen, Bedingungen und Verbundwerkstoffe.
Sie können jeden Knoten mit Ihrem eigenen benutzerdefinierten Dekorateurcode einwickeln. Auf diese Weise können Sie wiederverwendbare Funktionen anpassen.
Hinweis : Sie müssen Update()
auf dem untergeordneten Knoten manuell anrufen, sonst wird es nicht abgefeuert. Außerdem muss jeder Dekorateur eine Aussage von .End()
folgen. Andernfalls baut der Baum nicht richtig.
. Sequence ( )
. Decorator ( " Return Success " , child => {
child . Update ( ) ;
return TaskStatus . Success ;
} )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
Wechseln Sie den zurückgegebenen Status des untergeordneten Knotens um, wenn es TaskStatus.Success
oder TaskStatus.Failure
ist. Ändert nicht TaskStatus.Continue
.
. Sequence ( )
. Inverter ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Success
, wenn das Kind TaskStatus.Failure
zurückgibt. Ändert nicht TaskStatus.Continue
.
. Sequence ( )
. ReturnSuccess ( )
. Do ( ( ) => { return TaskStatus . Failure ; } )
. End ( )
. End ( )
Return TaskStatus.Failure
, wenn das Kind TaskStatus.Success
zurückgibt. Ändert nicht TaskStatus.Continue
.
. Sequence ( )
. ReturnFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Continue
unabhängig davon, welcher Status das Kind zurückkehrt. Dieser Dekorateur (und alle Nachkommensaufgaben) können unterbrochen werden, indem Sie BehaviorTree.Reset()
bezeichnen.
. Sequence ( )
. RepeatForever ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Failure
Wenn das Kind TaskStatus.Failure
zurückgibt, gibt es TaskStatus.Continue
zurück.
. Sequence ( )
. RepeatUntilFailure ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Return TaskStatus.Success
Wenn das Kind TaskStatus.Success
zurückgibt, gibt es TaskStatus.Continue
zurück.
. Sequence ( )
. RepeatUntilSuccess ( )
. Do ( ( ) => { return TaskStatus . Success ; } )
. End ( )
. End ( )
Bäume können mit nur wenigen Codezeile kombiniert werden. Auf diese Weise können Sie injizierbare Verhaltensbäume erstellen, die verschiedene Knoten für komplexe Funktionen wie Suchen oder Angriffe bündeln.
Seien Sie gewarnt, dass Spleißbäume einen neu gebauten Baum für die Injektion erfordern, da Knoten nur tief auf .Build()
kopiert werden.
using CleverCrow . Fluid . BTs . Trees ;
using CleverCrow . Fluid . BTs . Tasks ;
using UnityEngine ;
public class MyCustomAi : MonoBehaviour {
private BehaviorTree _tree ;
private void Awake ( ) {
var injectTree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( ) ;
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Splice ( injectTree . Build ( ) )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
}
private void Update ( ) {
// Update our tree every frame
_tree . Tick ( ) ;
}
}
Was flüssiges Verhaltensbaum so mächtig macht, ist die Fähigkeit, Ihre eigenen Knoten zu schreiben und sie dem Bauherr hinzuzufügen, ohne eine Quelle zu bearbeiten. Sie können sogar Einheitspakete erstellen, die neue Builder -Funktionen hinzufügen. Zum Beispiel können wir eine neue Baumbuilder -Methode wie diese schreiben, die das Ziel Ihres KI -Systems mit nur wenigen Codezeilen festlegt.
var tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. AgentDestination ( " Find Enemy " , target )
. Do ( ( ) => {
// Activate chase enemy code
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
Es sollte ungefähr 3 Minuten dauern, um Ihre erste benutzerdefinierte Aktion zu erstellen und zu implementieren. Erstellen Sie zuerst eine neue Aktion.
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
using UnityEngine ;
using UnityEngine . AI ;
public class AgentDestination : ActionBase {
private NavMeshAgent _agent ;
public Transform target ;
protected override void OnInit ( ) {
_agent = Owner . GetComponent < NavMeshAgent > ( ) ;
}
protected override TaskStatus OnUpdate ( ) {
_agent . SetDestination ( target . position ) ;
return TaskStatus . Success ;
}
}
Als nächstes müssen wir das BehaviorTreeBuilder
-Skript mit unserer neuen AgentDestination -Aktion erweitern. Weitere Informationen zu C# -Klasserweiterungen finden Sie in den offiziellen Dokumenten.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder AgentDestination ( this BehaviorTreeBuilder builder , string name , Transform target ) {
return builder . AddNode ( new AgentDestination {
Name = name ,
target = target ,
} ) ;
}
}
Und du bist fertig! Sie haben jetzt eine benutzerdefinierte Aktion und einen erweiterbaren Verhaltensbaumbuilder erstellt, der für neue Versionen zukünftig geprüft ist. Die folgenden Beispiele werden eher gleich sein. Aber jeder deckt einen anderen Knotentyp ab.
Sie können Ihre eigenen benutzerdefinierten Aktionen mit der folgenden Vorlage erstellen. Dies ist nützlich, um Code zu bündeln, den Sie ständig verwenden.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
using CleverCrow . Fluid . BTs . Tasks . Actions ;
public class CustomAction : ActionBase {
// Triggers only the first time this node is run (great for caching data)
protected override void OnInit ( ) {
}
// Triggers every time this node starts running. Does not trigger if TaskStatus.Continue was last returned by this node
protected override void OnStart ( ) {
}
// Triggers every time `Tick()` is called on the tree and this node is run
protected override TaskStatus OnUpdate ( ) {
// Points to the GameObject of whoever owns the behavior tree
Debug . Log ( Owner . name ) ;
return TaskStatus . Success ;
}
// Triggers whenever this node exits after running
protected override void OnExit ( ) {
}
}
Fügen Sie Ihren neuen Knoten einer Erweiterung hinzu.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomAction ( this BehaviorTreeBuilder builder , string name = " My Action " ) {
return builder . AddNode ( new CustomAction {
Name = name ,
} ) ;
}
}
Mit der folgenden Beispielvorlage können benutzerdefinierte Bedingungen hinzugefügt werden. Sie möchten diese für Schecks wie Sehvermögen verwenden, wenn die KI an einen Ort wechseln kann, und andere Aufgaben, die eine komplexe Überprüfung erfordern.
using UnityEngine ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomCondition : ConditionBase {
// Triggers only the first time this node is run (great for caching data)
protected override void OnInit ( ) {
}
// Triggers every time this node starts running. Does not trigger if TaskStatus.Continue was last returned by this node
protected override void OnStart ( ) {
}
// Triggers every time `Tick()` is called on the tree and this node is run
protected override bool OnUpdate ( ) {
// Points to the GameObject of whoever owns the behavior tree
Debug . Log ( Owner . name ) ;
return true ;
}
// Triggers whenever this node exits after running
protected override void OnExit ( ) {
}
}
Fügen Sie den neuen Zustand Ihrem Verhaltensbaumbuilder mit dem folgenden Snippet hinzu.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomCondition ( this BehaviorTreeBuilder builder , string name = " My Condition " ) {
return builder . AddNode ( new CustomCondition {
Name = name ,
} ) ;
}
}
Fluidverhaltensbaum ist nicht nur auf benutzerdefinierte Aktionen und Bedingungen beschränkt. Sie können neue zusammengesetzte Typen mit einer ziemlich einfachen API erstellen. Hier ist ein Beispiel für eine Grundsequenz.
using CleverCrow . Fluid . BTs . TaskParents . Composites ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomSequence : CompositeBase {
protected override TaskStatus OnUpdate ( ) {
for ( var i = ChildIndex ; i < Children . Count ; i ++ ) {
var child = Children [ ChildIndex ] ;
var status = child . Update ( ) ;
if ( status != TaskStatus . Success ) {
return status ;
}
ChildIndex ++ ;
}
return TaskStatus . Success ;
}
}
Das Hinzufügen von benutzerdefinierten Verbundwerkstoffen zu Ihrem Verhaltensbaum ist genauso einfach wie das Hinzufügen von Aktionen. Nimmt nur eine Codezeile.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomSequence ( this BehaviorTreeBuilder builder , string name = " My Sequence " ) {
return builder . ParentTask < CustomSequence > ( name ) ;
}
}
Dekorateure können auch benutzerdefiniert sein, um sich wiederholte Code zu reduzieren.
using CleverCrow . Fluid . BTs . Decorators ;
using CleverCrow . Fluid . BTs . Tasks ;
public class CustomInverter : DecoratorBase {
protected override TaskStatus OnUpdate ( ) {
if ( Child == null ) {
return TaskStatus . Success ;
}
var childStatus = Child . Update ( ) ;
var status = childStatus ;
switch ( childStatus ) {
case TaskStatus . Success :
status = TaskStatus . Failure ;
break ;
case TaskStatus . Failure :
status = TaskStatus . Success ;
break ;
}
return status ;
}
}
Das Implementieren von Dekoratoren ähnelt Verbundwerkstoffen. Wenn Sie Argumente auf dem Komposit festlegen müssen, möchten Sie eine Beute für die Methode BehaviorTreeBuilder.AddNodeWithPointer()
einnehmen.
using CleverCrow . Fluid . BTs . Trees ;
public static class BehaviorTreeBuilderExtensions {
public static BehaviorTreeBuilder CustomInverter ( this BehaviorTreeBuilder builder , string name = " My Inverter " ) {
// See BehaviorTreeBuilder.AddNodeWithPointer() if you need to set custom composite data from arguments
return builder . ParentTask < CustomInverter > ( name ) ;
}
}
Wenn Sie ein automatisches Formatierer verwenden, wird dies wahrscheinlich Ihre Code -Formatierung mit der Buildersyntax beeinträchtigen. Um dies zu vermeiden, können Sie die Formatierung wie bei Jetbrains Rider ausschalten. Wenn Sie eine bestimmte IDE benötigen, sollte es nicht zu schwierig sein, die spezifische Formatierung des Deaktivierungskommentars zu googeln, die Sie benötigen.
// @formatter:off
_tree = new BehaviorTreeBuilder ( gameObject )
. Sequence ( )
. Condition ( " Custom Condition " , ( ) => {
return true ;
} )
. Do ( " Custom Action " , ( ) => {
return TaskStatus . Success ;
} )
. End ( )
. Build ( ) ;
// @formatter:on
Um auf nächtliche develop
zuzugreifen, müssen Sie Ihre Packages/manifest.json
als SO manuell bearbeiten.
{
"dependencies" : {
"com.fluid.behavior-tree" : " https://github.com/ashblue/fluid-behavior-tree.git#nightly "
}
}
Beachten Sie, dass Sie diese Zeile und alle zugehörigen Sperrdaten im Manifest löschen müssen, um die Einheit wieder aufzubauen und dann zurückzugeben, um einen neueren nächtlichen Build zu erhalten. Als Einheit sperrt den Commit Hash für Git -URLs als Pakete.
Wenn Sie die Entwicklungsumgebung ausführen möchten, müssen Sie node.js. Führen Sie dann einmal Folgendes aus der Wurzel aus.
npm install
Wenn Sie einen Build Run npm run build
aus dem Root erstellen möchten, wird der Ordner dist
fopuliert.
Alle Commits sollten mit dem Komitee erstellt werden (das beim Ausführen npm install
automatisch installiert wird). Commits werden automatisch zu Versionsnummern bei der Veröffentlichung zusammengestellt, sodass dies sehr wichtig ist. PRs, die keine Commitizen -basierten Commits haben, werden abgelehnt.
Um einen Commit -Typ aus der Wurzel in ein Terminal in ein Terminal zu machen
npm run commit
Weitere Informationen finden Sie im Dokument für den beitragenden Richtlinien.
Vielen Dank an diese wunderbaren Menschen (Emoji -Schlüssel):
Asche blau | Jesse Talavera-Greenberg | Puresaltproduktionen ? | Martin Duvergey ? | Callstack ? | Piotr Jastrzebski | Sounghoo |
Tnthomas ? | Ownez | Angstr0m ? | Izzy ? | Jeremyvansnick |
Dieses Projekt folgt der All-Contributors-Spezifikation. Beiträge jeglicher Art willkommen!
Vielen Dank an diese wunderbaren Menschen (Emoji -Schlüssel):
Dieses Projekt folgt der All-Contributors-Spezifikation. Beiträge jeglicher Art willkommen!