تطبيق جافا سكريبت لأشجار السلوك. إنها مفيدة لتنفيذ الذكاء الاصطناعي. إذا كنت بحاجة إلى مزيد من المعلومات حول أشجار السلوك، فابحث عن GameDevAIPro، فهناك مقالة لطيفة عن أشجار السلوك من Alex Chambandard وPhilip Dunstan.
إذا كنت بحاجة إلى دعم TypeScript، فيرجى مراجعة فرع typescript
الذي سيكون الإصدار 3.0 القادم وإذا كانت لديك بعض الأسئلة والتعليقات، فيرجى طرحها.
إذا كنت تستخدم npm:
npm install behaviortree
أو باستخدام الغزل:
yarn add behaviortree
هذه الحزمة ليس لها تبعيات خاصة بها.
أولاً، يجب أن أذكر أنه من الممكن استخدام هذه المكتبة أيضًا في بيئة Common-js مثل Node v8. لكي ينجح هذا، يجب عليك تبديل كافة عبارات 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
).
تتلقى كل طريقة من مهامك السبورة التي تقوم بتعيينها عند إنشاء 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
باستدعاء أحد عقده الفرعية بشكل عشوائي، وإذا أدى ذلك إلى إرجاع 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()
عندما يكون لديك وقت لإجراء بعض حسابات الذكاء الاصطناعي في حلقة لعبتك.
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
(حلقة لعبتنا)، ينبح الكلب - قمنا بتنفيذ ذلك باستخدام عقدة مسجلة، لأننا نفعل ذلك مرتين - ثم يتجول بشكل عشوائي، ثم ينبح مرة أخرى ثم إذا حدث ذلك يجد نفسه واقفاً بجانب شجرة ويتبول على الشجرة.
يمكن أيضًا أن تكون كل عقدة 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
داخل مُنشئ BehaviorTree
.
إذا لم تعجبك عبارات import
الجديدة، فلا يزال بإمكانك استخدام عبارات require
التقليدية:
const {
BehaviorTree ,
Sequence ,
Task ,
SUCCESS ,
FAILURE ,
decorators : { AlwaysSucceedDecorator }
} = require ( 'behaviortree' )
يمكنك إضافة معلمة introspector
إلى طريقة step
التي تحتوي على مثيل لفئة 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 جورج تافونيوس
يُمنح الإذن مجانًا لأي شخص يحصل على نسخة من هذا البرنامج وملفات الوثائق المرتبطة به ("البرنامج")، للتعامل في البرنامج دون قيود، بما في ذلك، على سبيل المثال لا الحصر، حقوق الاستخدام والنسخ والتعديل والدمج. ونشر و/أو توزيع وترخيص من الباطن و/أو بيع نسخ من البرنامج، والسماح للأشخاص الذين تم توفير البرنامج لهم بالقيام بذلك، وفقًا للشروط التالية:
يجب تضمين إشعار حقوق الطبع والنشر أعلاه وإشعار الإذن هذا في جميع النسخ أو الأجزاء الكبيرة من البرنامج.
يتم توفير البرنامج "كما هو"، دون أي ضمان من أي نوع، صريحًا أو ضمنيًا، بما في ذلك، على سبيل المثال لا الحصر، ضمانات القابلية للتسويق والملاءمة لغرض معين وعدم الانتهاك. لا يتحمل المؤلفون أو أصحاب حقوق الطبع والنشر بأي حال من الأحوال المسؤولية عن أي مطالبة أو أضرار أو مسؤولية أخرى، سواء في إجراء العقد أو الضرر أو غير ذلك، الناشئة عن أو خارج أو فيما يتعلق بالبرنامج أو الاستخدام أو المعاملات الأخرى في برمجة.