Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Parallel nodes #60

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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',
],
};
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.jhw-cache
complexity
node_modules
dist
lib
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
}
124 changes: 77 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,18 @@ 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:

- Tests and implementation der Parallel Typen
- Benchmarking
- Adden der Parallel Arten zur README

x
x
x
x
x

x
x
Loading