Code Monkey home page Code Monkey logo

behaviortree.js's Introduction

BehaviorTree.js

A JavaScript implementation of Behavior Trees. They are useful for implementing AIs. If you need more information about Behavior Trees, look on GameDevAIPro, there is a nice article on Behavior Trees from Alex Champandard and Philip Dunstan.

TypeScript support?

If you need TypeScript support, please check out the typescript branch which will be the upcoming 3.0 release and if you have some questions and comments please make them known.

Features

  • The needed: Sequences, Selectors, Tasks
  • The extended: Decorators

Installation

If you use npm:

npm install behaviortree

or using yarn:

yarn add behaviortree

Dependencies?

This package has no own dependencies.

How to use

First, I should mention that it is possible to use this library also in common-js environment like node v8. For this to work, you should switch all import statements with require() statements.

So instead of

import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'

just use

const { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } = require('behaviortree')

I use the new ES modules syntax, because I think it is very readable. So all the code is written like this. To see working examples of both versions visit/clone the examples’ repo.

Creating a simple Task

A task is a simple Node (to be precise a leaf node), which takes care of all the dirty work in it's run method, which returns either true, false, or "running". For clarity, and to be flexible, please use the provided exported constants for those return values (SUCCESS, FAILURE, RUNNING).

Each method of your task receives the blackboard, which you assign when instantiating the BehaviorTree. A blackboard is basically a object, which holds data and methods all the task need to perform their work and to communicate with the world.

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
  }
})

The methods:

  • start - Called before run is called. But not if the task is resuming after ending with this.running()
  • end - Called after run is called. But not if the task finished with this.running()
  • run - Contains the main things you want the task to do

Creating a Sequence

A Sequence will call every of it's sub nodes one after each other until one node fails (returns FAILURE) or all nodes were called. If one node calls fails the Sequence will return FAILURE itself, else it will call 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
  ]
})

Creating a priority selector

A Selector calls every node in its list until one node returns SUCCESS, then itself returns as success. If none of it's sub node calls SUCCESS the selector returns 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
  ]
})

Creating a Random Selector

A Random selector just calls one of its subnode randomly, if that returns RUNNING, it will be called again on next run.

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
  ]
})

Creating a BehaviorTree instance

Creating an instance of a behavior tree is fairly simple. Just instantiate the BehaviorTree class and specify the shape of the tree, using the nodes mentioned above and the blackboard the nodes can use.

import { BehaviorTree } from 'behaviortree'
var bTree = new BehaviorTree({
  tree: mySelector,
  blackboard: {}
})

Run through the BehaviorTree

The blackboard you specified will be passed into every start(), end() and run() method as first argument. You can use it, to let the behavior tree know, on which object (e.g. artificial player) it is running, let it interact with the world or hold bits of state if you need. To run the tree, you can call step() whenever you have time for some AI calculations in your game loop.

bTree.step()

Using a lookup table for your tasks

BehaviorTree is coming with a internal registry in which you can register tasks and later reference them in your nodes by their names, that you choose. This is really handy, if you need the same piece of behavior in multiple trees, or want to separate the defining of tasks and the construction of the trees.

// Register a task:
BehaviorTree.register('testtask', myTask)
// Or register a sequence or priority:
BehaviorTree.register('test sequence', mySequence)

Which you now can simply refer to in your nodes, like:

import { Selector } from 'behaviortree'
const mySelector = new Selector({
  nodes: ['my awesome task', 'another awe# task to do']
})

Using the registry has one more benefit, for simple Tasks with only one run method, there is a short way to write those:

BehaviorTree.register('testtask', (blackboard) => {
  console.log('I am doing stuff')
  return SUCCESS
})

Now putting it all together

And now an example of how all could work together.

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 this example the following happens: each pass on the setInterval (our game loop), the dog barks – we implemented this with a registered node, because we do this twice – then it walks randomly around, then it barks again and then if it finds itself standing beside a tree it pees on the tree.

Decorators

Every node can also be a Decorator, which wraps a regular (or another decorated) node and either control their value or calling, add some conditions or do something with their returned state. In the src/decorators directory you'll find some already implemented decorators for inspiration or use, like an InvertDecorator which negates the return value of the decorated node or a CooldownDecorator which ensures the node is only called once within a cool downtime period.

const decoratedSequence = new InvertDecorator({
  node: 'awesome sequence doing stuff'
})

Creating own Decorators

To create an own decorator. You simply need a class that extends the Decorator class and overrides the decorate method. Simply look within the src/decorators sub folder to check some reference implementations.

Beware that you cannot simply instantiate the Decorator class and pass in the decorate methods as a blueprint as a dynamical decorator, because the way things works right now.

