├── .gitignore ├── .jscsrc ├── .jshintrc ├── LICENSE ├── README.md ├── app.js ├── jsconfig.json ├── lib ├── behavior3js.js ├── openDoorNodes.js └── treeLoader.js ├── package.json └── resources └── openDoor.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "fileExtensions": [".js", "jscs"], 4 | 5 | "requireSemicolons": true, 6 | "requireParenthesesAroundIIFE": true, 7 | "maximumLineLength": 250, 8 | "validateLineBreaks": "CRLF", 9 | "validateIndentation": 4, 10 | "disallowTrailingComma": true, 11 | "disallowUnusedParams": true, 12 | 13 | "disallowSpacesInsideObjectBrackets": null, 14 | "disallowImplicitTypeConversion": ["string"], 15 | 16 | "safeContextKeyword": "_this", 17 | 18 | "jsDoc": { 19 | "checkAnnotations": "closurecompiler", 20 | "checkParamNames": true, 21 | "requireParamTypes": true, 22 | "checkRedundantParams": true, 23 | "checkReturnTypes": true, 24 | "checkRedundantReturns": true, 25 | "requireReturnTypes": true, 26 | "checkTypes": "capitalizedNativeCase", 27 | "checkRedundantAccess": true, 28 | "requireNewlineAfterDescription": true 29 | }, 30 | 31 | "excludeFiles": [ 32 | "test/data/**", 33 | "patterns/*" 34 | ] 35 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "node": true, 4 | "eqeqeq": true, 5 | "strict": true, 6 | "newcap": false, 7 | "undef": true, 8 | "unused": true, 9 | "onecase": true, 10 | "lastsemic": true, 11 | "esversion": 6 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Erik Benson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # behavior3Test 2 | 3 | ## A simple example on how to implement the behavior3 lib in nodejs (and similarly in the browser) 4 | 5 | Note: the behavior3 lib does not support nodejs currently so there is an included version of it in the lib folder that works with node. 6 | 7 | This is a simple behavior tree built off the example given in the great article by Chris Simpson on behavior trees [here](http://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php) 8 | 9 | Download the sample and run node app and you will get an output 10 | 11 | [https://github.com/behavior3/behavior3js](https://github.com/behavior3/behavior3js) 12 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var b3 = require('./lib/behavior3js').b3; 4 | 5 | var treeLoader = require('./lib/treeLoader'); 6 | 7 | treeLoader.init(); 8 | 9 | var ai = treeLoader.ai(); 10 | 11 | console.log('**** Lucky tries the door'); 12 | var lucky = { 13 | memory: new b3.Blackboard() 14 | }; 15 | lucky.memory.set('name', 'Lucky'); 16 | ai.guy.tick(lucky, lucky.memory); 17 | 18 | console.log(''); 19 | console.log('**** Thief tries the door'); 20 | var thief = { 21 | memory: new b3.Blackboard() 22 | }; 23 | thief.memory.set('name', 'Thief'); 24 | thief.memory.set('locked', true); 25 | thief.memory.set('lockpick-level', 8); 26 | 27 | ai.guy.tick(thief, thief.memory); 28 | 29 | console.log(''); 30 | console.log('**** Thug tries the door'); 31 | var thug = { 32 | memory: new b3.Blackboard() 33 | }; 34 | thug.memory.set('name', 'Thug'); 35 | thug.memory.set('locked', true); 36 | thug.memory.set('lockpick-level', 2); 37 | 38 | ai.guy.tick(thug, thug.memory); 39 | console.log(''); 40 | console.log(''); 41 | console.log('**** Simulation complete'); 42 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | }, 5 | "exclude": [ 6 | "node_modules" 7 | ] 8 | } -------------------------------------------------------------------------------- /lib/behavior3js.js: -------------------------------------------------------------------------------- 1 | // test for nodejs runtime http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js 2 | if (typeof module !== 'undefined' && this.module !== module) { 3 | var b3 = {}; 4 | 5 | module.exports = { 6 | b3: b3 7 | }; 8 | } 9 | 10 | 11 | this.b3 = this.b3 || {}; 12 | 13 | (function() { 14 | "use strict"; 15 | 16 | b3.VERSION = '0.2.0'; 17 | 18 | // Returning status 19 | b3.SUCCESS = 1; 20 | b3.FAILURE = 2; 21 | b3.RUNNING = 3; 22 | b3.ERROR = 4; 23 | 24 | // Node categories 25 | b3.COMPOSITE = 'composite'; 26 | b3.DECORATOR = 'decorator'; 27 | b3.ACTION = 'action'; 28 | b3.CONDITION = 'condition'; 29 | })(); 30 | /** 31 | * List of internal and helper functions in Behavior3JS. 32 | * 33 | * @module functions 34 | **/ 35 | 36 | (function() { 37 | "use strict"; 38 | 39 | /** 40 | * This function is used to create unique IDs for trees and nodes. 41 | * 42 | * (consult http://www.ietf.org/rfc/rfc4122.txt). 43 | * 44 | * @class createUUID 45 | * @constructor 46 | * @return {String} A unique ID. 47 | **/ 48 | b3.createUUID = function() { 49 | var s = []; 50 | var hexDigits = "0123456789abcdef"; 51 | for (var i = 0; i < 36; i++) { 52 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 53 | } 54 | // bits 12-15 of the time_hi_and_version field to 0010 55 | s[14] = "4"; 56 | 57 | // bits 6-7 of the clock_seq_hi_and_reserved to 01 58 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); 59 | 60 | s[8] = s[13] = s[18] = s[23] = "-"; 61 | 62 | var uuid = s.join(""); 63 | return uuid; 64 | }; 65 | 66 | /** 67 | * Class is a meta-factory function to create classes in JavaScript. It is a 68 | * shortcut for the CreateJS syntax style. By default, the class created by 69 | * this function have an initialize function (the constructor). Optionally, 70 | * you can specify the inheritance by passing another class as parameter. 71 | * 72 | * By default, all classes created using this function, may receive only a 73 | * dictionary parameter as argument. This pattern is commonly used by jQuery 74 | * and its plugins. 75 | * 76 | * Since 0.2.0, Class also receives a `properties` parameter, a dictionary 77 | * which will be used to fill the new class prototype. 78 | * 79 | * Usage 80 | * ----- 81 | * 82 | * // Creating a simple class 83 | * var BaseClass = b3.Class(); 84 | * 85 | * var ChildClass = b3.Class(BaseClass, { 86 | * // constructor 87 | * initialize: function(params) { 88 | * 89 | * // call super initialize 90 | * BaseClass.initialize.call(this, params); 91 | * ... 92 | * } 93 | * }); 94 | * 95 | * @class Class 96 | * @constructor 97 | * @param {Object} baseClass The super class. 98 | * @param {Object} properties A dictionary with attributes and methods. 99 | * @return {Object} A new class. 100 | **/ 101 | b3.Class = function(baseClass, properties) { 102 | // create a new class 103 | var cls = function(params) { 104 | this.initialize(params || {}); 105 | }; 106 | 107 | // if base class is provided, inherit 108 | if (baseClass) { 109 | cls.prototype = Object.create(baseClass.prototype); 110 | cls.prototype.constructor = cls; 111 | } 112 | 113 | // create initialize if does not exist on baseClass 114 | if (!cls.prototype.initialize) { 115 | cls.prototype.initialize = function() {}; 116 | } 117 | 118 | // copy properties 119 | if (properties) { 120 | for (var key in properties) { 121 | cls.prototype[key] = properties[key]; 122 | } 123 | } 124 | 125 | return cls; 126 | }; 127 | })(); 128 | 129 | (function() { 130 | "use strict"; 131 | 132 | /** 133 | * The BehaviorTree class, as the name implies, represents the Behavior Tree 134 | * structure. 135 | * 136 | * There are two ways to construct a Behavior Tree: by manually setting the 137 | * root node, or by loading it from a data structure (which can be loaded 138 | * from a JSON). Both methods are shown in the examples below and better 139 | * explained in the user guide. 140 | * 141 | * The tick method must be called periodically, in order to send the tick 142 | * signal to all nodes in the tree, starting from the root. The method 143 | * `BehaviorTree.tick` receives a target object and a blackboard as 144 | * parameters. The target object can be anything: a game agent, a system, a 145 | * DOM object, etc. This target is not used by any piece of Behavior3JS, 146 | * i.e., the target object will only be used by custom nodes. 147 | * 148 | * The blackboard is obligatory and must be an instance of `Blackboard`. This 149 | * requirement is necessary due to the fact that neither `BehaviorTree` or 150 | * any node will store the execution variables in its own object (e.g., the 151 | * BT does not store the target, information about opened nodes or number of 152 | * times the tree was called). But because of this, you only need a single 153 | * tree instance to control multiple (maybe hundreds) objects. 154 | * 155 | * Manual construction of a Behavior Tree 156 | * -------------------------------------- 157 | * 158 | * var tree = new b3.BehaviorTree(); 159 | * 160 | * tree.root = new b3.Sequence({children:[ 161 | * new b3.Priority({children:[ 162 | * new MyCustomNode(), 163 | * new MyCustomNode() 164 | * ]}), 165 | * ... 166 | * ]}); 167 | * 168 | * 169 | * Loading a Behavior Tree from data structure 170 | * ------------------------------------------- 171 | * 172 | * var tree = new b3.BehaviorTree(); 173 | * 174 | * tree.load({ 175 | * 'title' : 'Behavior Tree title' 176 | * 'description' : 'My description' 177 | * 'root' : 'node-id-1' 178 | * 'nodes' : { 179 | * 'node-id-1' : { 180 | * 'name' : 'Priority', // this is the node type 181 | * 'title' : 'Root Node', 182 | * 'description' : 'Description', 183 | * 'children' : ['node-id-2', 'node-id-3'], 184 | * }, 185 | * ... 186 | * } 187 | * }) 188 | * 189 | * 190 | * @module b3 191 | * @class BehaviorTree 192 | **/ 193 | b3.BehaviorTree = b3.Class(null, { 194 | 195 | /** 196 | * The tree id, must be unique. By default, created with `b3.createUUID`. 197 | * @property {String} id 198 | * @readOnly 199 | **/ 200 | id: null, 201 | 202 | /** 203 | * The tree title. 204 | * @property {String} title 205 | * @readonly 206 | **/ 207 | title: null, 208 | 209 | /** 210 | * Description of the tree. 211 | * @property {String} description 212 | * @readonly 213 | **/ 214 | description: null, 215 | 216 | /** 217 | * A dictionary with (key-value) properties. Useful to define custom 218 | * variables in the visual editor. 219 | * 220 | * @property {Object} properties 221 | * @readonly 222 | **/ 223 | properties: null, 224 | 225 | /** 226 | * The reference to the root node. Must be an instance of `b3.BaseNode`. 227 | * @property {BaseNode} root 228 | **/ 229 | root: null, 230 | 231 | /** 232 | * The reference to the debug instance. 233 | * @property {Object} debug 234 | **/ 235 | debug: null, 236 | 237 | /** 238 | * Initialization method. 239 | * @method initialize 240 | * @constructor 241 | **/ 242 | initialize: function() { 243 | this.id = b3.createUUID(); 244 | this.title = 'The behavior tree'; 245 | this.description = 'Default description'; 246 | this.properties = {}; 247 | this.root = null; 248 | this.debug = null; 249 | }, 250 | 251 | /** 252 | * This method loads a Behavior Tree from a data structure, populating this 253 | * object with the provided data. Notice that, the data structure must 254 | * follow the format specified by Behavior3JS. Consult the guide to know 255 | * more about this format. 256 | * 257 | * You probably want to use custom nodes in your BTs, thus, you need to 258 | * provide the `names` object, in which this method can find the nodes by 259 | * `names[NODE_NAME]`. This variable can be a namespace or a dictionary, 260 | * as long as this method can find the node by its name, for example: 261 | * 262 | * //json 263 | * ... 264 | * 'node1': { 265 | * 'name': MyCustomNode, 266 | * 'title': ... 267 | * } 268 | * ... 269 | * 270 | * //code 271 | * var bt = new b3.BehaviorTree(); 272 | * bt.load(data, {'MyCustomNode':MyCustomNode}) 273 | * 274 | * 275 | * @method load 276 | * @param {Object} data The data structure representing a Behavior Tree. 277 | * @param {Object} [names] A namespace or dict containing custom nodes. 278 | **/ 279 | load: function(data, names) { 280 | names = names || {}; 281 | 282 | this.title = data.title || this.title; 283 | this.description = data.description || this.description; 284 | this.properties = data.properties || this.properties; 285 | 286 | var nodes = {}; 287 | var id, spec, node; 288 | // Create the node list (without connection between them) 289 | for (id in data.nodes) { 290 | spec = data.nodes[id]; 291 | var Cls; 292 | 293 | if (spec.name in names) { 294 | // Look for the name in custom nodes 295 | Cls = names[spec.name]; 296 | } else if (spec.name in b3) { 297 | // Look for the name in default nodes 298 | Cls = b3[spec.name]; 299 | } else { 300 | // Invalid node name 301 | throw new EvalError('BehaviorTree.load: Invalid node name + "'+ 302 | spec.name+'".'); 303 | } 304 | 305 | node = new Cls(spec.properties); 306 | node.id = spec.id || node.id; 307 | node.title = spec.title || node.title; 308 | node.description = spec.description || node.description; 309 | node.properties = spec.properties || node.properties; 310 | 311 | nodes[id] = node; 312 | } 313 | 314 | // Connect the nodes 315 | for (id in data.nodes) { 316 | spec = data.nodes[id]; 317 | node = nodes[id]; 318 | 319 | if (node.category === b3.COMPOSITE && spec.children) { 320 | for (var i=0; i 0) { 356 | var node = stack.pop(); 357 | 358 | var spec = {}; 359 | spec.id = node.id; 360 | spec.name = node.name; 361 | spec.title = node.title; 362 | spec.description = node.description; 363 | spec.properties = node.properties; 364 | spec.parameters = node.parameters; 365 | 366 | // verify custom node 367 | var proto = (node.constructor && node.constructor.prototype); 368 | var nodeName = (proto && proto.name) || node.name; 369 | if (!b3[nodeName] && customNames.indexOf(nodeName) < 0) { 370 | var subdata = {}; 371 | subdata.name = nodeName; 372 | subdata.title = (proto && proto.title) || node.title; 373 | subdata.category = node.category; 374 | 375 | customNames.push(nodeName); 376 | data.custom_nodes.push(subdata); 377 | } 378 | 379 | // store children/child 380 | if (node.category === b3.COMPOSITE && node.children) { 381 | var children = []; 382 | for (var i=node.children.length-1; i>=0; i--) { 383 | children.push(node.children[i].id); 384 | stack.push(node.children[i]); 385 | } 386 | spec.children = children; 387 | } else if (node.category === b3.DECORATOR && node.child) { 388 | stack.push(node.child); 389 | spec.child = node.child.id; 390 | } 391 | 392 | data.nodes[node.id] = spec; 393 | } 394 | 395 | return data; 396 | }, 397 | 398 | /** 399 | * Propagates the tick signal through the tree, starting from the root. 400 | * 401 | * This method receives a target object of any type (Object, Array, 402 | * DOMElement, whatever) and a `Blackboard` instance. The target object has 403 | * no use at all for all Behavior3JS components, but surely is important 404 | * for custom nodes. The blackboard instance is used by the tree and nodes 405 | * to store execution variables (e.g., last node running) and is obligatory 406 | * to be a `Blackboard` instance (or an object with the same interface). 407 | * 408 | * Internally, this method creates a Tick object, which will store the 409 | * target and the blackboard objects. 410 | * 411 | * Note: BehaviorTree stores a list of open nodes from last tick, if these 412 | * nodes weren't called after the current tick, this method will close them 413 | * automatically. 414 | * 415 | * @method tick 416 | * @param {Object} target A target object. 417 | * @param {Blackboard} blackboard An instance of blackboard object. 418 | * @return {Constant} The tick signal state. 419 | **/ 420 | tick: function(target, blackboard) { 421 | if (!blackboard) { 422 | throw 'The blackboard parameter is obligatory and must be an ' + 423 | 'instance of b3.Blackboard'; 424 | } 425 | 426 | /* CREATE A TICK OBJECT */ 427 | var tick = new b3.Tick(); 428 | tick.debug = this.debug; 429 | tick.target = target; 430 | tick.blackboard = blackboard; 431 | tick.tree = this; 432 | 433 | /* TICK NODE */ 434 | var state = this.root._execute(tick); 435 | 436 | /* CLOSE NODES FROM LAST TICK, IF NEEDED */ 437 | var lastOpenNodes = blackboard.get('openNodes', this.id); 438 | var currOpenNodes = tick._openNodes.slice(0); 439 | 440 | // does not close if it is still open in this tick 441 | var start = 0; 442 | var i; 443 | for (i=0; i=start; i--) { 452 | lastOpenNodes[i]._close(tick); 453 | } 454 | 455 | /* POPULATE BLACKBOARD */ 456 | blackboard.set('openNodes', currOpenNodes, this.id); 457 | blackboard.set('nodeCount', tick._nodeCount, this.id); 458 | 459 | return state; 460 | } 461 | }); 462 | 463 | })(); 464 | (function() { 465 | "use strict"; 466 | 467 | /** 468 | * A new Tick object is instantiated every tick by BehaviorTree. It is passed 469 | * as parameter to the nodes through the tree during the traversal. 470 | * 471 | * The role of the Tick class is to store the instances of tree, debug, 472 | * target and blackboard. So, all nodes can access these informations. 473 | * 474 | * For internal uses, the Tick also is useful to store the open node after 475 | * the tick signal, in order to let `BehaviorTree` to keep track and close 476 | * them when necessary. 477 | * 478 | * This class also makes a bridge between nodes and the debug, passing the 479 | * node state to the debug if the last is provided. 480 | * 481 | * @module b3 482 | * @class Tick 483 | **/ 484 | b3.Tick = b3.Class(null, { 485 | 486 | /** 487 | * The tree reference. 488 | * @property {b3.BehaviorTree} tree 489 | * @readOnly 490 | **/ 491 | tree: null, 492 | 493 | /** 494 | * The debug reference. 495 | * @property {Object} debug 496 | * @readOnly 497 | */ 498 | debug: null, 499 | 500 | /** 501 | * The target object reference. 502 | * @property {Object} target 503 | * @readOnly 504 | **/ 505 | target: null, 506 | 507 | /** 508 | * The blackboard reference. 509 | * @property {b3.Blackboard} blackboard 510 | * @readOnly 511 | **/ 512 | blackboard: null, 513 | 514 | /** 515 | * The list of open nodes. Update during the tree traversal. 516 | * @property {Array} _openNodes 517 | * @protected 518 | * @readOnly 519 | **/ 520 | _openNodes: [], 521 | 522 | /** 523 | * The number of nodes entered during the tick. Update during the tree 524 | * traversal. 525 | * 526 | * @property {Integer} _nodeCount 527 | * @protected 528 | * @readOnly 529 | **/ 530 | _nodeCount: 0, 531 | 532 | /** 533 | * Initialization method. 534 | * @method initialize 535 | * @constructor 536 | **/ 537 | initialize: function() { 538 | // set by BehaviorTree 539 | this.tree = null; 540 | this.debug = null; 541 | this.target = null; 542 | this.blackboard = null; 543 | 544 | // updated during the tick signal 545 | this._openNodes = []; 546 | this._nodeCount = 0; 547 | }, 548 | 549 | /** 550 | * Called when entering a node (called by BaseNode). 551 | * @method _enterNode 552 | * @param {Object} node The node that called this method. 553 | * @protected 554 | **/ 555 | _enterNode: function(node) { 556 | this._nodeCount++; 557 | this._openNodes.push(node); 558 | 559 | // TODO: call debug here 560 | }, 561 | 562 | /** 563 | * Callback when opening a node (called by BaseNode). 564 | * @method _openNode 565 | * @param {Object} node The node that called this method. 566 | * @protected 567 | **/ 568 | _openNode: function(node) { 569 | // TODO: call debug here 570 | }, 571 | 572 | /** 573 | * Callback when ticking a node (called by BaseNode). 574 | * @method _tickNode 575 | * @param {Object} node The node that called this method. 576 | * @protected 577 | **/ 578 | _tickNode: function(node) { 579 | // TODO: call debug here 580 | }, 581 | 582 | /** 583 | * Callback when closing a node (called by BaseNode). 584 | * @method _closeNode 585 | * @param {Object} node The node that called this method. 586 | * @protected 587 | **/ 588 | _closeNode: function(node) { 589 | // TODO: call debug here 590 | this._openNodes.pop(); 591 | }, 592 | 593 | /** 594 | * Callback when exiting a node (called by BaseNode). 595 | * @method _exitNode 596 | * @param {Object} node The node that called this method. 597 | * @protected 598 | **/ 599 | _exitNode: function(node) { 600 | // TODO: call debug here 601 | } 602 | }); 603 | })(); 604 | (function() { 605 | "use strict"; 606 | 607 | /** 608 | * The Blackboard is the memory structure required by `BehaviorTree` and its 609 | * nodes. It only have 2 public methods: `set` and `get`. These methods works 610 | * in 3 different contexts: global, per tree, and per node per tree. 611 | * 612 | * Suppose you have two different trees controlling a single object with a 613 | * single blackboard, then: 614 | * 615 | * - In the global context, all nodes will access the stored information. 616 | * - In per tree context, only nodes sharing the same tree share the stored 617 | * information. 618 | * - In per node per tree context, the information stored in the blackboard 619 | * can only be accessed by the same node that wrote the data. 620 | * 621 | * The context is selected indirectly by the parameters provided to these 622 | * methods, for example: 623 | * 624 | * // getting/setting variable in global context 625 | * blackboard.set('testKey', 'value'); 626 | * var value = blackboard.get('testKey'); 627 | * 628 | * // getting/setting variable in per tree context 629 | * blackboard.set('testKey', 'value', tree.id); 630 | * var value = blackboard.get('testKey', tree.id); 631 | * 632 | * // getting/setting variable in per node per tree context 633 | * blackboard.set('testKey', 'value', tree.id, node.id); 634 | * var value = blackboard.get('testKey', tree.id, node.id); 635 | * 636 | * Note: Internally, the blackboard store these memories in different 637 | * objects, being the global on `_baseMemory`, the per tree on `_treeMemory` 638 | * and the per node per tree dynamically create inside the per tree memory 639 | * (it is accessed via `_treeMemory[id].nodeMemory`). Avoid to use these 640 | * variables manually, use `get` and `set` instead. 641 | * 642 | * @module b3 643 | * @class Blackboard 644 | **/ 645 | b3.Blackboard = b3.Class(null, { 646 | 647 | /** 648 | * Initialization method. 649 | * @method initialize 650 | * @constructor 651 | **/ 652 | initialize: function() { 653 | this._baseMemory = {}; 654 | this._treeMemory = {}; 655 | }, 656 | 657 | /** 658 | * Internal method to retrieve the tree context memory. If the memory does 659 | * not exist, this method creates it. 660 | * 661 | * @method _getTreeMemory 662 | * @param {string} treeScope The id of the tree in scope. 663 | * @return {Object} The tree memory. 664 | * @protected 665 | **/ 666 | _getTreeMemory: function(treeScope) { 667 | if (!this._treeMemory[treeScope]) { 668 | this._treeMemory[treeScope] = { 669 | 'nodeMemory' : {}, 670 | 'openNodes' : [], 671 | 'traversalDepth' : 0, 672 | 'traversalCycle' : 0, 673 | }; 674 | } 675 | return this._treeMemory[treeScope]; 676 | }, 677 | 678 | /** 679 | * Internal method to retrieve the node context memory, given the tree 680 | * memory. If the memory does not exist, this method creates is. 681 | * 682 | * @method _getNodeMemory 683 | * @param {String} treeMemory the tree memory. 684 | * @param {String} nodeScope The id of the node in scope. 685 | * @return {Object} The node memory. 686 | * @protected 687 | **/ 688 | _getNodeMemory: function(treeMemory, nodeScope) { 689 | var memory = treeMemory.nodeMemory; 690 | if (!memory[nodeScope]) { 691 | memory[nodeScope] = {}; 692 | } 693 | 694 | return memory[nodeScope]; 695 | }, 696 | 697 | /** 698 | * Internal method to retrieve the context memory. If treeScope and 699 | * nodeScope are provided, this method returns the per node per tree 700 | * memory. If only the treeScope is provided, it returns the per tree 701 | * memory. If no parameter is provided, it returns the global memory. 702 | * Notice that, if only nodeScope is provided, this method will still 703 | * return the global memory. 704 | * 705 | * @method _getMemory 706 | * @param {String} treeScope The id of the tree scope. 707 | * @param {String} nodeScope The id of the node scope. 708 | * @return {Object} A memory object. 709 | * @protected 710 | **/ 711 | _getMemory: function(treeScope, nodeScope) { 712 | var memory = this._baseMemory; 713 | 714 | if (treeScope) { 715 | memory = this._getTreeMemory(treeScope); 716 | 717 | if (nodeScope) { 718 | memory = this._getNodeMemory(memory, nodeScope); 719 | } 720 | } 721 | 722 | return memory; 723 | }, 724 | 725 | /** 726 | * Stores a value in the blackboard. If treeScope and nodeScope are 727 | * provided, this method will save the value into the per node per tree 728 | * memory. If only the treeScope is provided, it will save the value into 729 | * the per tree memory. If no parameter is provided, this method will save 730 | * the value into the global memory. Notice that, if only nodeScope is 731 | * provided (but treeScope not), this method will still save the value into 732 | * the global memory. 733 | * 734 | * @method set 735 | * @param {String} key The key to be stored. 736 | * @param {String} value The value to be stored. 737 | * @param {String} treeScope The tree id if accessing the tree or node 738 | * memory. 739 | * @param {String} nodeScope The node id if accessing the node memory. 740 | **/ 741 | set: function(key, value, treeScope, nodeScope) { 742 | var memory = this._getMemory(treeScope, nodeScope); 743 | memory[key] = value; 744 | }, 745 | 746 | /** 747 | * Retrieves a value in the blackboard. If treeScope and nodeScope are 748 | * provided, this method will retrieve the value from the per node per tree 749 | * memory. If only the treeScope is provided, it will retrieve the value 750 | * from the per tree memory. If no parameter is provided, this method will 751 | * retrieve from the global memory. If only nodeScope is provided (but 752 | * treeScope not), this method will still try to retrieve from the global 753 | * memory. 754 | * 755 | * @method get 756 | * @param {String} key The key to be retrieved. 757 | * @param {String} treeScope The tree id if accessing the tree or node 758 | * memory. 759 | * @param {String} nodeScope The node id if accessing the node memory. 760 | * @return {Object} The value stored or undefined. 761 | **/ 762 | get: function(key, treeScope, nodeScope) { 763 | var memory = this._getMemory(treeScope, nodeScope); 764 | return memory[key]; 765 | } 766 | }); 767 | 768 | })(); 769 | (function() { 770 | "use strict"; 771 | 772 | /** 773 | * The BaseNode class is used as super class to all nodes in BehaviorJS. It 774 | * comprises all common variables and methods that a node must have to 775 | * execute. 776 | * 777 | * **IMPORTANT:** Do not inherit from this class, use `b3.Composite`, 778 | * `b3.Decorator`, `b3.Action` or `b3.Condition`, instead. 779 | * 780 | * The attributes are specially designed to serialization of the node in a 781 | * JSON format. In special, the `parameters` attribute can be set into the 782 | * visual editor (thus, in the JSON file), and it will be used as parameter 783 | * on the node initialization at `BehaviorTree.load`. 784 | * 785 | * BaseNode also provide 5 callback methods, which the node implementations 786 | * can override. They are `enter`, `open`, `tick`, `close` and `exit`. See 787 | * their documentation to know more. These callbacks are called inside the 788 | * `_execute` method, which is called in the tree traversal. 789 | * 790 | * @module b3 791 | * @class BaseNode 792 | **/ 793 | b3.BaseNode = b3.Class(null, { 794 | 795 | /** 796 | * Node ID. 797 | * @property {string} id 798 | * @readonly 799 | **/ 800 | id: null, 801 | 802 | /** 803 | * Node name. Must be a unique identifier, preferable the same name of the 804 | * class. You have to set the node name in the prototype. 805 | * 806 | * @property {String} name 807 | * @readonly 808 | **/ 809 | name: null, 810 | 811 | /** 812 | * Node category. Must be `b3.COMPOSITE`, `b3.DECORATOR`, `b3.ACTION` or 813 | * `b3.CONDITION`. This is defined automatically be inheriting the 814 | * correspondent class. 815 | * 816 | * @property {CONSTANT} category 817 | * @readonly 818 | **/ 819 | category: null, 820 | 821 | /** 822 | * Node title. 823 | * @property {String} title 824 | * @optional 825 | * @readonly 826 | **/ 827 | title: null, 828 | 829 | /** 830 | * Node description. 831 | * @property {String} description 832 | * @optional 833 | * @readonly 834 | **/ 835 | description: null, 836 | 837 | /** 838 | * A dictionary (key, value) describing the node parameters. Useful for 839 | * defining parameter values in the visual editor. Note: this is only 840 | * useful for nodes when loading trees from JSON files. 841 | * 842 | * **Deprecated since 0.2.0. This is too similar to the properties 843 | * attribute, thus, this attribute is deprecated in favor to 844 | * `properties`.** 845 | * 846 | * @property {Object} parameters 847 | * @deprecated since 0.2.0. 848 | * @readonly 849 | **/ 850 | parameters: null, 851 | 852 | /** 853 | * A dictionary (key, value) describing the node properties. Useful for 854 | * defining custom variables inside the visual editor. 855 | * 856 | * @property properties 857 | * @type {Object} 858 | * @readonly 859 | **/ 860 | properties: null, 861 | 862 | /** 863 | * Initialization method. 864 | * @method initialize 865 | * @constructor 866 | **/ 867 | initialize: function(params) { 868 | this.id = b3.createUUID(); 869 | this.title = this.title || this.name; 870 | this.description = ''; 871 | this.parameters = {}; 872 | this.properties = {}; 873 | }, 874 | 875 | /** 876 | * This is the main method to propagate the tick signal to this node. This 877 | * method calls all callbacks: `enter`, `open`, `tick`, `close`, and 878 | * `exit`. It only opens a node if it is not already open. In the same 879 | * way, this method only close a node if the node returned a status 880 | * different of `b3.RUNNING`. 881 | * 882 | * @method _execute 883 | * @param {Tick} tick A tick instance. 884 | * @return {Constant} The tick state. 885 | * @protected 886 | **/ 887 | _execute: function(tick) { 888 | // ENTER 889 | this._enter(tick); 890 | 891 | // OPEN 892 | if (!tick.blackboard.get('isOpen', tick.tree.id, this.id)) { 893 | this._open(tick); 894 | } 895 | 896 | // TICK 897 | var status = this._tick(tick); 898 | 899 | // CLOSE 900 | if (status !== b3.RUNNING) { 901 | this._close(tick); 902 | } 903 | 904 | // EXIT 905 | this._exit(tick); 906 | 907 | return status; 908 | }, 909 | 910 | /** 911 | * Wrapper for enter method. 912 | * @method _enter 913 | * @param {Tick} tick A tick instance. 914 | * @protected 915 | **/ 916 | _enter: function(tick) { 917 | tick._enterNode(this); 918 | this.enter(tick); 919 | }, 920 | 921 | /** 922 | * Wrapper for open method. 923 | * @method _open 924 | * @param {Tick} tick A tick instance. 925 | * @protected 926 | **/ 927 | _open: function(tick) { 928 | tick._openNode(this); 929 | tick.blackboard.set('isOpen', true, tick.tree.id, this.id); 930 | this.open(tick); 931 | }, 932 | 933 | /** 934 | * Wrapper for tick method. 935 | * @method _tick 936 | * @param {Tick} tick A tick instance. 937 | * @return {Constant} A state constant. 938 | * @protected 939 | **/ 940 | _tick: function(tick) { 941 | tick._tickNode(this); 942 | return this.tick(tick); 943 | }, 944 | 945 | /** 946 | * Wrapper for close method. 947 | * @method _close 948 | * @param {Tick} tick A tick instance. 949 | * @protected 950 | **/ 951 | _close: function(tick) { 952 | tick._closeNode(this); 953 | tick.blackboard.set('isOpen', false, tick.tree.id, this.id); 954 | this.close(tick); 955 | }, 956 | 957 | /** 958 | * Wrapper for exit method. 959 | * @method _exit 960 | * @param {Tick} tick A tick instance. 961 | * @protected 962 | **/ 963 | _exit: function(tick) { 964 | tick._exitNode(this); 965 | this.exit(tick); 966 | }, 967 | 968 | /** 969 | * Enter method, override this to use. It is called every time a node is 970 | * asked to execute, before the tick itself. 971 | * 972 | * @method enter 973 | * @param {Tick} tick A tick instance. 974 | **/ 975 | enter: function(tick) {}, 976 | 977 | /** 978 | * Open method, override this to use. It is called only before the tick 979 | * callback and only if the not isn't closed. 980 | * 981 | * Note: a node will be closed if it returned `b3.RUNNING` in the tick. 982 | * 983 | * @method open 984 | * @param {Tick} tick A tick instance. 985 | **/ 986 | open: function(tick) {}, 987 | 988 | /** 989 | * Tick method, override this to use. This method must contain the real 990 | * execution of node (perform a task, call children, etc.). It is called 991 | * every time a node is asked to execute. 992 | * 993 | * @method tick 994 | * @param {Tick} tick A tick instance. 995 | **/ 996 | tick: function(tick) {}, 997 | 998 | /** 999 | * Close method, override this to use. This method is called after the tick 1000 | * callback, and only if the tick return a state different from 1001 | * `b3.RUNNING`. 1002 | * 1003 | * @method close 1004 | * @param {Tick} tick A tick instance. 1005 | **/ 1006 | close: function(tick) {}, 1007 | 1008 | /** 1009 | * Exit method, override this to use. Called every time in the end of the 1010 | * execution. 1011 | * 1012 | * @method exit 1013 | * @param {Tick} tick A tick instance. 1014 | **/ 1015 | exit: function(tick) {}, 1016 | }); 1017 | 1018 | })(); 1019 | (function() { 1020 | "use strict"; 1021 | 1022 | /** 1023 | * Action is the base class for all action nodes. Thus, if you want to create 1024 | * new custom action nodes, you need to inherit from this class. For example, 1025 | * take a look at the Runner action: 1026 | * 1027 | * var Runner = b3.Class(b3.Action, { 1028 | * name: 'Runner', 1029 | * 1030 | * tick: function(tick) { 1031 | * return b3.RUNNING; 1032 | * } 1033 | * }); 1034 | * 1035 | * @module b3 1036 | * @class Action 1037 | * @extends BaseNode 1038 | **/ 1039 | b3.Action = b3.Class(b3.BaseNode, { 1040 | 1041 | /** 1042 | * Node category. Default to `b3.ACTION`. 1043 | * @property {String} category 1044 | * @readonly 1045 | **/ 1046 | category: b3.ACTION, 1047 | 1048 | /** 1049 | * Initialization method. 1050 | * @method initialize 1051 | * @constructor 1052 | **/ 1053 | initialize: function(params) { 1054 | b3.BaseNode.prototype.initialize.call(this); 1055 | } 1056 | }); 1057 | 1058 | })(); 1059 | (function() { 1060 | "use strict"; 1061 | 1062 | /** 1063 | * Composite is the base class for all composite nodes. Thus, if you want to 1064 | * create new custom composite nodes, you need to inherit from this class. 1065 | * 1066 | * When creating composite nodes, you will need to propagate the tick signal 1067 | * to the children nodes manually. To do that, override the `tick` method and 1068 | * call the `_execute` method on all nodes. For instance, take a look at how 1069 | * the Sequence node inherit this class and how it call its children: 1070 | * 1071 | * // Inherit from Composite, using the util function Class. 1072 | * var Sequence = b3.Class(b3.Composite, { 1073 | * 1074 | * // Remember to set the name of the node. 1075 | * name: 'Sequence', 1076 | * 1077 | * // Override the tick function 1078 | * tick: function(tick) { 1079 | * 1080 | * // Iterates over the children 1081 | * for (var i=0; i Activations', 1478 | 1479 | /** 1480 | * Node parameters. 1481 | * @property {String} parameters 1482 | * @readonly 1483 | **/ 1484 | parameters: {'maxLoop': 1}, 1485 | 1486 | /** 1487 | * Initialization method. 1488 | * 1489 | * Settings parameters: 1490 | * 1491 | * - **maxLoop** (*Integer*) Maximum number of repetitions. 1492 | * - **child** (*BaseNode*) The child node. 1493 | * 1494 | * @method initialize 1495 | * @param {Object} params Object with parameters. 1496 | * @constructor 1497 | **/ 1498 | initialize: function(params) { 1499 | b3.Decorator.prototype.initialize.call(this, params); 1500 | 1501 | if (!params.maxLoop) { 1502 | throw "maxLoop parameter in Limiter decorator is an obligatory " + 1503 | "parameter"; 1504 | } 1505 | 1506 | this.maxLoop = params.maxLoop; 1507 | }, 1508 | 1509 | /** 1510 | * Open method. 1511 | * @method open 1512 | * @param {Tick} tick A tick instance. 1513 | **/ 1514 | open: function(tick) { 1515 | tick.blackboard.set('i', 0, tick.tree.id, this.id); 1516 | }, 1517 | 1518 | /** 1519 | * Tick method. 1520 | * @method tick 1521 | * @param {Tick} tick A tick instance. 1522 | * @return {Constant} A state constant. 1523 | **/ 1524 | tick: function(tick) { 1525 | if (!this.child) { 1526 | return b3.ERROR; 1527 | } 1528 | 1529 | var i = tick.blackboard.get('i', tick.tree.id, this.id); 1530 | 1531 | if (i < this.maxLoop) { 1532 | var status = this.child._execute(tick); 1533 | 1534 | if (status == b3.SUCCESS || status == b3.FAILURE) 1535 | tick.blackboard.set('i', i+1, tick.tree.id, this.id); 1536 | 1537 | return status; 1538 | } 1539 | 1540 | return b3.FAILURE; 1541 | } 1542 | }); 1543 | 1544 | })(); 1545 | (function() { 1546 | "use strict"; 1547 | 1548 | /** 1549 | * The MaxTime decorator limits the maximum time the node child can execute. 1550 | * Notice that it does not interrupt the execution itself (i.e., the child 1551 | * must be non-preemptive), it only interrupts the node after a `RUNNING` 1552 | * status. 1553 | * 1554 | * @module b3 1555 | * @class MaxTime 1556 | * @extends Decorator 1557 | **/ 1558 | b3.MaxTime = b3.Class(b3.Decorator, { 1559 | 1560 | /** 1561 | * Node name. Default to `MaxTime`. 1562 | * @property {String} name 1563 | * @readonly 1564 | **/ 1565 | name: 'MaxTime', 1566 | 1567 | /** 1568 | * Node title. Default to `Max XXms`. Used in Editor. 1569 | * @property {String} title 1570 | * @readonly 1571 | **/ 1572 | title: 'Max ms', 1573 | 1574 | /** 1575 | * Node parameters. 1576 | * @property {String} parameters 1577 | * @readonly 1578 | **/ 1579 | parameters: {'maxTime': 0}, 1580 | 1581 | /** 1582 | * Initialization method. 1583 | * 1584 | * Settings parameters: 1585 | * 1586 | * - **maxTime** (*Integer*) Maximum time a child can execute. 1587 | * - **child** (*BaseNode*) The child node. 1588 | * 1589 | * @method initialize 1590 | * @param {Object} params Object with parameters. 1591 | * @constructor 1592 | **/ 1593 | initialize: function(params) { 1594 | b3.Decorator.prototype.initialize.call(this, params); 1595 | 1596 | if (!params.maxTime) { 1597 | throw "maxTime parameter in MaxTime decorator is an obligatory " + 1598 | "parameter"; 1599 | } 1600 | 1601 | this.maxTime = params.maxTime; 1602 | }, 1603 | 1604 | /** 1605 | * Open method. 1606 | * @method open 1607 | * @param {Tick} tick A tick instance. 1608 | **/ 1609 | open: function(tick) { 1610 | var startTime = (new Date()).getTime(); 1611 | tick.blackboard.set('startTime', startTime, tick.tree.id, this.id); 1612 | }, 1613 | 1614 | /** 1615 | * Tick method. 1616 | * @method tick 1617 | * @param {Tick} tick A tick instance. 1618 | * @return {Constant} A state constant. 1619 | **/ 1620 | tick: function(tick) { 1621 | if (!this.child) { 1622 | return b3.ERROR; 1623 | } 1624 | 1625 | var currTime = (new Date()).getTime(); 1626 | var startTime = tick.blackboard.get('startTime', tick.tree.id, this.id); 1627 | 1628 | var status = this.child._execute(tick); 1629 | if (currTime - startTime > this.maxTime) { 1630 | return b3.FAILURE; 1631 | } 1632 | 1633 | return status; 1634 | } 1635 | }); 1636 | 1637 | })(); 1638 | (function() { 1639 | "use strict"; 1640 | 1641 | /** 1642 | * RepeatUntilFailure is a decorator that repeats the tick signal until the 1643 | * node child returns `FAILURE`, `RUNNING` or `ERROR`. Optionally, a maximum 1644 | * number of repetitions can be defined. 1645 | * 1646 | * @module b3 1647 | * @class RepeatUntilFailure 1648 | * @extends Decorator 1649 | **/ 1650 | b3.RepeatUntilFailure = b3.Class(b3.Decorator, { 1651 | 1652 | /** 1653 | * Node name. Default to `RepeatUntilFailure`. 1654 | * @property {String} name 1655 | * @readonly 1656 | **/ 1657 | name: 'RepeatUntilFailure', 1658 | 1659 | /** 1660 | * Node title. Default to `Repeat Until Failure`. 1661 | * @property {String} title 1662 | * @readonly 1663 | **/ 1664 | title: 'Repeat Until Failure', 1665 | 1666 | /** 1667 | * Node parameters. 1668 | * @property {String} parameters 1669 | * @readonly 1670 | **/ 1671 | parameters: {'maxLoop': -1}, 1672 | 1673 | /** 1674 | * Initialization method. 1675 | * 1676 | * Settings parameters: 1677 | * 1678 | * - **maxLoop** (*Integer*) Maximum number of repetitions. Default to -1 1679 | * (infinite). 1680 | * - **child** (*BaseNode*) The child node. 1681 | * 1682 | * @method initialize 1683 | * @param {Object} params Object with parameters. 1684 | * @constructor 1685 | **/ 1686 | initialize: function(params) { 1687 | b3.Decorator.prototype.initialize.call(this, params); 1688 | this.maxLoop = params.maxLoop || -1; 1689 | }, 1690 | 1691 | /** 1692 | * Open method. 1693 | * @method open 1694 | * @param {Tick} tick A tick instance. 1695 | **/ 1696 | open: function(tick) { 1697 | tick.blackboard.set('i', 0, tick.tree.id, this.id); 1698 | }, 1699 | 1700 | /** 1701 | * Tick method. 1702 | * @method tick 1703 | * @param {Tick} tick A tick instance. 1704 | * @return {Constant} A state constant. 1705 | **/ 1706 | tick: function(tick) { 1707 | if (!this.child) { 1708 | return b3.ERROR; 1709 | } 1710 | 1711 | var i = tick.blackboard.get('i', tick.tree.id, this.id); 1712 | var status = b3.ERROR; 1713 | 1714 | while (this.maxLoop < 0 || i < this.maxLoop) { 1715 | status = this.child._execute(tick); 1716 | 1717 | if (status == b3.SUCCESS) { 1718 | i++; 1719 | } else { 1720 | break; 1721 | } 1722 | } 1723 | 1724 | i = tick.blackboard.set('i', i, tick.tree.id, this.id); 1725 | return status; 1726 | } 1727 | }); 1728 | 1729 | })(); 1730 | (function() { 1731 | "use strict"; 1732 | 1733 | /** 1734 | * RepeatUntilSuccess is a decorator that repeats the tick signal until the 1735 | * node child returns `SUCCESS`, `RUNNING` or `ERROR`. Optionally, a maximum 1736 | * number of repetitions can be defined. 1737 | * 1738 | * @module b3 1739 | * @class RepeatUntilSuccess 1740 | * @extends Decorator 1741 | **/ 1742 | b3.RepeatUntilSuccess = b3.Class(b3.Decorator, { 1743 | 1744 | /** 1745 | * Node name. Default to `RepeatUntilSuccess`. 1746 | * @property {String} name 1747 | * @readonly 1748 | **/ 1749 | name: 'RepeatUntilSuccess', 1750 | 1751 | /** 1752 | * Node title. Default to `Repeat Until Success`. 1753 | * @property {String} title 1754 | * @readonly 1755 | **/ 1756 | title: 'Repeat Until Success', 1757 | 1758 | /** 1759 | * Node parameters. 1760 | * @property {String} parameters 1761 | * @readonly 1762 | **/ 1763 | parameters: {'maxLoop': -1}, 1764 | 1765 | /** 1766 | * Initialization method. 1767 | * 1768 | * Settings parameters: 1769 | * 1770 | * - **maxLoop** (*Integer*) Maximum number of repetitions. Default to -1 1771 | * (infinite). 1772 | * - **child** (*BaseNode*) The child node. 1773 | * 1774 | * @method initialize 1775 | * @param {Object} params Object with parameters. 1776 | * @constructor 1777 | **/ 1778 | initialize: function(params) { 1779 | b3.Decorator.prototype.initialize.call(this, params); 1780 | this.maxLoop = params.maxLoop || -1; 1781 | }, 1782 | 1783 | /** 1784 | * Open method. 1785 | * @method open 1786 | * @param {Tick} tick A tick instance. 1787 | **/ 1788 | open: function(tick) { 1789 | tick.blackboard.set('i', 0, tick.tree.id, this.id); 1790 | }, 1791 | 1792 | /** 1793 | * Tick method. 1794 | * @method tick 1795 | * @param {Tick} tick A tick instance. 1796 | * @return {Constant} A state constant. 1797 | **/ 1798 | tick: function(tick) { 1799 | if (!this.child) { 1800 | return b3.ERROR; 1801 | } 1802 | 1803 | var i = tick.blackboard.get('i', tick.tree.id, this.id); 1804 | var status = b3.ERROR; 1805 | 1806 | while (this.maxLoop < 0 || i < this.maxLoop) { 1807 | status = this.child._execute(tick); 1808 | 1809 | if (status == b3.FAILURE) { 1810 | i++; 1811 | } else { 1812 | break; 1813 | } 1814 | } 1815 | 1816 | i = tick.blackboard.set('i', i, tick.tree.id, this.id); 1817 | return status; 1818 | } 1819 | }); 1820 | 1821 | })(); 1822 | (function() { 1823 | "use strict"; 1824 | 1825 | /** 1826 | * Repeater is a decorator that repeats the tick signal until the child node 1827 | * return `RUNNING` or `ERROR`. Optionally, a maximum number of repetitions 1828 | * can be defined. 1829 | * 1830 | * @module b3 1831 | * @class Repeater 1832 | * @extends Decorator 1833 | **/ 1834 | b3.Repeater = b3.Class(b3.Decorator, { 1835 | 1836 | /** 1837 | * Node name. Default to `Repeater`. 1838 | * @property {String} name 1839 | * @readonly 1840 | **/ 1841 | name: 'Repeater', 1842 | 1843 | /** 1844 | * Node title. Default to `Repeat XXx`. Used in Editor. 1845 | * @property {String} title 1846 | * @readonly 1847 | **/ 1848 | title: 'Repeat x', 1849 | 1850 | /** 1851 | * Node parameters. 1852 | * @property {String} parameters 1853 | * @readonly 1854 | **/ 1855 | parameters: {'maxLoop': -1}, 1856 | 1857 | /** 1858 | * Initialization method. 1859 | * 1860 | * Settings parameters: 1861 | * 1862 | * - **maxLoop** (*Integer*) Maximum number of repetitions. Default to -1 1863 | * (infinite). 1864 | * - **child** (*BaseNode*) The child node. 1865 | * 1866 | * @method initialize 1867 | * @param {Object} params Object with parameters. 1868 | * @constructor 1869 | **/ 1870 | initialize: function(params) { 1871 | b3.Decorator.prototype.initialize.call(this, params); 1872 | this.maxLoop = params.maxLoop || -1; 1873 | }, 1874 | 1875 | /** 1876 | * Open method. 1877 | * @method open 1878 | * @param {Tick} tick A tick instance. 1879 | **/ 1880 | open: function(tick) { 1881 | tick.blackboard.set('i', 0, tick.tree.id, this.id); 1882 | }, 1883 | 1884 | /** 1885 | * Tick method. 1886 | * @method tick 1887 | * @param {Tick} tick A tick instance. 1888 | **/ 1889 | tick: function(tick) { 1890 | if (!this.child) { 1891 | return b3.ERROR; 1892 | } 1893 | 1894 | var i = tick.blackboard.get('i', tick.tree.id, this.id); 1895 | var status = b3.SUCCESS; 1896 | 1897 | while (this.maxLoop < 0 || i < this.maxLoop) { 1898 | status = this.child._execute(tick); 1899 | 1900 | if (status == b3.SUCCESS || status == b3.FAILURE) { 1901 | i++; 1902 | } else { 1903 | break; 1904 | } 1905 | } 1906 | 1907 | tick.blackboard.set('i', i, tick.tree.id, this.id); 1908 | return status; 1909 | } 1910 | }); 1911 | 1912 | })(); 1913 | 1914 | (function() { 1915 | "use strict"; 1916 | 1917 | /** 1918 | * This action node returns `ERROR` always. 1919 | * 1920 | * @module b3 1921 | * @class Error 1922 | * @extends Action 1923 | **/ 1924 | b3.Error = b3.Class(b3.Action, { 1925 | 1926 | /** 1927 | * Node name. Default to `Error`. 1928 | * @property {String} name 1929 | * @readonly 1930 | **/ 1931 | name: 'Error', 1932 | 1933 | /** 1934 | * Tick method. 1935 | * @method tick 1936 | * @param {b3.Tick} tick A tick instance. 1937 | * @return {Constant} Always return `b3.ERROR`. 1938 | **/ 1939 | tick: function(tick) { 1940 | return b3.ERROR; 1941 | } 1942 | }); 1943 | 1944 | })(); 1945 | (function() { 1946 | "use strict"; 1947 | 1948 | /** 1949 | * This action node returns `FAILURE` always. 1950 | * 1951 | * @module b3 1952 | * @class Failer 1953 | * @extends Action 1954 | **/ 1955 | b3.Failer = b3.Class(b3.Action, { 1956 | 1957 | /** 1958 | * Node name. Default to `Failer`. 1959 | * @property {String} name 1960 | * @readonly 1961 | **/ 1962 | name: 'Failer', 1963 | 1964 | /** 1965 | * Tick method. 1966 | * @method tick 1967 | * @param {b3.Tick} tick A tick instance. 1968 | * @return {Constant} Always return `b3.FAILURE`. 1969 | **/ 1970 | tick: function(tick) { 1971 | return b3.FAILURE; 1972 | }, 1973 | }); 1974 | 1975 | })(); 1976 | (function() { 1977 | "use strict"; 1978 | 1979 | /** 1980 | * This action node returns RUNNING always. 1981 | * 1982 | * @module b3 1983 | * @class Runner 1984 | * @extends Action 1985 | **/ 1986 | b3.Runner = b3.Class(b3.Action, { 1987 | 1988 | /** 1989 | * Node name. Default to `Runner`. 1990 | * @property {String} name 1991 | * @readonly 1992 | **/ 1993 | name: 'Runner', 1994 | 1995 | /** 1996 | * Tick method. 1997 | * @method tick 1998 | * @param {b3.Tick} tick A tick instance. 1999 | * @return {Constant} Always return `b3.RUNNING`. 2000 | **/ 2001 | tick: function(tick) { 2002 | return b3.RUNNING; 2003 | } 2004 | }); 2005 | 2006 | })(); 2007 | (function() { 2008 | "use strict"; 2009 | 2010 | /** 2011 | * This action node returns `SUCCESS` always. 2012 | * 2013 | * @module b3 2014 | * @class Succeeder 2015 | * @extends Action 2016 | **/ 2017 | b3.Succeeder = b3.Class(b3.Action, { 2018 | 2019 | /** 2020 | * Node name. Default to `Succeeder`. 2021 | * @property {String} name 2022 | * @readonly 2023 | **/ 2024 | name: 'Succeeder', 2025 | 2026 | /** 2027 | * Tick method. 2028 | * @method tick 2029 | * @param {b3.Tick} tick A tick instance. 2030 | * @return {Constant} Always return `b3.SUCCESS`. 2031 | **/ 2032 | tick: function(tick) { 2033 | return b3.SUCCESS; 2034 | } 2035 | }); 2036 | 2037 | })(); 2038 | (function() { 2039 | "use strict"; 2040 | 2041 | /** 2042 | * Wait a few seconds. 2043 | * 2044 | * @module b3 2045 | * @class Wait 2046 | * @extends Action 2047 | **/ 2048 | b3.Wait = b3.Class(b3.Action, { 2049 | 2050 | /** 2051 | * Node name. Default to `Wait`. 2052 | * @property {String} name 2053 | * @readonly 2054 | **/ 2055 | name: 'Wait', 2056 | 2057 | /** 2058 | * Node title. Default to `Wait XXms`. Used in Editor. 2059 | * @property {String} title 2060 | * @readonly 2061 | **/ 2062 | title: 'Wait ms', 2063 | 2064 | /** 2065 | * Node parameters. 2066 | * @property {String} parameters 2067 | * @readonly 2068 | **/ 2069 | parameters: {'milliseconds': 0}, 2070 | 2071 | /** 2072 | * Initialization method. 2073 | * 2074 | * Settings parameters: 2075 | * 2076 | * - **milliseconds** (*Integer*) Maximum time, in milliseconds, a child 2077 | * can execute. 2078 | * 2079 | * @method initialize 2080 | * @param {Object} settings Object with parameters. 2081 | * @constructor 2082 | **/ 2083 | initialize: function(settings) { 2084 | settings = settings || {}; 2085 | 2086 | b3.Action.prototype.initialize.call(this); 2087 | this.endTime = settings.milliseconds || 0; 2088 | }, 2089 | 2090 | /** 2091 | * Open method. 2092 | * @method open 2093 | * @param {Tick} tick A tick instance. 2094 | **/ 2095 | open: function(tick) { 2096 | var startTime = (new Date()).getTime(); 2097 | tick.blackboard.set('startTime', startTime, tick.tree.id, this.id); 2098 | }, 2099 | 2100 | /** 2101 | * Tick method. 2102 | * @method tick 2103 | * @param {Tick} tick A tick instance. 2104 | * @return {Constant} A state constant. 2105 | **/ 2106 | tick: function(tick) { 2107 | var currTime = (new Date()).getTime(); 2108 | var startTime = tick.blackboard.get('startTime', tick.tree.id, this.id); 2109 | 2110 | if (currTime - startTime > this.endTime) { 2111 | return b3.SUCCESS; 2112 | } 2113 | 2114 | return b3.RUNNING; 2115 | } 2116 | }); 2117 | 2118 | })(); -------------------------------------------------------------------------------- /lib/openDoorNodes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var b3 = require('./behavior3js').b3; 3 | 4 | function init(action, condition) { 5 | actions(action); 6 | conditions(condition); 7 | } 8 | 9 | function actions(action) { 10 | action('walkToDoor', { 11 | tick: function(tick) { 12 | tick.blackboard.set('walking', 1); 13 | let name = tick.blackboard.get('name'); 14 | console.log(name + ' is walking to the door'); 15 | 16 | return b3.SUCCESS; 17 | } 18 | }); 19 | action('openDoor', { 20 | tick: function(tick) { 21 | tick.blackboard.set('walking', 0); 22 | let name = tick.blackboard.get('name'); 23 | console.log(name + ' is opening the door'); 24 | return b3.SUCCESS; 25 | } 26 | }); 27 | action('smashDoor', { 28 | tick: function(tick) { 29 | tick.blackboard.set('walking', 0); 30 | let name = tick.blackboard.get('name'); 31 | console.log(name + ' is smashing the door'); 32 | 33 | return b3.SUCCESS; 34 | } 35 | }); 36 | action('walkThroughDoor', { 37 | tick: function(tick) { 38 | tick.blackboard.set('walking', 1); 39 | let name = tick.blackboard.get('name'); 40 | console.log(name + ' is walking through the door'); 41 | 42 | return b3.SUCCESS; 43 | } 44 | }); 45 | action('closeDoor', { 46 | tick: function(tick) { 47 | tick.blackboard.set('walking', 0); 48 | let name = tick.blackboard.get('name'); 49 | console.log(name + ' is closing the door'); 50 | 51 | return b3.SUCCESS; 52 | } 53 | }); 54 | } 55 | 56 | function conditions(condition) { 57 | condition('canIunlockTheDoor', { 58 | tick: function(tick) { 59 | var skill = tick.blackboard.get('lockpick-level'); 60 | let name = tick.blackboard.get('name'); 61 | if (!skill) { 62 | skill = 0; 63 | tick.blackboard.set('lockpick-level', skill); 64 | } 65 | 66 | if (skill > 5) { 67 | console.log(name + ' picked the lock'); 68 | return b3.SUCCESS; 69 | } 70 | 71 | console.log(name + ' could not pick the lock'); 72 | 73 | skill += 1; 74 | tick.blackboard.set('lockpick-level', skill); 75 | return b3.FAILURE; 76 | } 77 | }); 78 | condition('IsDoorUnlocked', { 79 | tick: function(tick) { 80 | let name = tick.blackboard.get('name'); 81 | var locked = tick.blackboard.get('locked'); 82 | if (!locked) { 83 | return b3.SUCCESS; 84 | } 85 | console.log(name + ' notices the door is locked'); 86 | return b3.FAILURE; 87 | } 88 | }); 89 | } 90 | 91 | module.exports = { 92 | init: init 93 | }; 94 | -------------------------------------------------------------------------------- /lib/treeLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var b3 = require('./behavior3js').b3; 3 | var tree = {}; 4 | var ai = {}; 5 | 6 | /** 7 | * Action node wrapper for loadNode 8 | * 9 | * @param {String} name 10 | * @param {Object} properties 11 | */ 12 | function loadAction(name, properties) { 13 | return loadNode(name, properties, b3.Action); 14 | } 15 | 16 | /** 17 | * Condition node wrapper for loadNode 18 | * 19 | * @param {String} name 20 | * @param {Object} properties 21 | */ 22 | function loadCondition(name, properties) { 23 | return loadNode(name, properties, b3.Condition); 24 | } 25 | 26 | /** 27 | * Load the custom node definition and functionality 28 | * 29 | * @param {String} name 30 | * @param {Object} properties 31 | * @param {String} type 32 | */ 33 | function loadNode(name, properties, type) { 34 | var node = b3.Class(type); 35 | var nodeProto = node.prototype; 36 | nodeProto.name = name; 37 | for (var prop in properties) { 38 | nodeProto[prop] = properties[prop]; 39 | } 40 | tree[name] = node; 41 | return node; 42 | } 43 | 44 | /** 45 | * Init the module 46 | */ 47 | function init() { 48 | require('./openDoorNodes').init(loadAction, loadCondition); 49 | ai = {'guy': new b3.BehaviorTree()}; 50 | ai.guy.load(require('../resources/openDoor.json'), tree); 51 | } 52 | 53 | module.exports = { 54 | ai: function() {return ai;}, 55 | init: init 56 | }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "behavior3test", 3 | "version": "1.0.0", 4 | "description": "sample project implementing behavior3js", 5 | "main": "app.js", 6 | "scripts": { 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/efbenson/behavior3Test.git" 11 | }, 12 | "keywords": [ 13 | "behavior", 14 | "tree", 15 | "behavior tree", 16 | "behavior3", 17 | "b3" 18 | ], 19 | "author": "efbenson@gmail.com", 20 | "license": "MIT", 21 | "dependencies": { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/openDoor.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "A Behavior Tree", 3 | "description": "", 4 | "root": "4976592d-0fe9-4088-8d8e-67a2c0ba98de", 5 | "display": { 6 | "camera_x": 1280, 7 | "camera_y": 647.5, 8 | "camera_z": 1, 9 | "x": 0, 10 | "y": 0 11 | }, 12 | "properties": {}, 13 | "nodes": { 14 | "d14c7d44-66c8-4d21-83e1-e2041a0cfe73": { 15 | "id": "d14c7d44-66c8-4d21-83e1-e2041a0cfe73", 16 | "name": "Sequence", 17 | "title": "Sequence", 18 | "description": "", 19 | "display": { 20 | "x": -600, 21 | "y": -252 22 | }, 23 | "properties": {}, 24 | "children": [ 25 | "7d493574-613c-4703-8946-82f694c5780e", 26 | "6e0ae324-d48d-4084-89f7-54f89061d82d" 27 | ] 28 | }, 29 | "4976592d-0fe9-4088-8d8e-67a2c0ba98de": { 30 | "id": "4976592d-0fe9-4088-8d8e-67a2c0ba98de", 31 | "name": "Sequence", 32 | "title": "Sequence", 33 | "description": "", 34 | "display": { 35 | "x": -876, 36 | "y": -384 37 | }, 38 | "properties": {}, 39 | "children": [ 40 | "fb0f5694-af5f-46a9-82c0-22b9fb85e2b3", 41 | "5fa8758b-06eb-429a-8c00-a92ec2c54934", 42 | "d799cbce-9e9e-4156-845e-4130a994217f", 43 | "48dcae93-0cd3-428b-8dea-7cc48a19f91e" 44 | ] 45 | }, 46 | "5fa8758b-06eb-429a-8c00-a92ec2c54934": { 47 | "id": "5fa8758b-06eb-429a-8c00-a92ec2c54934", 48 | "name": "Priority", 49 | "title": "Priority", 50 | "description": "", 51 | "display": { 52 | "x": -756, 53 | "y": -276 54 | }, 55 | "properties": {}, 56 | "children": [ 57 | "77f35615-10c2-433d-b2d2-b7976c3bbdb6", 58 | "d14c7d44-66c8-4d21-83e1-e2041a0cfe73", 59 | "0c0acf46-97e7-477c-bed2-54912f01194e" 60 | ] 61 | }, 62 | "fb0f5694-af5f-46a9-82c0-22b9fb85e2b3": { 63 | "id": "fb0f5694-af5f-46a9-82c0-22b9fb85e2b3", 64 | "name": "walkToDoor", 65 | "title": "walkToDoor", 66 | "description": "", 67 | "display": { 68 | "x": -592, 69 | "y": -512 70 | }, 71 | "parameters": {}, 72 | "properties": {} 73 | }, 74 | "b555ede2-1600-4cdc-9df4-111454e26dac": { 75 | "id": "b555ede2-1600-4cdc-9df4-111454e26dac", 76 | "name": "openDoor", 77 | "title": "openDoor", 78 | "description": "", 79 | "display": { 80 | "x": -448, 81 | "y": -304 82 | }, 83 | "parameters": {}, 84 | "properties": {} 85 | }, 86 | "6e0ae324-d48d-4084-89f7-54f89061d82d": { 87 | "id": "6e0ae324-d48d-4084-89f7-54f89061d82d", 88 | "name": "openDoor", 89 | "title": "openDoor", 90 | "description": "", 91 | "display": { 92 | "x": -444, 93 | "y": -168 94 | }, 95 | "properties": {} 96 | }, 97 | "0c0acf46-97e7-477c-bed2-54912f01194e": { 98 | "id": "0c0acf46-97e7-477c-bed2-54912f01194e", 99 | "name": "smashDoor", 100 | "title": "smashDoor", 101 | "description": "", 102 | "display": { 103 | "x": -444, 104 | "y": -96 105 | }, 106 | "properties": {} 107 | }, 108 | "d799cbce-9e9e-4156-845e-4130a994217f": { 109 | "id": "d799cbce-9e9e-4156-845e-4130a994217f", 110 | "name": "walkThroughDoor", 111 | "title": "walkThroughDoor", 112 | "description": "", 113 | "display": { 114 | "x": -684, 115 | "y": -24 116 | }, 117 | "properties": {} 118 | }, 119 | "48dcae93-0cd3-428b-8dea-7cc48a19f91e": { 120 | "id": "48dcae93-0cd3-428b-8dea-7cc48a19f91e", 121 | "name": "closeDoor", 122 | "title": "closeDoor", 123 | "description": "", 124 | "display": { 125 | "x": -696, 126 | "y": 60 127 | }, 128 | "properties": {} 129 | }, 130 | "7d493574-613c-4703-8946-82f694c5780e": { 131 | "id": "7d493574-613c-4703-8946-82f694c5780e", 132 | "name": "canIunlockTheDoor", 133 | "title": "canIunlockTheDoor", 134 | "description": "", 135 | "display": { 136 | "x": -432, 137 | "y": -240 138 | }, 139 | "properties": {} 140 | }, 141 | "77f35615-10c2-433d-b2d2-b7976c3bbdb6": { 142 | "id": "77f35615-10c2-433d-b2d2-b7976c3bbdb6", 143 | "name": "Sequence", 144 | "title": "Sequence", 145 | "description": "", 146 | "display": { 147 | "x": -592, 148 | "y": -352 149 | }, 150 | "parameters": {}, 151 | "properties": {}, 152 | "children": [ 153 | "ae8c258c-ee24-4741-8914-b3e3b4ff4eca", 154 | "b555ede2-1600-4cdc-9df4-111454e26dac" 155 | ] 156 | }, 157 | "ae8c258c-ee24-4741-8914-b3e3b4ff4eca": { 158 | "id": "ae8c258c-ee24-4741-8914-b3e3b4ff4eca", 159 | "name": "IsDoorUnlocked", 160 | "title": "IsDoorUnlocked", 161 | "description": "", 162 | "display": { 163 | "x": -448, 164 | "y": -368 165 | }, 166 | "parameters": {}, 167 | "properties": {} 168 | } 169 | }, 170 | "custom_nodes": [ 171 | { 172 | "name": "walkToDoor", 173 | "title": "walkToDoor", 174 | "category": "action" 175 | }, 176 | { 177 | "name": "openDoor", 178 | "title": "openDoor", 179 | "category": "action" 180 | }, 181 | { 182 | "name": "smashDoor", 183 | "title": "smashDoor", 184 | "category": "action" 185 | }, 186 | { 187 | "name": "walkThroughDoor", 188 | "title": "walkThroughDoor", 189 | "category": "action" 190 | }, 191 | { 192 | "name": "closeDoor", 193 | "title": "closeDoor", 194 | "category": "action" 195 | }, 196 | { 197 | "name": "canIunlockTheDoor", 198 | "title": "canIunlockTheDoor", 199 | "category": "condition" 200 | }, 201 | { 202 | "name": "IsDoorUnlocked", 203 | "title": "IsDoorUnlocked", 204 | "category": "condition" 205 | } 206 | ] 207 | } --------------------------------------------------------------------------------