Home

Awesome

_____      _   _ _   _           ______ _____ 
|  ___|    | | (_) | (_)          | ___ \_   _|
| |__ _ __ | |_ _| |_ _  ___  ___ | |_/ / | |  
|  __| '_ \| __| | __| |/ _ \/ __|| ___ \ | |  
| |__| | | | |_| | |_| |  __/\__ \| |_/ / | |  
\____/_| |_|\__|_|\__|_|\___||___/\____/  \_/  
                                               

Behavior Tree framework based on and used for Unity Entities (DOTS)

Release Notes

Why another Behavior Tree framework?

Existing BT frameworks do not support Entities out of the box.

Features

Disadvantages

Packages

HowTo

Installation

Requirement: Unity >= 2020.2 and entities package >= 0.14.0-preview.19

Install the packages either by

UPM: 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:

openupm add com.quabug.entities-bt.builder.graphview

Usage

GraphView Builder

Create behavior tree graph
<img width="600" alt="create graph" src="https://user-images.githubusercontent.com/683655/158053015-766eeff2-3263-4d3a-b51e-3eebf47c1a46.gif" />
Attach graph of behavior tree onto Entity
<img width="600" alt="attach graph" src="https://user-images.githubusercontent.com/683655/158052727-8a9376fc-c68b-4af1-bdaa-71473938c7aa.gif" />

Component Builder

Create behavior tree
<img width="600" alt="create" src="https://user-images.githubusercontent.com/683655/158053081-c17b41a0-ffae-48ee-955f-be92cdbf277d.gif" />
Attach behavior tree onto Entity
<img width="600" alt="attach" src="https://user-images.githubusercontent.com/683655/158053177-b7ee1081-4d81-4732-a4e8-f13f718ef58f.gif" />

Serialization

<img width="600" alt="save-to-file" src="https://user-images.githubusercontent.com/683655/72407209-b7b77900-3799-11ea-9de3-0703b1936f63.gif" />

Thread control

<img width="400" alt="thread-control" src="https://user-images.githubusercontent.com/683655/72407274-ee8d8f00-3799-11ea-9847-76ad6fdc5a37.png" />

Variant

Variant Types
Variant Sources
Code Example
    [BehaviorNode("867BFC14-4293-4D4E-B3F0-280AD4BAA403")]
    public struct VariantNode : INodeData
    {
        public BlobVariantReader<int> IntVariant;
        public BlobVariantReaderAndWriter<float> FloatVariant;

        public NodeState Tick<TNodeBlob, TBlackboard>(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<TNodeBlob, TBlackboard>(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.

<img width="400" alt="" src="https://user-images.githubusercontent.com/683655/82422698-5db32100-9ab5-11ea-8bc6-eb3c67ac7676.png">

Debug

<img width="600" alt="debug" src="https://user-images.githubusercontent.com/683655/72407368-517f2600-379a-11ea-8aa9-c72754abce9f.gif" />

Custom behavior node

Action

// 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<TNodeBlob, TBlackboard>(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<Translation>(); // get blackboard data by ref (read/write)
        var deltaTime = bb.GetData<BehaviorTreeTickDeltaTime>(); // get blackboard data by value (readonly)
        translation.Value += Velocity * deltaTime.Value;
        return NodeState.Running;
    }

    public void Reset<TNodeBlob, TBlackboard>(int index, ref TNodeBlob blob, ref TBlackboard bb)
        where TNodeBlob : struct, INodeBlob
        where TBlackboard : struct, IBlackboard
    {}
}

// debug view (optional)
public class EntityMoveDebugView : BTDebugView<EntityMoveNode> {}

Decorator

// 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<TNodeBlob, TBlackboard>(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<TNodeBlob, TBlackboard>(int index, ref TNodeBlob blob, ref TBlackboard bb)
        where TNodeBlob : struct, INodeBlob
        where TBlackboard : struct, IBlackboard
    {}
}

// debug view (optional)
public class BTDebugRepeatForever : BTDebugView<RepeatForeverNode> {}

Composite

// 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<TNodeBlob, TBlackboard>(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<TNodeBlob, TBlackboard>(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.

public struct SomeNode : INodeData
{
    // read-only access
    BlobVariantReader<int> IntVariable;
    
    // read-write access (there's no write-only access)
    BlobVariantWriter<float> FloatVariable;
    
    // read-write access
    BlobVariantReaderAndWriter<double> FloatVariable;

    // leave method attribute to be empty and will generate right access of this method
    public NodeState Tick<TNodeBlob, TBlackboard>(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<ReadOnlyComponent>();
        
        // generate `[ReadWrite(typeof(ReadWriteComponent)]` on `Tick` method
        bb.GetDataRef<ReadWriteComponent>();
        
        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<TNodeBlob, TBlackboard>(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<ReadOnlyComponent>();
        
        // generate `[ReadWrite(typeof(ReadWriteComponent)]` on `Reset` method
        bb.GetDataRef<ReadWriteComponent>();
        
        // ...
    }
}

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


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<TNodeBlob, TBlackboard>(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<int, float>(typeof(long));
        return NodeState.Success;
    }
}

Advanced: customize debug view

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<T>(index).

Advanced: behavior tree component

[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.

<img width="600" alt="" src="https://user-images.githubusercontent.com/683655/72411453-d7549e80-37a5-11ea-925a-b3949180dd16.png" />

Advanced: virtual node builder

A single builder node can produce multiple behavior nodes while building.

public class BTSequence : BTNode<SequenceNode>
{
    [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<RecursiveResetStateNode>(this)
        : base.Self
    ;
}

Data Structure

public struct NodeBlob
{
    // default data (serializable data)
    public BlobArray<int> Types; // type id of behavior node, generated from `Guid` of `BehaviorNodeAttribute`
    public BlobArray<int> EndIndices; // range of node branch must be in [nodeIndex, nodeEndIndex)
    public BlobArray<int> Offsets; // data offset of `DefaultDataBlob` of this node
    public BlobArray<byte> DefaultDataBlob; // nodes data
    
    // runtime only data (only exist on runtime)
    public BlobArray<NodeState> States; // nodes states
    // initialize from `DefaultDataBlob`
    public BlobArray<byte> RuntimeDataBlob; // same as `DefaultNodeData` but only available at runtime and will reset to `DefaultNodeData` once reset.
}
<img width="600" alt="data-structure" src="https://user-images.githubusercontent.com/683655/72414832-1edf2880-37ae-11ea-8ef1-146e99d30727.png" />