Using built-in Decorators

There are several "simple" decorators already built for your convenience. Check the src/decorators directory for more details (and the specs for what they are doing). Using them is as simple as:

import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE, decorators } from 'behaviortree'

const { AlwaysSucceedDecorator } = decorators

Importing BehaviorTree defintions from JSON files

There is a BehaviorTreeImporter class defined that can be used to fill a BehaviorTree instance out of a JSON definition for a tree. A definition structure looks like this:

{
  "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" }
  ]
}

Through the type property, the importer looks up Decorators, Selectors, Sequences and your own defined classes from an internal type definition as well as tasks from the BehaviorTree registry, and returns an object, that can be used as tree within the BehaviorTree constructor.

Using traditional-style requires

If you don't like the new import-statements, you should still be able to use the traditional require-statements:

const {
  BehaviorTree,
  Sequence,
  Task,
  SUCCESS,
  FAILURE,
  decorators: { AlwaysSucceedDecorator }
} = require('behaviortree')

Introspecting the Tree (debugging the Tree)

You can add a introspector parameter to the step-method containing an instance of the Introspector class or another class implementing a similar interface. Doing that allows you to gather useful statistics/data about every run of your behavior tree and shows you, which tasks did run and returned which results. Useful in gaining an understanding about the correctness of the tree.

But don't do this on a production environment, because the work that is done there is simply not needed for regular evaluation.

const { Introspector } = require('behaviortree')
const introspector = new Introspector()
bTree.step({ introspector })
console.log(introspector.lastResult)

That would result in something like:

{
  name: 'select',
  result: Symbol(running),
  children: [
    {
      name: 'targeting',
      result: false
    },
    {
      name: 'jump',
      result: Symbol(running)
    }
  ]
}

Contributing

You want to contribute? If you have some ideas or critics, just open an issue, here on GitHub. If you want to get your hands dirty, you can fork this repository. But note: If you write code, don't forget to write tests. And then make a pull request. I'll be happy to see what's coming.

Running tests

Tests are done with jest, and I use yarn to manage packages and lock versions.

yarn
yarn test

Version history

  • 2.1.0
    • Rework debug handling and implement it as using an Introspector-Interface & -Module
    • Fix problem with start & end calling in RUNNING branching nodes
    • Add start & end callbacks to Decorators
    • Add UMD package for direct use in borwsers
  • 2.0.5 - Fix edge case that did not call start on subsequent branching nodes after a running one
  • 2.0.4 - Fix bug that start not called after run in branching nodes
  • 2.0.3 - Add decorators to exports
  • 2.0.2 - Now with working node.js build (as well as babel build)
  • 2.0.0 - Complete ES7 rewrite and improvement on ways it works and how it can be used
  • 1.0.4 - Fix resuming in priority nodes
  • 1.0.3 - Removed a useless console.log statement
  • 1.0.2 - Supporting NodeJS now. Bumped to 1.0.2 because of NPM package
  • 0.9.2 - Added AlwaysSucceedDecorator and AlwaysFailDecorator
  • 0.9.1 - Fixed run method in Decorator
  • 0.9.0 - Added Decorators and the InvertDecorator
  • 0.8.0 - Added the Random Selector
  • 0.7.0 - first functional complete release

MIT License

Copyright (C) 2013-2020 Georg Tavonius

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 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 SOFTWARE.

behaviortree.js's People

Contributors

antoinemacia avatar calamari avatar dependabot[bot] avatar mreinstein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

behaviortree.js's Issues

debug: true not working

I've noticed that sometimes my tests will fail if I try to do bTree.step({debug: true}) but they work if I dont enable debug. I get the following exception below:

TypeError: Cannot read property 'map' of undefined
    at t.get (/home/wrk/coordinator/node_modules/behaviortree/dist/index.node.js:1:8666)
    at t.value (/home/wrk/coordinator/node_modules/behaviortree/dist/index.node.js:1:8463)
    at e.value (/home/wrk/coordinator/node_modules/behaviortree/dist/index.node.js:1:4435)
    at Object.route (/home/wrk/coordinator/lib/bt.js:31:542)

I am using 2.0.5 right now. Have you run across this?

CooldownDecorator example

Hi,
This package is really useful but I'm struggling to implement a cooldown Decorator. Could you advise me why this doesn't work:
First i register the task:

BehaviorTree.register(
    "fire",
    new Task({
      run: function (enemyShip) {
        enemyShip.fireMissile();
        return SUCCESS;
      },
    })
  );

Then i decorate it:

const decoratedTask = new CooldownDecorator({
    node: "fire",
    config: 5,
  });

