行為樹的 JavaScript 實作。它們對於實施人工智慧很有用。如果您需要有關行為樹的更多信息,請查看 GameDevAIPro,其中有一篇來自 Alex Champandard 和 Philip Dunstan 的關於行為樹的好文章。
如果您需要 TypeScript 支持,請查看即將發布的 3.0 版本的typescript
分支,如果您有任何問題和意見,請告知。
如果您使用 npm:
npm install behaviortree
或使用紗線:
yarn add behaviortree
該包沒有自己的依賴項。
首先,我應該要提到的是,也可以在像 Node v8 這樣的 common-js 環境中使用這個函式庫。為此,您應該將所有import
語句切換為require()
語句。
所以而不是
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } from 'behaviortree'
只需使用
const { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } = require ( 'behaviortree' )
我使用新的 ES 模組語法,因為我認為它非常可讀。所以所有的程式碼都是這樣寫的。若要查看兩個版本的工作範例,請存取/克隆範例的儲存庫。
任務是一個簡單的Node
(準確地說是葉節點),它負責處理其run
方法中的所有髒工作,該方法傳回true
、 false
或"running"
。為了清楚起見並保持靈活性,請使用提供的匯出常數作為這些傳回值( SUCCESS
、 FAILURE
、 RUNNING
)。
任務的每個方法都會接收黑板,該黑板是您在實例化行為樹時分配的。黑板基本上是一個對象,它保存所有任務執行其工作以及與世界通信所需的數據和方法。
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
}
} )
方法:
start
- 在呼叫 run 之前呼叫。但如果任務在以 this.running() 結束後恢復,則不會end
- 在調用 run 後調用。但如果任務透過 this.running() 完成則不會run
- 包含您希望任務執行的主要操作Sequence
將依序呼叫其每個子節點,直到一個節點失敗(返回FAILURE
)或呼叫所有節點。如果一個節點呼叫失敗,則Sequence
將傳回FAILURE
本身,否則它將呼叫SUCCESS
。
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
]
} )
Selector
呼叫其清單中的每個節點,直到一個節點返回SUCCESS
,然後自身返回成功。如果它的子節點都沒有呼叫SUCCESS
則選擇器會傳回FAILURE
。
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
]
} )
Random
選擇器只是隨機呼叫其子節點之一,如果返回RUNNING
,它將在下次運行時再次呼叫。
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
]
} )
創建行為樹的實例相當簡單。只需實例化BehaviorTree
類別並使用上面提到的節點和節點可以使用的黑板來指定樹的形狀。
import { BehaviorTree } from 'behaviortree'
var bTree = new BehaviorTree ( {
tree : mySelector ,
blackboard : { }
} )
您指定的blackboard
將作為第一個參數傳遞到每個start()
、 end()
和run()
方法。您可以使用它,讓行為樹知道它正在哪個物件(例如人工玩家)上運行,讓它與世界互動或根據需要保存狀態位元。要運行樹,只要您有時間在遊戲循環中進行一些 AI 計算,您就可以呼叫step()
。
bTree . step ( )
BehaviourTree 附帶一個內部註冊表,您可以在其中註冊任務,然後按您選擇的名稱在節點中引用它們。如果您需要在多個樹中執行相同的行為,或者想要將任務的定義和樹的構造分開,這非常方便。
// Register a task:
BehaviorTree . register ( 'testtask' , myTask )
// Or register a sequence or priority:
BehaviorTree . register ( 'test sequence' , mySequence )
現在您可以在節點中簡單地引用它,例如:
import { Selector } from 'behaviortree'
const mySelector = new Selector ( {
nodes : [ 'my awesome task' , 'another awe# task to do' ]
} )
使用註冊表還有一個好處,對於只有一種run
方法的簡單任務,有一個簡短的方法可以編寫它們:
BehaviorTree . register ( 'testtask' , ( blackboard ) => {
console . log ( 'I am doing stuff' )
return SUCCESS
} )
現在是一個如何協同工作的例子。
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 )
在這個例子中,會發生以下情況:每次經過setInterval
(我們的遊戲循環)時,狗都會吠叫——我們用註冊節點實現了這一點,因為我們這樣做了兩次——然後它隨機走動,然後再次吠叫,然後如果它發現自己站在一棵樹旁邊,它在樹上撒尿。
每個節點也可以是Decorator
,它包裝一個常規(或另一個裝飾)節點,並控制它們的值或呼叫、添加一些條件或對其返回的狀態執行某些操作。在src/decorators
目錄中,您會發現一些已經實現的裝飾器以供靈感或使用,例如InvertDecorator
可以否定裝飾節點的回傳值,或者CooldownDecorator
可以確保節點在冷卻停機時間內僅被調用一次。
const decoratedSequence = new InvertDecorator ( {
node : 'awesome sequence doing stuff'
} )
創建一個自己的裝飾器。您只需要一個擴展Decorator
類別並重寫裝飾方法的類別。只需查看src/decorators
子資料夾即可檢查一些參考實作。
請注意,您不能簡單地實例化Decorator
類別並將decorate
方法作為藍圖作為動態裝飾器傳遞,因為現在的工作方式。
為了您的方便,已經建造了幾個“簡單”裝飾器。檢查src/decorators
目錄以獲取更多詳細資訊(以及它們正在執行的操作的規格)。使用它們非常簡單:
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE , decorators } from 'behaviortree'
const { AlwaysSucceedDecorator } = decorators
定義了一個BehaviorTreeImporter
類,可用來從樹的JSON 定義中填入BehaviorTree
實例。定義結構如下圖所示:
{
"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 " }
]
}
透過type
屬性,匯入器從內部類型定義中尋找Decorators
、 Selectors
、 Sequences
和您自己定義的類,以及從BehaviorTree
註冊表中尋找任務,並傳回一個對象,該物件可以在BehaviorTree
建構函數中用作tree
。
如果您不喜歡新的import
語句,您仍然應該能夠使用傳統的require
語句:
const {
BehaviorTree ,
Sequence ,
Task ,
SUCCESS ,
FAILURE ,
decorators : { AlwaysSucceedDecorator }
} = require ( 'behaviortree' )
您可以將introspector
參數新增至包含Introspector
類別或實作類似介面的另一個類別的實例的step
。這樣做可以讓您收集有關行為樹每次運行的有用統計數據/數據,並向您顯示哪些任務確實運行並返回了哪些結果。有助於了解樹的正確性。
但不要在生產環境中執行此操作,因為在那裡完成的工作根本不需要定期評估。
const { Introspector } = require ( 'behaviortree' )
const introspector = new Introspector ( )
bTree . step ( { introspector } )
console . log ( introspector . lastResult )
這會導致類似的結果:
{
name : 'select' ,
result : Symbol ( running ) ,
children : [
{
name : 'targeting' ,
result : false
} ,
{
name : 'jump' ,
result : Symbol ( running )
}
]
}
你想貢獻嗎?如果您有任何想法或批評,請在 GitHub 上提出問題。如果你想親自動手,可以分叉這個儲存庫。但請注意:如果您編寫程式碼,請不要忘記編寫測試。然後提出拉取請求。我很高興看到接下來會發生什麼。
測試是用jest完成的,我使用yarn來管理套件和鎖定版本。
yarn
yarn test
RUNNING
分支節點中開始和結束呼叫的問題版權所有 (C) 2013-2020 喬治‧塔沃尼斯
特此免費授予任何獲得本軟體和相關文件文件(「軟體」)副本的人不受限制地使用本軟體,包括但不限於使用、複製、修改、合併的權利、發布、分發、再授權和/或銷售軟體的副本,並允許向其提供軟體的人員這樣做,但須滿足以下條件:
上述版權聲明和本授權聲明應包含在本軟體的所有副本或主要部分中。
本軟體以「現況」提供,不提供任何明示或暗示的保證,包括但不限於適銷性、特定用途的適用性和不侵權的保證。 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE軟體.