Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
40cffb6
Move all files to TypeSciprt & use esbuild
Calamari Nov 5, 2021
af01fd7
Add types everywhere
Calamari Nov 5, 2021
ba56ce0
Let benchmark file be js
Calamari Nov 5, 2021
c9944cc
Update prettier rules to use semicolon
Calamari Nov 5, 2021
2b26969
Check up tests and finalize typings with them
Calamari Nov 5, 2021
775e0ae
Typescript definitely warrants for a major version
Calamari Nov 5, 2021
0765946
Export types in dist
Calamari Nov 5, 2021
0d03b16
Ignore bin file in eslint
Calamari Nov 5, 2021
84fd85d
Export lib files to lib dir instead of dist
Calamari Nov 5, 2021
1294c2d
Fix specs and build dependencies
Calamari May 23, 2022
c40fd4c
Make BranchNode remember last run
Calamari Jan 5, 2022
42a39d9
Make decorators work with new running response
Calamari Jan 5, 2022
80ea24c
Use new helper method more
Calamari Jan 5, 2022
b56fbc3
Add Parallel node
Calamari Jan 6, 2022
60ac175
Add Parallel* nodes
Calamari Jan 9, 2022
919a215
Adapt Random node to new inner workings
Calamari Jan 9, 2022
c8d4286
Clean up import statements
Calamari Jan 9, 2022
9d33335
Upgrade node packages
Calamari Jan 9, 2022
4feced4
Fix introspector call in BranchNode
Calamari Jan 10, 2022
9ec961e
Remove console.logs
Calamari Jan 10, 2022
4a2fb8b
Add introspector for parallel tasks
Calamari Jan 10, 2022
62094a1
Fix CooldownDecorator for running tasks
Calamari Jan 10, 2022
e4ba1eb
Add new accomplishments to README
Calamari Jan 10, 2022
c0f402c
Small cleanup
Calamari Dec 5, 2022
d4b18e6
3.0.0-beta.0
Calamari Dec 5, 2022
1251d4b
Fix mixup with callback result type
Calamari Jan 21, 2023
87e2919
Try getting typescript publishing to work
Calamari Jan 21, 2023
a4e37b2
Make a class out of the NodeRegistry
Calamari Mar 6, 2026
18baa96
Fix any in Importer
Calamari Mar 6, 2026
b24b714
Remove circular dependency
Calamari Mar 9, 2026
8fccda4
Remove duplication
Calamari Mar 9, 2026
41e9197
Oops, Add missing exports
Calamari Mar 9, 2026
0f97102
Bump to new beta version
Calamari Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .babelrc

This file was deleted.

1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin/
12 changes: 0 additions & 12 deletions .eslintrc

This file was deleted.

11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.jhw-cache
complexity
node_modules
dist
lib
dist
4 changes: 2 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"printWidth": 120,
"printWidth": 140,
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"semi": true,
"singleQuote": true
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.words": ["Cooldown", "introspector", "Klass"]
}
116 changes: 69 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ First, I should mention that it is possible to use this library also in common-j
So instead of

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

just use

```js
const { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } = require('behaviortree')
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](https://github.com/Calamari/BehaviorTree.js-Examples).
Expand All @@ -50,26 +50,26 @@ A task is a simple `Node` (to be precise a leaf node), which takes care of all t
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.

```js
import { Task, SUCCESS } from 'behaviortree'
import { Task, SUCCESS } from 'behaviortree';
const myTask = new Task({
// (optional) this function is called directly before the run method
// is called. It allows you to setup things before starting to run
start: function (blackboard) {
blackboard.isStarted = true
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
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
return SUCCESS;
}
})
});
```

The methods:
Expand All @@ -83,61 +83,61 @@ The methods:
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`.

```js
import { Sequence } from 'behaviortree'
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`.

```js
import { Selector } from 'behaviortree'
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.

```js
import { Random } from 'behaviortree'
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.

```js
import { BehaviorTree } from 'behaviortree'
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.

```js
bTree.step()
bTree.step();
```

### Using a lookup table for your tasks
Expand All @@ -146,80 +146,80 @@ BehaviorTree is coming with a internal registry in which you can register tasks

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

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

```js
import { Selector } from 'behaviortree'
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:

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

### Now putting it all together

And now an example of how all could work together.

```js
import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree'
import { BehaviorTree, Sequence, Task, SUCCESS, FAILURE } from 'behaviortree';
BehaviorTree.register(
'bark',
new Task({
run: function (dog) {
dog.bark()
return SUCCESS
dog.bark();
return SUCCESS;
}
})
)
);

const tree = new Sequence({
nodes: [
'bark',
new Task({
run: function (dog) {
dog.randomlyWalk()
return SUCCESS
dog.randomlyWalk();
return SUCCESS;
}
}),
'bark',
new Task({
run: function (dog) {
if (dog.standBesideATree()) {
dog.liftALeg()
dog.pee()
return SUCCESS
dog.liftALeg();
dog.pee();
return SUCCESS;
} else {
return FAILURE
return FAILURE;
}
}
})
]
})
});

const dog = new Dog(/*...*/) // the nasty details of a dog are omitted
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)
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.
Expand All @@ -231,7 +231,7 @@ Every node can also be a `Decorator`, which wraps a regular (or another decorate
```js
const decoratedSequence = new InvertDecorator({
node: 'awesome sequence doing stuff'
})
});
```

### Creating own Decorators
Expand All @@ -245,9 +245,9 @@ Beware that you cannot simply instantiate the `Decorator` class and pass in the
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:

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

const { AlwaysSucceedDecorator } = decorators
const { AlwaysSucceedDecorator } = decorators;
```

### Importing BehaviorTree defintions from JSON files
Expand Down Expand Up @@ -289,20 +289,20 @@ const {
SUCCESS,
FAILURE,
decorators: { AlwaysSucceedDecorator }
} = require('behaviortree')
} = require('behaviortree');
```

### Introspecting the Tree (debugging the Tree)
### Introspecting the Tree (debugging/visualizing 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.

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

That would result in something like:
Expand All @@ -324,6 +324,17 @@ That would result in something like:
}
```

## Built-in Types of Nodes

It should be easy to add your own type of nodes if there is something missing, but there the most common ones should be in here already. Decorators you will most likely will have to implement those yourself, as well as Tasks, since those contain the meat of your project. To tie those together, here are the build-in nodes you might want to use (in a nutshell):

- **Selector**: A selector node runs all its children nodes in sequence until the one that returns `SUCCESS`.
- **Sequence**: A sequence node runs all its children nodes in sequence until the one that returns `FAILURE`.
- **Parallel**: A parallel node runs all of its children in parallel and returns `RUNNING` until one returns `FAILURE`.
- **ParallelSelector**: A parallel node runs all of its children in parallel and returns `RUNNING` as long as one node is `RUNNING`.
- **ParallelComplete**: A parallel node runs all of its children in parallel and returns the first result that is **not** `RUNNING`.
- **Random**: A random node runs only one of its children nodes chosen randomly.

## 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.
Expand All @@ -339,6 +350,10 @@ yarn test

## Version history

- **3.0.0**
- Rewrite the code in TypeScript
- Add `Parallel`, `ParallelSelector` & `ParallelComplete` nodes
- CooldownDecorator does not start timer when task is still `RUNNING`
- **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
Expand Down Expand Up @@ -367,3 +382,10 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
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.

## TODO:

- Rewrite README to be typescripty
- Tests and implementation of Parallel Typs
- Benchmarking
- Add Parallel to README
Loading