Then I use it in the tree:

const tree = new Selector({
    nodes: [
      new Sequence({
        nodes: [
          new Task({
            run: function (enemyShip) {
              return enemyShip.checkIsInMissileRange(environmentDetails)
                ? SUCCESS
                : FAILURE;
            },
          }),
          new Task({
            run: function (enemyShip) {
              enemyShip.haltToStop(environmentDetails, setTargetShipDetails);
              return SUCCESS;
            },
          }),
          decoratedTask,
        ],
      }),
      new Sequence({
        nodes: [
          new Task({
            run: function (enemyShip) {
              enemyShip.moveToTarget(environmentDetails, setTargetShipDetails);
              return SUCCESS;
            },
          }),
        ],
      }),
    ],
  });

However the task 'fire' is still called for every step of the behaviour tree. Thanks in advance!

Question: Is there a non-remember Selector?

Hello, I think the current Selector will remember the last RUNNING node, and in next tick running directly start from it, ignoring any siblings in front of it, even the sibling at font will return SUCCESS now, right?

Test codes:
import { BehaviorTree, Sequence, Selector, Task, SUCCESS, FAILURE, RUNNING } from 'behaviortree'

let count = 0;
const blackboard = {};

BehaviorTree.register('000', new Task({
  run: function (blackboard ) {
    console.log('000')
    if (count <= 3) {
      return FAILURE
    } else {
      return SUCCESS
    }
  }
}))
BehaviorTree.register('111', new Task({
  run: function (blackboard ) {
    console.log('111')
    return RUNNING
  }
}))

const tree = new Selector({nodes: [
  '000',
  '111',
]})

const bTree = new BehaviorTree({
  tree: tree,
  blackboard: blackboard
})

function step() {
  bTree.step()
  console.log('-----------------------------')
  count++;
  if (count < 10) setTimeout(step, 1000);
}
step();
Output:
000
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------
111
-----------------------------

I want to ask that, is there a non-remember Selector, which check all children from first until fail one every tick?

Expected output:
000
111
-----------------------------
000
111
-----------------------------
000
111
-----------------------------
000
-----------------------------
000
-----------------------------
000
-----------------------------
000
-----------------------------
000
-----------------------------
000
-----------------------------
000
-----------------------------

Optimize handling of attached objects and Tree creations

I think the best way to create a Tree would be something like this:

const instance = new BTree.Instance({
  name: 'Maybe a name',
  object: { /* the object that is passed through everything */,
  tree: tree // any instance of a node 
})

This also makes it possible to register different nodes to different trees and we get rid of the awkward setObject method.

Long running taks

I think there is a bug in how the tree handles tasks calling "running" multiples times. I will try to explain with this example:

The tree:

  • Build a wood wall (Sequence)
    • do I have wood? (task, return fail)
    • go find wood (task, return running)

Step 1:
the bot doesn't have wood, so it go find it. First node call fail, second node call running
Step 2:
the tree go directly to "go find wood", and the node call running
Step 3:
I expected the tree returning to the same task, as it will call running several times. But instead it started over the first task

Is this the expected behaviour?

Failing to run the examples in README.md

Failing to run the examples in README.md

I installed behaviortree with

npm i --save behaviortree

Then I copied and pasted the following code (taken from README.md) into a file called bt_test.js.

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)

Then I tried to run it with

node bt_test.js

Firstly the compiler complained about }); on line 33, which I removed.

Secondly, I had problems with the import keyword.

Running bt_test.js gave me the following error:

