Eine JavaScript-Implementierung von Verhaltensbäumen. Sie sind nützlich für die Implementierung von KIs. Wenn Sie weitere Informationen zu Verhaltensbäumen benötigen, schauen Sie sich GameDevAIPro an. Dort gibt es einen schönen Artikel über Verhaltensbäume von Alex Champandard und Philip Dunstan.
Wenn Sie TypeScript-Unterstützung benötigen, sehen Sie sich bitte den typescript
Zweig an, der in der kommenden Version 3.0 enthalten sein wird. Wenn Sie Fragen und Kommentare haben, teilen Sie uns diese bitte mit.
Wenn Sie npm verwenden:
npm install behaviortree
oder mit Garn:
yarn add behaviortree
Dieses Paket hat keine eigenen Abhängigkeiten.
Zunächst sollte ich erwähnen, dass es möglich ist, diese Bibliothek auch in einer Common-JS-Umgebung wie Node v8 zu verwenden. Damit dies funktioniert, sollten Sie alle import
durch require()
-Anweisungen ersetzen.
Also statt
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } from 'behaviortree'
einfach verwenden
const { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } = require ( 'behaviortree' )
Ich verwende die neue Syntax der ES-Module, weil ich sie für sehr gut lesbar halte. Der gesamte Code ist also so geschrieben. Um funktionierende Beispiele beider Versionen zu sehen, besuchen/klonen Sie das Repository der Beispiele.
Eine Aufgabe ist ein einfacher Node
(genauer gesagt ein Blattknoten), der die gesamte Drecksarbeit in seiner run
erledigt, die entweder true
, false
oder "running"
zurückgibt. Aus Gründen der Übersichtlichkeit und aus Gründen der Flexibilität verwenden Sie bitte die bereitgestellten exportierten Konstanten für diese Rückgabewerte ( SUCCESS
, FAILURE
, RUNNING
).
Jede Methode Ihrer Aufgabe erhält das Blackboard, das Sie bei der Instanziierung des BehaviorTree zuweisen. Eine Tafel ist im Grunde ein Objekt, das Daten und Methoden enthält, die alle Aufgaben benötigen, um ihre Arbeit zu erledigen und mit der Welt zu kommunizieren.
import { Task , SUCCESS } from 'behaviortree'
const myTask = new Task ( {
// (optional) this function is called directly before the run method
// is called. It allows you to setup things before starting to run
start : function ( blackboard ) {
blackboard . isStarted = true
} ,
// (optional) this function is called directly after the run method
// is completed with either this.success() or this.fail(). It allows you to clean up
// things, after you run the task.
end : function ( blackboard ) {
blackboard . isStarted = false
} ,
// This is the meat of your task. The run method does everything you want it to do.
run : function ( blackboard ) {
return SUCCESS
}
} )
Die Methoden:
start
– Wird aufgerufen, bevor run aufgerufen wird. Aber nicht, wenn die Aufgabe fortgesetzt wird, nachdem sie mit this.running() beendet wurde.end
– Wird aufgerufen, nachdem run aufgerufen wurde. Aber nicht, wenn die Aufgabe mit this.running() beendet wurderun
– Enthält die wichtigsten Aufgaben, die die Aufgabe ausführen soll Eine Sequence
ruft nacheinander jeden ihrer Unterknoten auf, bis ein Knoten ausfällt ( FAILURE
zurückgibt) oder alle Knoten aufgerufen wurden. Wenn ein Knotenaufruf fehlschlägt, gibt die Sequence
selbst FAILURE
zurück, andernfalls ruft sie SUCCESS
auf.
import { Sequence } from 'behaviortree'
const mySequence = new Sequence ( {
nodes : [
// here comes in a list of nodes (Tasks, Sequences or Priorities)
// as objects or as registered strings
]
} )
Ein Selector
ruft jeden Knoten in seiner Liste auf, bis ein Knoten SUCCESS
zurückgibt und dann selbst als Erfolg zurückgegeben wird. Wenn keiner seiner Unterknoten SUCCESS
aufruft, gibt der Selektor FAILURE
zurück.
import { Selector } from 'behaviortree'
const mySelector = new Selector ( {
nodes : [
// here comes in a list of nodes (Tasks, Sequences or Priorities)
// as objects or as registered strings
]
} )
Ein Random
ruft einfach zufällig einen seiner Unterknoten auf. Wenn dieser RUNNING
zurückgibt, wird er beim nächsten Durchlauf erneut aufgerufen.
import { Random } from 'behaviortree'
const mySelector = new Random ( {
nodes : [
// here comes in a list of nodes (Tasks, Sequences or Priorities)
// as objects or as registered strings
]
} )
Das Erstellen einer Instanz eines Verhaltensbaums ist ziemlich einfach. Instanziieren Sie einfach die BehaviorTree
-Klasse und geben Sie die Form des Baums an, indem Sie die oben genannten Knoten und die Tafel verwenden, die die Knoten verwenden können.
import { BehaviorTree } from 'behaviortree'
var bTree = new BehaviorTree ( {
tree : mySelector ,
blackboard : { }
} )
Das von Ihnen angegebene blackboard
wird als erstes Argument an jede start()
, end()
und run()
Methode übergeben. Sie können es verwenden, um dem Verhaltensbaum mitzuteilen, auf welchem Objekt (z. B. einem künstlichen Spieler) er läuft, ihn mit der Welt interagieren zu lassen oder bei Bedarf Zustandsbits zu speichern. Um den Baum auszuführen, können Sie step()
aufrufen, wann immer Sie Zeit für einige KI-Berechnungen in Ihrer Spielschleife haben.
bTree . step ( )
BehaviorTree verfügt über eine interne Registrierung, in der Sie Aufgaben registrieren und sie später in Ihren Knoten anhand der von Ihnen gewählten Namen referenzieren können. Dies ist sehr praktisch, wenn Sie das gleiche Verhalten in mehreren Bäumen benötigen oder die Definition von Aufgaben und die Erstellung der Bäume trennen möchten.
// Register a task:
BehaviorTree . register ( 'testtask' , myTask )
// Or register a sequence or priority:
BehaviorTree . register ( 'test sequence' , mySequence )
Worauf Sie jetzt in Ihren Knoten einfach verweisen können, wie zum Beispiel:
import { Selector } from 'behaviortree'
const mySelector = new Selector ( {
nodes : [ 'my awesome task' , 'another awe# task to do' ]
} )
Die Verwendung der Registrierung hat noch einen weiteren Vorteil: Für einfache Aufgaben mit nur einer run
gibt es eine kurze Möglichkeit, diese zu schreiben:
BehaviorTree . register ( 'testtask' , ( blackboard ) => {
console . log ( 'I am doing stuff' )
return SUCCESS
} )
Und nun ein Beispiel dafür, wie alle zusammenarbeiten könnten.
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } from 'behaviortree'
BehaviorTree . register (
'bark' ,
new Task ( {
run : function ( dog ) {
dog . bark ( )
return SUCCESS
}
} )
)
const tree = new Sequence ( {
nodes : [
'bark' ,
new Task ( {
run : function ( dog ) {
dog . randomlyWalk ( )
return SUCCESS
}
} ) ,
'bark' ,
new Task ( {
run : function ( dog ) {
if ( dog . standBesideATree ( ) ) {
dog . liftALeg ( )
dog . pee ( )
return SUCCESS
} else {
return FAILURE
}
}
} )
]
} )
const dog = new Dog ( /*...*/ ) // the nasty details of a dog are omitted
const bTree = new BehaviorTree ( {
tree : tree ,
blackboard : dog
} )
// The "game" loop:
setInterval ( function ( ) {
bTree . step ( )
} , 1000 / 60 )
In diesem Beispiel passiert Folgendes: Bei jedem Durchlauf von setInterval
(unserer Spielschleife) bellt der Hund – wir haben dies mit einem registrierten Knoten implementiert, weil wir dies zweimal tun –, dann läuft er zufällig herum, dann bellt er erneut und dann, wenn er es tut steht neben einem Baum und pinkelt auf den Baum.
Jeder Knoten kann auch ein Decorator
sein, der einen regulären (oder einen anderen dekorierten) Knoten umhüllt und entweder seinen Wert oder Aufruf steuert, einige Bedingungen hinzufügt oder etwas mit seinem zurückgegebenen Zustand macht. Im Verzeichnis src/decorators
finden Sie einige bereits implementierte Dekoratoren zur Inspiration oder Verwendung, wie einen InvertDecorator
, der den Rückgabewert des dekorierten Knotens negiert, oder einen CooldownDecorator
der sicherstellt, dass der Knoten nur einmal innerhalb einer kühlen Ausfallzeit aufgerufen wird.
const decoratedSequence = new InvertDecorator ( {
node : 'awesome sequence doing stuff'
} )
So erstellen Sie einen eigenen Dekorateur. Sie benötigen lediglich eine Klasse, die die Decorator
-Klasse erweitert und die Dekorationsmethode überschreibt. Schauen Sie einfach im Unterordner src/decorators
nach, um einige Referenzimplementierungen zu überprüfen.
Beachten Sie, dass Sie nicht einfach die Decorator
-Klasse instanziieren und die decorate
als Blaupause als dynamischen Dekorator übergeben können, da die Dinge derzeit so funktionieren.
Für Ihre Bequemlichkeit sind bereits mehrere „einfache“ Dekoratoren erstellt worden. Schauen Sie im Verzeichnis src/decorators
nach, um weitere Einzelheiten zu erfahren (und die Spezifikationen für ihre Funktion). Ihre Verwendung ist so einfach wie:
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE , decorators } from 'behaviortree'
const { AlwaysSucceedDecorator } = decorators
Es ist eine BehaviorTreeImporter
Klasse definiert, die zum Füllen einer BehaviorTree
Instanz aus einer JSON-Definition für einen Baum verwendet werden kann. Eine Definitionsstruktur sieht so aus:
{
"type" : " selector " ,
"name" : " the root " ,
"nodes" : [
{
"type" : " ifEnemyInSight " ,
"name" : " handling enemies " ,
"node" : { "type" : " walk " , "name" : " go to enemy " }
},
{
"type" : " cooldown " ,
"name" : " jumping around " ,
"cooldown" : 1 ,
"node" : { "type" : " jump " , "name" : " jump up " }
},
{ "type" : " idle " , "name" : " doing nothing " }
]
}
Über die type
Eigenschaft sucht der Importer nach Decorators
, Selectors
, Sequences
und Ihren eigenen definierten Klassen aus einer internen Typdefinition sowie nach Aufgaben aus der BehaviorTree
Registrierung und gibt ein Objekt zurück, das als tree
im BehaviorTree
-Konstruktor verwendet werden kann.
Wenn Ihnen die neuen import
-Anweisungen nicht gefallen, sollten Sie weiterhin die herkömmlichen require
-Anweisungen verwenden können:
const {
BehaviorTree ,
Sequence ,
Task ,
SUCCESS ,
FAILURE ,
decorators : { AlwaysSucceedDecorator }
} = require ( 'behaviortree' )
Sie können der step
-Methode einen introspector
Parameter hinzufügen, der eine Instanz der Introspector
Klasse oder einer anderen Klasse enthält, die eine ähnliche Schnittstelle implementiert. Auf diese Weise können Sie nützliche Statistiken/Daten zu jeder Ausführung Ihres Verhaltensbaums sammeln und sehen, welche Aufgaben ausgeführt wurden und welche Ergebnisse zurückgegeben wurden. Nützlich, um ein Verständnis für die Richtigkeit des Baums zu erlangen.
Tun Sie dies jedoch nicht in einer Produktionsumgebung, da die dort geleistete Arbeit für eine regelmäßige Evaluierung einfach nicht erforderlich ist.
const { Introspector } = require ( 'behaviortree' )
const introspector = new Introspector ( )
bTree . step ( { introspector } )
console . log ( introspector . lastResult )
Das würde etwa Folgendes ergeben:
{
name : 'select' ,
result : Symbol ( running ) ,
children : [
{
name : 'targeting' ,
result : false
} ,
{
name : 'jump' ,
result : Symbol ( running )
}
]
}
Du möchtest einen Beitrag leisten? Wenn Sie Ideen oder Kritik haben, eröffnen Sie einfach ein Problem hier auf GitHub. Wenn Sie sich die Hände schmutzig machen möchten, können Sie dieses Repository forken. Aber beachten Sie: Wenn Sie Code schreiben, vergessen Sie nicht, Tests zu schreiben. Und stellen Sie dann eine Pull-Anfrage. Ich bin gespannt, was kommt.
Tests werden mit Jest durchgeführt und ich verwende Garn, um Pakete zu verwalten und Versionen zu sperren.
yarn
yarn test
RUNNING
VerzweigungsknotenCopyright (C) 2013-2020 Georg Tavonius
Hiermit wird jeder Person, die eine Kopie dieser Software und der zugehörigen Dokumentationsdateien (die „Software“) erhält, kostenlos die Erlaubnis erteilt, mit der Software ohne Einschränkung zu handeln, einschließlich und ohne Einschränkung der Rechte zur Nutzung, zum Kopieren, Ändern und Zusammenführen , Kopien der Software zu veröffentlichen, zu verteilen, unterzulizenzieren und/oder zu verkaufen und Personen, denen die Software zur Verfügung gestellt wird, dies zu gestatten, vorbehaltlich der folgenden Bedingungen:
Der obige Urheberrechtshinweis und dieser Genehmigungshinweis müssen in allen Kopien oder wesentlichen Teilen der Software enthalten sein.
DIE SOFTWARE WIRD „WIE BESEHEN“ ZUR VERFÜGUNG GESTELLT, OHNE JEGLICHE AUSDRÜCKLICHE ODER STILLSCHWEIGENDE GEWÄHRLEISTUNG, EINSCHLIESSLICH, ABER NICHT BESCHRÄNKT AUF DIE GEWÄHRLEISTUNG DER MARKTGÄNGIGKEIT, EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND NICHTVERLETZUNG. IN KEINEM FALL SIND DIE AUTOREN ODER URHEBERRECHTSINHABER HAFTBAR FÜR JEGLICHE ANSPRÜCHE, SCHÄDEN ODER ANDERE HAFTUNG, WEDER AUS EINER VERTRAGLICHEN HANDLUNG, AUS HANDLUNG ODER ANDERWEITIG, DIE SICH AUS, AUS ODER IN ZUSAMMENHANG MIT DER SOFTWARE ODER DER NUTZUNG ODER ANDEREN HANDELN IN DER SOFTWARE ERGEBEN SOFTWARE.