Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/quabug/EntitiesBT
Behavior Tree for Unity ECS (DOTS) framework
https://github.com/quabug/EntitiesBT
behavior-tree unity3d
Last synced: about 1 month ago
JSON representation
Behavior Tree for Unity ECS (DOTS) framework
- Host: GitHub
- URL: https://github.com/quabug/EntitiesBT
- Owner: quabug
- License: mit
- Created: 2019-12-03T18:08:00.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-07-31T09:25:30.000Z (almost 2 years ago)
- Last Synced: 2024-02-08T15:09:40.359Z (4 months ago)
- Topics: behavior-tree, unity3d
- Language: C#
- Homepage:
- Size: 1.39 MB
- Stars: 437
- Watchers: 17
- Forks: 33
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Lists
- awesome-stars - EntitiesBT
- anything_about_game - EntitiesBT - Behavior Tree for Unity ECS (DOTS) framework (GameAI)
- awesome-unity3d - EntitiesBT - Behavior Tree for Unity ECS (DOTS) framework (Open Source Repositories / DOTS)
- awesome-unity3d-study - EntitiesBT
- awesome-opensource-unity - EntitiesBT - Behavior Tree for Unity ECS (DOTS) framework. (Open Source Packages / DOTS)
- awesome - quabug/EntitiesBT - Behavior Tree for Unity ECS (DOTS) framework (C\#)
README
```
_____ _ _ _ _ ______ _____
| ___| | | (_) | (_) | ___ \_ _|
| |__ _ __ | |_ _| |_ _ ___ ___ | |_/ / | |
| __| '_ \| __| | __| |/ _ \/ __|| ___ \ | |
| |__| | | | |_| | |_| | __/\__ \| |_/ / | |
\____/_| |_|\__|_|\__|_|\___||___/\____/ \_/
```
Behavior Tree framework based on and used for Unity Entities (DOTS)## Release Notes
- [1.2.0](https://github.com/quabug/EntitiesBT/pull/160#issue-696822887)## Why another Behavior Tree framework?
Existing BT frameworks do not support Entities out of the box.## Features
- Actions are easy to read/write data from/to entity.
- Use Component of Unity directly instead of own editor window to maximize compatibility of other plugins.
- Data-oriented design, save all nodes data into a continuous data blob ([NodeBlob.cs](Packages/essential/Runtime/Entities/NodeBlob.cs))
- Node has no internal states.
- Separate runtime nodes and editor nodes.
- Easy to extend.
- Also compatible with Unity GameObject without entity.
- Able to serialize the behavior tree into a binary file.
- Flexible thread control: force on the main thread, force on job thread, controlled by the behavior tree.
- Runtime debug window to show the states of nodes.
- Optimized. 0 GC allocated by behavior tree itself after initializing, only 64Byte GC allocated every tick by [`CreateArchetypeChunkArrayAsync`](Packages/essential/Runtime/Entities/VirtualMachineSystem.cs#L59).## Disadvantages
- Incompatible with burst.
- Incompatible with il2cpp.
- Lack of action nodes. (Will add some actions as extensions if I need them)
- Difficult to change tree structure at runtime.
- Node data must be compatible with `Blob` and created by [`BlobBuilder`](https://docs.unity3d.com/Packages/[email protected]/api/Unity.Entities.BlobBuilder.html)## Packages
- [essential](Packages/essential): essential part of entities behavior tree, any extension should depend on this package.
- [codegen](Packages/codegen): auto-generate [entity query accesors](#entityquery) on the methods of nodes.
- [builder.component](Packages/builder.component): build behavior tree data from unity components.
- [builder.graphview](Packages/com.quabug.entities-bt.builder.graphview): build behavior tree data by graph with components.
- [builder.odin](Packages/builder.odin): advanced hierarchy builder based on Odin and its serializer.
- [builder.visual](Packages/builder.visual): build and use behavior tree by graph of DOTS visual scripting (suspended).
- [debug.component-viewer](Packages/debug.component-viewer): show selected entity with behavior tree as components in inspector of unity while running.
- [variable.scriptable-object](Packages/variable.scriptable-object): extension for using scriptable object data as a variable source of behavior tree node.## HowTo
### Installation
Requirement: Unity >= 2020.2 and entities package >= 0.14.0-preview.19Install the packages either by
[UPM](https://docs.unity3d.com/Manual/upm-ui-giturl.html):
modify `Packages/manifest.json` as below:
```
{
"dependencies": {
...
"com.quabug.entities-bt.builder.graphview": "1.4.0",
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.quabug"
]
}
]
}
```or
[OpenUPM](https://openupm.com/docs/getting-started.html#installing-an-upm-package):
```
openupm add com.quabug.entities-bt.builder.graphview
```### Usage
#### GraphView Builder
##### Create behavior tree graph##### Attach graph of behavior tree onto _Entity_
#### Component Builder
##### Create behavior tree##### Attach behavior tree onto _Entity_
#### Serialization
#### Thread control
- Force Run on Main Thread: running on the main thread only, will not use job to tick behavior tree. Safe to call `UnityEngine` method.
- Force Run on Job: running on job threads only, will not use the main thread to tick the behavior tree. Not safe to call `UnityEngine` method.
- Controlled by Behavior Tree: Running on job threads by default, but will switch to main thread once meet decorator of [`RunOnMainThread`](Packages/essential/Runtime/Nodes/Decorators/RunOnMainThreadNode.cs)#### Variant
##### Variant Types
- `BlobVariantReader`: read-only variant
- `BlobVariantWriter`: write-only variant
- `BlobVariantReaderAndWriter`: read-write variant, able to link to the same source.##### Variant Sources
- `LocalVariant`: regular variable, custom value will save into `NodeData`.- `ComponentVariant`: fetch data from `Component` on `Entity`
- _Component Value Name_: which value should be accessed from the component
- _Copy To Local Node_: Will read component data into a local node and never write back into component data. (Force `ReadOnly` access)- `NodeVariant`: fetch data from the blob of another node
- _Node Object_: another node should be accessed by this variable, and must be in the same behavior tree.
- _Value Field Name_: the name of the data field in another node.
- _Access Runtime Data_:
- false: will copy data to local blob node while building, value change of _Node Object_ won't affect variable once build.
- true: will access data field of _Node Object_ at runtime, something like reference value of _Node Object_.- `ScriptableObjectVariant`
- _Scriptable Object_: target SO.
- _Scriptable Object Value_: target field.##### Code Example
``` c#
[BehaviorNode("867BFC14-4293-4D4E-B3F0-280AD4BAA403")]
public struct VariantNode : INodeData
{
public BlobVariantReader IntVariant;
public BlobVariantReaderAndWriter FloatVariant;public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard blackboard)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
var intVariant = IntVariant.Read(index, ref blob, ref blackboard); // get variable value
var floatVariant = FloatVariant.Read(index, ref blob, ref blackboard);
FloatVariant.Write(index, ref blob, ref blackboard, floatVariant + 1);
return NodeState.Success;
}public void Reset(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{}
}
```#### Multiple Trees
Adding multiple `BehaviorTreeRoot` onto a single entity gameobject will create numerous behavior trees to control this single entity.
Behavior tree sorted by `Order` of `BehaviorTreeRoot`.### Debug
### Custom behavior node
#### Action
``` c#
// most important part of node, actual logic on runtime.
[Serializable] // for debug view only
[BehaviorNode("F5C2EE7E-690A-4B5C-9489-FB362C949192")] // must add this attribute to indicate a class is a `BehaviorNode`
public struct EntityMoveNode : INodeData
{
public float3 Velocity; // node data saved in `INodeBlob`
public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{ // access and modify node data
ref var translation = ref bb.GetDataRef(); // get blackboard data by ref (read/write)
var deltaTime = bb.GetData(); // get blackboard data by value (readonly)
translation.Value += Velocity * deltaTime.Value;
return NodeState.Running;
}public void Reset(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{}
}// debug view (optional)
public class EntityMoveDebugView : BTDebugView {}
```#### Decorator
``` c#
// runtime behavior
[Serializable] // for debug view only
[BehaviorNode("A13666BD-48E3-414A-BD13-5C696F2EA87E", BehaviorNodeType.Decorate/*decorator must explicit declared*/)]
public struct RepeatForeverNode : INodeData
{
public NodeState BreakStates;
public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard blackboard)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
// short-cut to tick first only children
var childState = blob.TickChildrenReturnFirstOrDefault(index, blackboard);
if (childState == 0) // 0 means no child was ticked
// tick an already completed `Sequence` or `Selector` will return 0
{
blob.ResetChildren(index, blackboard);
childState = blob.TickChildrenReturnFirstOrDefault(index, blackboard);
}
if (BreakStates.HasFlag(childState)) return childState;
return NodeState.Running;
}public void Reset(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{}
}// debug view (optional)
public class BTDebugRepeatForever : BTDebugView {}
```#### Composite
``` c#
// runtime behavior
[StructLayout(LayoutKind.Explicit)] // sizeof(SelectorNode) == 0
[BehaviorNode("BD4C1D8F-BA8E-4D74-9039-7D1E6010B058", BehaviorNodeType.Composite/*composite must explicit declared*/)]
public struct SelectorNode : INodeData
{
public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard blackboard)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
// tick children and break if the child state is running or success.
return blob.TickChildrenReturnLastOrDefault(index, blackboard, breakCheck: state => state.IsRunningOrSuccess());
}public void Reset(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{}
}// avoid debugging view since there's nothing that needs to debug for `Selector`
```#### `EntityQuery`
The behavior tree needs some extra information for generating `EntityQuery`.``` c#
public struct SomeNode : INodeData
{
// read-only access
BlobVariantReader IntVariable;
// read-write access (there's no write-only access)
BlobVariantWriter FloatVariable;
// read-write access
BlobVariantReaderAndWriter FloatVariable;// leave method attribute to be empty and will generate right access of this method
public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard blackboard)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
// generate `[ReadOnly(typeof(ReadOnlyComponent)]` on `Tick` method
bb.GetData();
// generate `[ReadWrite(typeof(ReadWriteComponent)]` on `Tick` method
bb.GetDataRef();
return NodeState.Success;
}// or manually declare right access types for this method
[EntitiesBT.Core.ReadWrite(typeof(ReadWriteComponentData))]
[EntitiesBT.Core.ReadOnly(typeof(ReadOnlyComponentData))]
public void Reset(int index, ref TNodeBlob blob, ref TBlackboard bb)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
// generate `[ReadOnly(typeof(ReadOnlyComponent)]` on `Reset` method
bb.GetData();
// generate `[ReadWrite(typeof(ReadWriteComponent)]` on `Reset` method
bb.GetDataRef();
// ...
}
}
```make sure to mark the outside method call with the proper access attributes to generate the appropriate access type on `Tick` or `Reset` method of the node
```c#
public static class Extension
{
[ReadOnly(typeof(FooComponent)), ReadWrite(typeof(BarComponent))]
public static void Call<[ReadWrite] T, [ReadOnly] U>([ReadOnly] Type type) { /* ... */ }
}public struct SomeNode : INodeData
{
// leave method attribute to be empty to generate automatically
public NodeState Tick(int index, ref TNodeBlob blob, ref TBlackboard blackboard)
where TNodeBlob : struct, INodeBlob
where TBlackboard : struct, IBlackboard
{
// the following call will generate access attributes on `Tick` as below:
// [ReadOnly(typeof(FooComponent))]
// [ReadWrite(typeof(BarComponent))]
// [ReadWrite(typeof(int))]
// [ReadOnly(typeof(float))]
// [ReadOnly(typeof(long))]
Extension.Call(typeof(long));
return NodeState.Success;
}
}
```#### Advanced: customize debug view
- Behavior Node example: [PrioritySelectorNode.cs](Packages/essential/Runtime/Nodes/Composites/PrioritySelectorNode.cs)
- Debug View example: [BTDebugPrioritySelector.cs](Packages/debug.component-viewer/Runtime/BTDebugPrioritySelector.cs)#### Advanced: access other node data
`NodeBlob` stores all the behavioral tree's internal data, which can be accessed from any node.
To access specific node data, just store its index and access it by `INodeData.GetNodeData(index)`.
- Behavior Node example: [ModifyPriorityNode.cs](Packages/essential/Runtime/Nodes/Actions/ModifyPriorityNode.cs)
- Editor/Builder example: [BTModifyPriority.cs](Packages/builder.component/Runtime/Components/BTModifyPriority.cs)#### Advanced: behavior tree component
``` c#
[BehaviorTreeComponent] // mark a component data as `BehaviorTreeComponent`
public struct BehaviorTreeTickDeltaTime : IComponentData
{
public float Value;
}[UpdateBefore(typeof(VirtualMachineSystem))]
public class BehaviorTreeDeltaTimeSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref BehaviorTreeTickDeltaTime deltaTime) => deltaTime.Value = Time.DeltaTime);
}
}
```
The components of behavior will automatically add to `Entity` on the stage of converting `GameObject` to `Entity`, if `AutoAddBehaviorTreeComponents` is enabled.#### Advanced: virtual node builder
A single builder node can produce multiple behavior nodes while building.
``` C#
public class BTSequence : BTNode
{
[Tooltip("Enable this will re-evaluate node state from the first child until running node instead of skip to the running node directly.")]
[SerializeField] private bool _recursiveResetStatesBeforeTick;public override INodeDataBuilder Self => _recursiveResetStatesBeforeTick
// add `RecursiveResetStateNode` as the parent of `this` node
? new BTVirtualDecorator(this)
: base.Self
;
}
```## Data Structure
``` c#
public struct NodeBlob
{
// default data (serializable data)
public BlobArray Types; // type id of behavior node, generated from `Guid` of `BehaviorNodeAttribute`
public BlobArray EndIndices; // range of node branch must be in [nodeIndex, nodeEndIndex)
public BlobArray Offsets; // data offset of `DefaultDataBlob` of this node
public BlobArray DefaultDataBlob; // nodes data
// runtime only data (only exist on runtime)
public BlobArray States; // nodes states
// initialize from `DefaultDataBlob`
public BlobArray RuntimeDataBlob; // same as `DefaultNodeData` but only available at runtime and will reset to `DefaultNodeData` once reset.
}
```