$ node bt_test.js
(function (exports, require, module, __filename, __dirname) { import BehaviorTree, { Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'
                                                                     ^^^^^^^^^^^^
SyntaxError: Unexpected identifier
    at new Script (vm.js:74:7)
    at createScript (vm.js:246:10)
    at Object.runInThisContext (vm.js:298:10)
    at Module._compile (internal/modules/cjs/loader.js:670:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:713:10)
    at Module.load (internal/modules/cjs/loader.js:612:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:551:12)
    at Function.Module._load (internal/modules/cjs/loader.js:543:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:744:10)
    at startup (internal/bootstrap/node.js:238:19)

After reading the documentation at https://nodejs.org/api/esm.html, I
ranamed the file from bt_test.js to bt_test.mjs and added the
--experimental-modules option to the run invocation.

Still I had an error:

$ node --experimental-modules bt_test.mjs
(node:19929) ExperimentalWarning: The ESM module loader is experimental.
file:///home/giacomo/DEV_experimental/bt_nodejs/bt_test.mjs:1
import BehaviorTree, { Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'
                       ^^^^^^^^
SyntaxError: The requested module 'behaviortree' does not provide an export named 'Sequence'
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:89:21)

I tried both nodejs version 9.11 and nodejs version 10.4.

Can anyone help me compiling and running the example above?

Bad usage examples

Just want to have this clarified, this second snippet in the screenshot has no relation to the first snippet..

image

Is it trying to say this:

// Register a task:
BehaviorTree.register('testtask', myTask)
// Or register a sequence or priority:
BehaviorTree.register('test sequence', mySequence)

Which you now can simply refer to in your nodes, like:

import { Selector } from 'behaviortree'
const mySelector = new Selector({
  nodes: ['testtask', 'test sequence']
})

Or am I misunderstanding something?

Add TypeScript support

I guess, that will be a version 3, since I think there will be some opportunities to make the API a bit better. Let's see.

What is the correct way to import behaviortree in a webpack app? (You may need an appropriate loader to handle this file type)

As of today I have been using behaviortree in node and it's been working great.

I am now in the process of adding behaviortree in my gui app, built with webpack, and I can't seem to get it working.
I either get a

You may need an appropriate loader to handle this file type

or

Selector is not a constructor

I tried many things, from all possible import/require, paths to the node_module combinations, with no luck.
I realize this might just be me being still ignorant about webpack but I can't find a way to make it work.
What would be the best way to create behaviortree objects in a webpack packed app?

This is the basic code I am using:

import {GBranchNode} from "./BranchNode";  //my implementation of the GUI Branchnode class, not BT
import * as main from "../src";
import Sequence from "behaviortree/src";    //behaviortree Sequence class

export class GRoot extends GBranchNode{
    constructor(context, x = 10, y = 10)
    {
        super("Root", context, false, x, y)
        this.priority = 0;
        this.setPriority(this.priority)
    }

    action(){
        super.action()
        main.result = new Sequence({nodes: []})         //Constructor is not defined (or, "get a loader message")
    }
}

Decorators in a tree stored in the register exhibit global state

Consider the following code where I've created a decorator that will add a custom cooldown timer to a BT that will start as soon as the tree experiences a SUCCESS in the sequence that it is wrapping. This concept works excellent when there is 1 tree present.

However, as soon as I instance the class a second time, the two trees exhibit shared behavior. Meaning, as soon as one of the trees experiences a SUCCESS, both of their decorators fire and the cooldown begins for BOTH trees instead of just the one that had the SUCCESS as you would expect.

// Custom decorator that adds a cooldown after a SUCCESS
// Modified from the CooldownDecorator in the repo.
// Original CooldownDecorator times the cooldown from the START of
// the sequence. I need to time from the end of the sequence getting a SUCCESS.
class SuccessCooldownDecorator extends Decorator {
  nodeType = 'SuccessCooldownDecorator'

  setConfig ({ cooldown }) {
    this.config = { cooldown }
  }

  decorate (run) {
    if (this.lock) {
      return FAILURE
    }
    const result = run()
    if (result === SUCCESS) {
      this.lock = true
      setTimeout(() => {
        this.lock = false
      }, this.config.cooldown * 1000)
    }
    return result
  }
}

// Create a new sequence
BehaviorTree.register('my-sequence', new Sequence({
  nodes: [ ... ]
}))

// Implement a custom Decorator around the Sequence
BehaviorTree.register('decorated', new SuccessCooldownDecorator({
  node: 'my-sequence',
  config: { cooldown: 2 }
}))

// Place the BT inside of a class
class MyContainer {
  contructor () {
    this.tree = new BehaviorTree({
      node: 'decorated',
      blackboard: { ... }
    })
  }

  update () {
    this.tree.step()
  }
}

const inst1 = new MyContainer()
const inst2 = new MyContainer()

If I move the decorator being created out of the register and into the class constructor, it works as expected.

class MyContainer {
  contructor () {
    this.tree = new BehaviorTree({
      node: new SuccessCooldownDecorator({
        node: 'my-sequence',
        config: { cooldown: 2 }
      }),
      blackboard: { ... }
    })
  }
  ...
}

I tried looking through the Decorator and Node classes to see if I could identify the issue I'm having, but I couldn't find any instances of any global state being used. I'm not sure why doing new Sequence in the global scope works fine, but doing new Decorator in the global scope doesn't allow this use case.

Is my custom decorator implementation incorrect? Am I doing something else wrong?

Condition node implementation request

It seems to me that there is no condition node object.

It would be nice to have it, to allow the user to build a tree like this:

{
  "type": "sequence",
  "name": "the root",
  "nodes": [
    {
      "type": "findBall",
      "name": "ACTION/TASK: look around and try to see a ball"
    },
    {
      "type": "isBallNear",
      "name": "CONDITION: is ball near?",
      "nodes": [
        {
          "type": "graspBall",
          "name": "ACTION/TASK: grasp the ball"
        }
      ]
    }
  ]
}

condition_2018-06-27-15-13-58_1452x517

I understand that a condition can be implemented with a sequence.
The above would be translated like this:

{
  "type": "sequence",
  "name": "the root",
  "nodes": [
    {
      "type": "findBall",
      "name": "ACTION/TASK: look around and try to see a ball"
    },
    {
      "type": "sequence",
      "name": "sequence used to implement a condition",
      "nodes": [
        {
          "type": "isBallNear",
          "name": "ACTION/TASK used as CONDITION: is ball near?"
        },
        {
          "type": "graspBall",
          "name": "ACTION/TASK: grasp the ball"
        }
      ]
    }
  ]
}

sequence_implementing_condition_2018-06-27-15-15-47_1452x927

Although the two trees yield to the same behaviour, I think that having the condition node can be more readable. So I am asking if it is possible to implement the condition node.

Not able to create a custom decorator

I have an issue while trying to create a custom decorator.

I have tried a MWE using a decorator that is already present in the library, namely CooldownDecorator, and it works just fine.

But when I try to build a custom decorator, problems arise.

I created the following decorator:

$ cat IsBallNearDecorator.js
// const { run, SUCCESS, FAILURE, Decorator } = require('behaviortree') // (*)
import { run, SUCCESS, FAILURE, Decorator } from 'behaviortree';

export default class IsBallNearDecorator extends Decorator {
// class IsBallNearDecorator extends Decorator { // (*)

  nodeType = 'IsBallNearDecorator';

  decorate (run, blackboard) {
    console.log("blackboard.isBallNear: "+blackboard.isBallNear);
    if (blackboard.isBallNear)
      return run()
    else
      return FAILURE
  }
}

// module.exports = IsBallNearDecorator // (*)

Which is very similar to the test src/BehaviorTreeImporter.spec.js that is present in the library of the behaviortree module.

The class IsBallNearDecorator is imported into a minimal behaviortree, like this:

$ cat bt__IsBallNearDecorator.js
// const { BehaviorTreeImporter, BehaviorTree, Sequence, Task, SUCCESS, FAILURE } = require('behaviortree') // (*)
import { BehaviorTreeImporter, BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree';

import { IsBallNearDecorator } from './IsBallNearDecorator.js';
// const IsBallNearDecorator = require('./IsBallNearDecorator'); // (*)

const takeBallTask = new Task({
  run: function(blackboard) {
    console.log("DEBUG1: takeBall task");
    return SUCCESS;
  }
});

let importer = new BehaviorTreeImporter();

const json = {
  type: 'selector',
  name: 'the root',
  nodes: [
    {
      type: 'IsBallNearDecorator',
//      type: 'cooldown', cooldown: 1,
      name: 'testing around',
      node: {
	type: 'takeBall',
	name: 'take the ball'
      }
    }
  ]
};

BehaviorTree.register('IsBallNearDecorator', new IsBallNearDecorator());
BehaviorTree.register('takeBall', takeBallTask);

const blb = {
  isBallNear: "asdf"
}

const bTree = new BehaviorTree({
  tree: importer.parse(json),
  blackboard: blb,
})

setInterval(function () {
  bTree.step()
}, 1000 )

When trying to execute the tree, I get the following error:

$ babel-node bt__IsBallNearDecorator.js 
/usr/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/index.js:558
      throw err;
      ^

SyntaxError: /home/giacomo/DEV/behaviortree--nodejs--Calamari/IsBallNearDecorator.js: Unexpected token (7:11)
   5 | // class IsBallNearDecorator extends Decorator { // (*)
   6 | 
>  7 |   nodeType = 'IsBallNearDecorator';
     |            ^
   8 | 
   9 |   decorate (run, blackboard) {
  10 |     console.log("blackboard.isBallNear: "+blackboard.isBallNear);
    at Parser.pp$5.raise (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:4454:13)
    at Parser.pp.unexpected (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:1761:8)
    at Parser.pp$1.parseClassProperty (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:2571:50)
    at Parser.pp$1.parseClassBody (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:2516:34)
    at Parser.pp$1.parseClass (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:2406:8)
    at Parser.pp$1.parseExport (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:2642:19)
    at Parser.pp$1.parseStatement (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:1884:74)
    at Parser.pp$1.parseBlockBody (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:2268:21)
    at Parser.pp$1.parseTopLevel (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:1778:8)
    at Parser.parse (/usr/lib/node_modules/babel-cli/node_modules/babylon/lib/index.js:1673:17)

I am not sure if nodeType is mandatory, so I tried to comment it out (// nodeType = 'IsBallNearDecorator';), but then I get another error:

$ babel-node bt__IsBallNearDecorator.js 
/home/giacomo/DEV/behaviortree--nodejs--Calamari/bt__IsBallNearDecorator.js:33
_behaviortree.BehaviorTree.register('IsBallNearDecorator', new _IsBallNearDecorator.IsBallNearDecorator());
                                                           ^

TypeError: _IsBallNearDecorator.IsBallNearDecorator is not a constructor
    at Object.<anonymous> (/home/giacomo/DEV/behaviortree--nodejs--Calamari/bt__IsBallNearDecorator.js:32:46)
    at Module._compile (internal/modules/cjs/loader.js:654:30)
    at loader (/usr/lib/node_modules/babel-cli/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/usr/lib/node_modules/babel-cli/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (internal/modules/cjs/loader.js:566:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:506:12)
    at Function.Module._load (internal/modules/cjs/loader.js:498:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:695:10)
    at Object.<anonymous> (/usr/lib/node_modules/babel-cli/lib/_babel-node.js:154:22)
    at Module._compile (internal/modules/cjs/loader.js:654:30)

I also tried invoking node instead of babel-node (replacing import with require(..), export default with module.exports, ..), but the result is the same error.

I tried to modify the behaviortree library, but I haven't been able to get webpack compile the source code.

Can anyone help me with this issue?

how to use decorators?

Why can't it work in this way?

const {InvertDecorator} = require('behaviortree');

for every Decorator nodes, shall I rewrite them all as below?

class InvertDecorator extends Decorator {
//rewrite the codes.
}

Missing `start` call ?

Hello @Calamari, thanks for making the lib !

I have a question that, why no 222 start log before the first 222 RUNNING ?

The logs:

*** 000 start
000 SUCCESS
*** 000 end
*** 111 start
111 RUNNING
-----------------------------
111 RUNNING
-----------------------------
111 RUNNING
-----------------------------
111 SUCCESS
*** 111 end
222 RUNNING
-----------------------------
222 RUNNING
-----------------------------
222 RUNNING
-----------------------------
222 SUCCESS
*** 222 end
-----------------------------
*** 000 start
000 SUCCESS
*** 000 end
*** 111 start
111 SUCCESS
*** 111 end
*** 222 start
222 SUCCESS
*** 222 end
-----------------------------
*** 000 start
000 SUCCESS
*** 000 end
*** 111 start
111 SUCCESS
*** 111 end
*** 222 start
222 SUCCESS
*** 222 end
-----------------------------
*** 000 start
000 SUCCESS
*** 000 end
*** 111 start
111 SUCCESS
*** 111 end
*** 222 start
222 SUCCESS
*** 222 end
-----------------------------

The codes:

import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE, RUNNING } from 'behaviortree'

BehaviorTree.register('bark', new Task({
  start: function (dog) {
    console.log('*** 000 start')
  },
  end: function (dog) {
    console.log('*** 000 end')
  },
  run: function (dog) {
    console.log('000 SUCCESS')
    return SUCCESS
  }
}))

let testCount1 = 0;
let testCount2 = 0;
const tree = new Sequence({
  nodes: [
    'bark',
    new Task({
      start: function (dog) {
        console.log('*** 111 start')
      },
      end: function (dog) {
        console.log('*** 111 end')
      },
      run: function (dog) {
        testCount1++;
        if (testCount1 <= 3) {
          console.log('111 RUNNING')
          return RUNNING
        } else {
          console.log('111 SUCCESS')
          return SUCCESS
        }
      }
    }),
    new Task({
      start: function (dog) {
        console.log('*** 222 start')
      },
      end: function (dog) {
        console.log('*** 222 end')
      },
      run: function (dog) {
        testCount2++;
        if (testCount2 <= 3) {
          console.log('222 RUNNING')
          return RUNNING
        } else {
          console.log('222 SUCCESS')
          return SUCCESS
        }
      }
    })
  ]
})

class Dog {
}

const dog = new Dog()

const bTree = new BehaviorTree({
  tree: tree,
  blackboard: dog
})

let count = 0;
function step() {
  bTree.step()
  console.log('-----------------------------')
  count++;
  if (count < 10) setTimeout(step, 1000);
}
step();

LoopDecorator ignores RUNNING result

Hello,

I'm using the LoopDecorator as a way to dynamically add nodes to my trees. I queue up some actions by adding them to an array in the blackboard and then use a looped sequence to shift an action off of the queue and process it in a task. This continues until the action queue is empty at which point a FAILURE result is returned, breaking out of the Loop and continuing with the rest of the tree.

This pattern has been working fine so far but now i have some cases where I need to interrupt the Loop and then have it continue where it left off on a later step. Unfortunately, if i return a RUNNING status in one of the loop iterations it just continues to the next one.

Does it make sense to extend the LoopDecorator with a custom one that allows it to be stopped and resumed or is there perhaps a better way to handle the scenario I described above?

Thanks for a great library by the way! I'm currently using the typescript version (3.0.0-beta.1).

QA: alternative names for node

I've found there're alternative names for node, here's a summary

a composite node include sequence, selector and parallel

Composites
    sequence = and
    selector = priority = fallack = or

action = task
condition = decorator = if
services = ?

I think those are common alter names for node

and do we have a condition node?

Testing: Add ways to easier test trees

Currently it's hard to test own trees. We need a way to make it easier.

Maybe via adding some introspection possibilities to inject loggers into the tree?

Programmatically pushing nodes to branchnodes requires update of numNodes property

I discovered that pushing nodes in a blueprint in order to programmatically populate blueprints/branches will not properly execute unless you increment the numNodes property of the object.
Example code below, without mainSequence.numNodes++ I couldn't get steps working, they didn't see the nodes at all.
Is this an undefined behavior or is there a function I should be using instead?

const { BehaviorTree, Sequence, Task, Selector, SUCCESS, FAILURE, RUNNING, decorators: {InvertDecorator}, Decorator} = require('behaviortree')
const NanoTimer = require('nanotimer');
const timer = new NanoTimer();

function NoteTask(note = 60, velocity = 100) {
    return new Task({
        start: function (blackboard) {
            blackboard.isStarted = true;
            console.log("NOTE " + this.note)
        },
        end: function (blackboard) {
            blackboard.isStarted = false;
            blackboard.deltaTime = 0;
        },
        run: function (blackboard) {
            return SUCCESS
        },
        note: note,
        velocity: velocity,
    })
}

let deltaTime = {
    deltaTime: 0
};


const mainSequence = new Sequence({
    nodes: []
})


if(mainSequence.blueprint.nodes == null){
   // mainSequence.blueprint.nodes = []
}
mainSequence.blueprint.nodes.push(NoteTask(60))
mainSequence.numNodes++
mainSequence.blueprint.nodes.push(NoteTask(69))
mainSequence.numNodes++
console.log(mainSequence)

let bTree = new BehaviorTree({
    tree: mainSequence,
    blackboard: deltaTime
})

//console.log(mainSequence)

timer.setTimeout(loop, '', '1000m')

function loop() {
    bTree.step()
    timer.setTimeout(loop,'',  "400m");
}

loop();


Node.js constructor: use original user parameter instead of destructuring

Hi,

Is there any chance for the following change?

(

constructor({ run = NOOP, start = NOOP, end = NOOP, ...props }) {
)

to something like

  constructor(setup) {
    setup.run = setup.run || NOOP;
    setup.start = setup.start || NOOP;
    setup.end = setup.end || NOOP;  
    this.blueprint = setup;
  }

This would have the advantage that User can pass in a class instance while preserving the class context. E.g.

class MyAction {
    
    myMethod() {/*...*/}

    run() {
         this.myMethod() //currently throws "myMethod is not a function"
    }
}

BehaviorTree.register("myAction", new Task( new MyAction() ));

Unneeded dependencies

Hello!

First thing first, love your work! I love the simplicity of this lib, minimal and easy to use, bravo!

One thing I noticed is some of the required dependencies are mostly dev stuff, but definitely weight big on production builds

    "core-js": "^3.11.1",
    "eslint-config-prettier": "^8.2.0",
    "eslint-plugin-prettier": "^3.4.0",
    "prettier": "^2.2.1"

Prettier on its own is fatso and definitely not a need in production

The only one that fits here is core-js, but I'm curious what are the polyfills you are using? Bringing them individually would also bring the bundle size seriously

I might be wrong, but Im sure a lot of the apps that would use this lib would be games, which are all performance bound - removing unnecessary deps would make this lib more attractive (or not needed to be forked)

Keen to hear your thoughts!

How to pass parameters to behaviortree tasks

I am asking for an explanation about how to pass parameters to the
behaviortree tasks.

In the README.md file I see that, in a JSON example, the
"cooldown" decorator presents a key called "cooldown" set to 1. I have
tested this decorator and I have seen that his logic is to run the
child node and then wait a number of seconds equal to the "cooldown"
key (I tried with very different values).

I looked at the behaviortree source code and saw that the setConfig function
is overridden in CooldownDecorator.js, like this:

setConfig ({ cooldown = 5 }) {
  this.config = {
    cooldown
    }
  }

I think it is this function (which is invoked in the Decorator
constructor - from which CooldownDecorator.js inherits) that reads the
value of the "cooldown" key in the sample JSON and sets it in the
this.config object of CoolDownDecorator. Is it correct?

In this way I seem to have understood how to pass parameters to
decorators, but I do not understand how to apply this functionality to
tasks, since I see that the setConfig function is not present in
Task.js nor in Node.js (from which Task.js inherits).

An example of what I would like to do is the following:

  1. create a "say" task in the BT register that triggers a speech
    synthesizer saying the string "utterance" (which by default is
    "Hello!")

  2. create a BT defined by a JSON file that uses the "say" task and
    specify a parameter with "utterance" as key and "Have a nice day!"
    as value (different from the default).

Can you help me with this?

Need node break function

Need node break function

Expect Logic

  1. Cansee is used to find the target
  2. Cansee find the target then Seek track the target
  3. When a Walk is executed, a target is found and the Walk is break to execute the Cansee

Actual Situation

  1. There is no way to break the Walk and then execute the Cansee during the Walk execution
/ * 
          Root
           | 
        Selector
        /      \
   Sequence    Walk (If Cansee finds the target, the Walk is break)
    /   \      |
Cansee Seek    |
(need loop execution) 
  ^ - - - - - -|
  
*/

Export tree to JSON

Is there an easy way?
Is it enough to JSON.Stringify my tree and feed it to an importer.parse?

(p.s. I'm sorry I'm uzi-ing all these questions! Know it's for the best as I'm prototyping the GUI I told you before! Thanks in advance for your help :) )

Calling step() when not done last task

Hello, This is a really awesome project you have here there is just one point that I want to question. It may not be a bug it may just be that I should use it differently.

When I call step() (which I do during my game loop) if the last step has not been completed it outputs "the BehaviorTree "name" did call step but one Task did not finish on last call of step."

In the source it shows that it outputs that message then just continues. Shouldn't step return and do nothing until success or fail has been called? I do not feel like slowing down my game loop just to make sure my behaviour has finished before I call step again.

Maybe I should be using it in a different way?

The first task of a nested sequence does not seem to inoke start

I have three sequences: A, B and C
A has two tasks
B has two tasks
C is A,B

When I run C in my behavior tree, the first task of B doesn't call start (but calls Run), the rest of the tasks work fine.
It even happens if C is A,A or B,B

I read that the latest release 2.0.4 fixed this issue but I am still encountering it

it doesn't work in browser

i tried import the module into browser by webpack4 as below
import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'

once i want to check by console.log(BehaviorTree), console shows undefined.

when i tried this way
import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from '../node_modules/behaviortree/dist/index.node'
it can work.

but when i tried
import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from '../node_modules/behaviortree/dist/index'
it doesn't work.

it seems that this package can only be used in Node, but not applied for web.

Help understanding JSON trees

Hey @Calamari and everyone!

I am making a little graphic editor to make trees for BehaviorTree.js but I think I need to understand better how the JSON files work. Looking at the example in README

{ "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" } ] }

My questions are:

  1. I see "nodes" and "node" fields. I understand "nodes" are for branching nodes, but what about "node"? Are they the wrapped node inside a decorator?

  2. Are names actually needed?

  3. Does everything additional (e.g.) go in the blackboard? Or is that "cooldown":1 field a parameter passed to the node?

Thank you for your help, can't wait to show you guys the editor when I'm finished

How to Import JSON BT files into behaviortree

Import JSON BT files into behaviortree

1. How to use BehaviorTreeImporter?

I am facing problems on how to use BehaviorTreeImporter for importing a JSON file.

I am trying to compile a nodejs source like this:

import { BehaviorTreeImporter, BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'

const treeFromJSON = BehaviorTreeImporter;

I have a BT in bt_sample.json (taken from the example I found in the README.md).

I naively tried the following commands, without success:

treeFromJSON.parse("bt_sample.json")

treeFromJSON("bt_sample.json")

How can I load the JSON file tree structure into behaviortree?

2. BT JSON documentation

Is there any documentation on how to structure data in a JSON BT file?

Thank you very much for your attention.

"loop until success" decorator

@Calamari I was looking at your basic inverter decorator which provides a good template for building from, but it's not clear to me how I'd implement this. My assumption is that "loop until success" would only allow one child node, and there would need to be some internal logic in the decorator to handle re-running until success is encountered.

I know this is a fairly specific node type but this might help people like me figure out how to build custom decorators more easily. thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.