การใช้งาน JavaScript ของ Behavior Trees มีประโยชน์ในการนำ AIs ไปใช้ หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับ Behavior Trees โปรดดูที่ GameDevAIPro มีบทความดีๆ เกี่ยวกับ Behavior Trees จาก Alex Champandard และ Philip Dunstan
หากคุณต้องการการสนับสนุน TypeScript โปรดตรวจสอบสาขา typescript
ซึ่งจะเป็นรุ่น 3.0 ที่กำลังจะมาถึง และหากคุณมีคำถามและความคิดเห็น โปรดแจ้งให้ทราบ
หากคุณใช้ npm:
npm install behaviortree
หรือใช้เส้นด้าย:
yarn add behaviortree
แพ็คเกจนี้ไม่มีการพึ่งพาของตัวเอง
ก่อนอื่น ฉันควรจะพูดถึงว่ามันเป็นไปได้ที่จะใช้ไลบรารีนี้ในสภาพแวดล้อม js ทั่วไปเช่น node v8 เพื่อให้ได้ผล คุณควรสลับคำสั่ง import
ทั้งหมดด้วยคำสั่ง require()
ดังนั้นแทนที่จะ
import { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } from 'behaviortree'
เพียงแค่ใช้
const { BehaviorTree , Sequence , Task , SUCCESS , FAILURE } = require ( 'behaviortree' )
ฉันใช้ไวยากรณ์โมดูล ES ใหม่ เพราะฉันคิดว่ามันสามารถอ่านได้ดีมาก ดังนั้นโค้ดทั้งหมดจึงเขียนแบบนี้ หากต้องการดูตัวอย่างการทำงานของทั้งสองเวอร์ชัน โปรดไปที่/โคลน repo ของตัวอย่าง
งานเป็น Node
ธรรมดา (หรือโหนดปลายสุดที่แม่นยำ) ซึ่งดูแลงานสกปรกทั้งหมดในวิธี run
ซึ่งส่งคืน true
, false
หรือ "running"
เพื่อความชัดเจนและเพื่อให้มีความยืดหยุ่น โปรดใช้ค่าคงที่ที่ส่งออกที่ให้ไว้สำหรับค่าที่ส่งคืน ( SUCCESS
, FAILURE
, RUNNING
)
แต่ละวิธีในงานของคุณจะได้รับกระดานดำ ซึ่งคุณกำหนดเมื่อสร้างอินสแตนซ์ BehaviorTree โดยพื้นฐานแล้วกระดานดำนั้นเป็นวัตถุซึ่งเก็บข้อมูลและวิธีการทั้งหมดที่จำเป็นสำหรับการทำงานและสื่อสารกับโลก
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
- ถูกเรียกก่อนเรียกใช้ แต่ไม่ใช่หากงานกลับมาทำงานต่อหลังจากจบด้วย this.running()end
- ถูกเรียกหลังจากการเรียกใช้ แต่ไม่ใช่ถ้างานเสร็จสิ้นด้วย 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
selector เพียงเรียกโหนดย่อยโหนดใดโหนดหนึ่งแบบสุ่ม หากส่งคืน 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()
เป็นอาร์กิวเมนต์แรก คุณสามารถใช้มันเพื่อให้แผนผังพฤติกรรมรู้ว่ามันกำลังทำงานอยู่บนวัตถุใด (เช่น ผู้เล่นปลอม) ปล่อยให้มันโต้ตอบกับโลกหรือคงสถานะไว้หากคุณต้องการ หากต้องการรันแผนผัง คุณสามารถเรียก step()
เมื่อใดก็ตามที่คุณมีเวลาสำหรับการคำนวณ AI ในลูปเกมของคุณ
bTree . step ( )
BehaviorTree มาพร้อมกับรีจิสตรีภายในซึ่งคุณสามารถลงทะเบียนงานและอ้างอิงงานเหล่านั้นในโหนดของคุณตามชื่อที่คุณเลือกได้ในภายหลัง สิ่งนี้มีประโยชน์มาก หากคุณต้องการพฤติกรรมที่เหมือนกันในทรีหลายต้น หรือต้องการแยกการกำหนดงานและการก่อสร้างต้นไม้ออกจากกัน
// 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
(game loop ของเรา) สุนัขเห่า - เราปรับใช้สิ่งนี้กับโหนดที่ลงทะเบียนแล้ว เพราะเราทำสิ่งนี้สองครั้ง - จากนั้นมันจะเดินไปมาแบบสุ่ม จากนั้นมันก็เห่าอีกครั้ง และถ้ามัน พบว่าตัวเองยืนอยู่ข้างต้นไม้ที่มันฉี่อยู่บนต้นไม้
ทุกโหนดสามารถเป็น 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
ที่กำหนดไว้ซึ่งสามารถใช้เพื่อเติมอินสแตนซ์ BehaviorTree
จากคำจำกัดความ JSON สำหรับทรี โครงสร้างคำจำกัดความมีลักษณะดังนี้:
{
"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
และส่งคืนอ็อบเจ็กต์ที่สามารถใช้เป็น tree
ภายใน Constructor BehaviorTree
หากคุณไม่ชอบคำสั่ง import
ใหม่ คุณควรยังคงสามารถใช้คำสั่ง require
- แบบดั้งเดิมได้:
const {
BehaviorTree ,
Sequence ,
Task ,
SUCCESS ,
FAILURE ,
decorators : { AlwaysSucceedDecorator }
} = require ( 'behaviortree' )
คุณสามารถเพิ่มพารามิเตอร์ introspector
ให้กับ step
-method ที่มีอินสแตนซ์ของคลาส Introspector
หรือคลาสอื่นที่ใช้อินเทอร์เฟซที่คล้ายกัน การทำเช่นนี้ช่วยให้คุณสามารถรวบรวมสถิติ/ข้อมูลที่เป็นประโยชน์เกี่ยวกับการเรียกใช้แผนผังพฤติกรรมของคุณทุกครั้ง และแสดงให้คุณเห็นว่างานใดบ้างที่ดำเนินการและส่งคืนผลลัพธ์ใด มีประโยชน์ในการทำความเข้าใจเกี่ยวกับความถูกต้องของต้นไม้
แต่อย่าทำเช่นนี้ในสภาพแวดล้อมการใช้งานจริง เนื่องจากงานที่ทำเสร็จแล้วไม่จำเป็นสำหรับการประเมินตามปกติ
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 หากคุณต้องการทำให้มือของคุณสกปรก คุณสามารถแยกพื้นที่เก็บข้อมูลนี้ได้ แต่หมายเหตุ: หากคุณเขียนโค้ดอย่าลืมเขียนแบบทดสอบด้วย แล้วทำการขอดึง ฉันยินดีที่จะดูว่าอะไรจะเกิดขึ้น
การทดสอบเสร็จสิ้นด้วยความตลกขบขัน และฉันใช้เส้นด้ายเพื่อจัดการแพ็คเกจและล็อคเวอร์ชัน
yarn
yarn test
RUNNING
ลิขสิทธิ์ (C) 2013-2020 เกออร์ก ทาโวเนียส
อนุญาตให้บุคคลใดก็ตามที่ได้รับสำเนาของซอฟต์แวร์นี้และไฟล์เอกสารที่เกี่ยวข้อง ("ซอฟต์แวร์") อนุญาตโดยไม่เสียค่าใช้จ่าย เพื่อจัดการกับซอฟต์แวร์โดยไม่มีข้อจำกัด รวมถึงแต่ไม่จำกัดเพียงสิทธิ์ในการใช้ คัดลอก ปรับเปลี่ยน ผสาน เผยแพร่ แจกจ่าย ให้อนุญาตช่วง และ/หรือขายสำเนาของซอฟต์แวร์ และอนุญาตให้บุคคลที่ได้รับซอฟต์แวร์นี้สามารถทำได้ ภายใต้เงื่อนไขต่อไปนี้:
ประกาศเกี่ยวกับลิขสิทธิ์ข้างต้นและประกาศการอนุญาตนี้จะรวมอยู่ในสำเนาทั้งหมดหรือส่วนสำคัญของซอฟต์แวร์
ซอฟต์แวร์นี้มีให้ "ตามที่เป็น" โดยไม่มีการรับประกันใดๆ ทั้งโดยชัดแจ้งหรือโดยนัย ซึ่งรวมถึงแต่ไม่จำกัดเพียงการรับประกันความสามารถในการค้าขาย ความเหมาะสมสำหรับวัตถุประสงค์เฉพาะ และการไม่ละเมิด ไม่ว่าในกรณีใดผู้เขียนหรือผู้ถือลิขสิทธิ์จะต้องรับผิดต่อการเรียกร้องค่าเสียหายหรือความรับผิดอื่นใดไม่ว่าในการกระทำของสัญญาการละเมิดหรืออย่างอื่นที่เกิดขึ้นจากหรือเกี่ยวข้องกับซอฟต์แวร์หรือการใช้งานหรือข้อตกลงอื่น ๆ ใน ซอฟต์แวร์.