Unity game development–Behavior tree (Part 2)

1. Behavior tree node code example

The node implementation code connecting Part1 is as follows (simple framework)

State type enumeration

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum E_NodeState
{<!-- -->
   Success,//success
   Failed,//Failed
   Running//In progress
}

Node base class

namespace BT
{<!-- -->
    /// <summary>
    /// Behavior node base class
    /// </summary>
    public abstract class BTBaseNode
    {<!-- -->
        protected List<BTBaseNode> childList = new List<BTBaseNode>();

        public BTBaseNode parent;

        //The sequence number of the child node currently executing the logic
        protected int NowIndex = 0;

        /// <summary>
        /// Add child node method
        /// </summary>
        /// <param name="nodes"></param>
        public virtual void AddChild(params BTBaseNode[] nodes)
        {<!-- -->
            for (int i = 0; i < nodes.Length; i + + )
            {<!-- -->
                nodes[i].parent = this;
                childList.Add(nodes[i]);
            }
        }

        public virtual void RemoveChild(int index)
        {<!-- -->

        }

        /// <summary>
        /// Execute node logic (Evaluate)
        /// </summary>
        /// <returns></returns>
        public abstract E_NodeState Excute();
    }
}

ActionNode

namespace BT
{<!-- -->
    /// <summary>
    /// Action node (execute specific logic) This action node pushes the logic to external execution
    /// </summary>
    public class BTActionNode : BTBaseNode
    {<!-- -->
        public Func<E_NodeState> action;
        //Use a delegate to undertake external functions, where the external logic requires a function that returns an E_NodeState value.
        public E_NodeState nodeState;
        public BTActionNode(Func<E_NodeState> action)
        {<!-- -->
            this.action = action;
        }

        public override E_NodeState Excute()
        {<!-- -->
            if (action == null)
            {<!-- -->
                nodeState = E_NodeState.Failed;
                return nodeState;
            }

            switch (action.Invoke())
            {<!-- -->
                case E_NodeState.Failed:
                    nodeState = E_NodeState.Failed;
                    return E_NodeState.Failed;
                case E_NodeState.Running:
                    nodeState = E_NodeState.Running;
                    return E_NodeState.Running;
            }
            nodeState = E_NodeState.Success;
            return nodeState;
        }
    }
}

ConditionNode

namespaceBT
{<!-- -->
    /// <summary>
    /// Conditional node finally makes a conditional judgment
    /// </summary>
    public class BTConditionNode : BTBaseNode
    {<!-- -->

        public Func<bool> action;

        public BTConditionNode(Func<bool> action)
        {<!-- -->
            this.action = action;
        }

        public override E_NodeState Excute()
        {<!-- -->
            if (action == null)
            {<!-- -->
                return E_NodeState.Failed;
            }

            return action.Invoke() ? E_NodeState.Success : E_NodeState.Failed;
        }

    }
}

SelectorNode

namespace BT
{<!-- -->
    /// <summary>
    /// Select node. Select one of the child nodes to execute, usually the first successful child node. If no child node succeeds,
    /// </summary>
    public class BTSelectNode : BTBaseNode
    {<!-- -->
        public BTSelectNode() : base() {<!-- --> }

        public override E_NodeState Excute()
        {<!-- -->
            BTBaseNode childNode;
            if (childList.Count != 0)
            {<!-- -->
                childNode = childList[NowIndex];
                switch (childNode.Excute())//Return the result of executing the child node
                {<!-- -->
                    case E_NodeState.Success:
                        NowIndex = 0;
                        return E_NodeState.Success;
                    case E_NodeState.Failed:
                         + + NowIndex;
                        if (NowIndex == childList.Count)
                        {<!-- -->
                            NowIndex = 0;
                            return E_NodeState.Failed;
                        }
                        break;
                    case E_NodeState.Running:
                        return E_NodeState.Running;
                }
            }
            return E_NodeState.Failed;
        }
    }
}

SequenceNode

namespace BT
{<!-- -->
    /// <summary>
    /// Sequential node executes its child nodes in order until one child node returns failure
    /// </summary>
    public class BTSequenceNode : BTBaseNode
    {<!-- -->
        public BTSequenceNode() : base() {<!-- --> }
        public override E_NodeState Excute()
        {<!-- -->
            BTBaseNode childNode;
            if (childList.Count != 0)
            {<!-- -->
                childNode = childList[NowIndex];
                switch (childNode.Excute())
                {<!-- -->
                    case E_NodeState.Success:
                         + + NowIndex;
                        if (NowIndex == childList.Count)
                        {<!-- -->
                            NowIndex = 0;
                            return E_NodeState.Success;
                        }
                        break;
                    case E_NodeState.Failed:
                        NowIndex = 0;
                        return E_NodeState.Failed;
                    case E_NodeState.Running:
                        return E_NodeState.Running;
                    default:
                        break;
                }
            }
            return E_NodeState.Failed;
        }
    }
}

Reverse Decorator Node (DecoratorNotNode)

namespace BT
{<!-- -->
    /// <summary>
    /// Reverse modified node
    /// </summary>
    public class BTDecoratorNot : BTBaseNode
    {<!-- -->
        public Func<bool> action;

        public BTDecoratorNot(Func<bool> action)
        {<!-- -->
            this.action = action;
        }

        public override E_NodeState Excute()
        {<!-- -->
            if (action == null)
            {<!-- -->
                return E_NodeState.Failed;
            }

            return action.Invoke() ? E_NodeState.Failed : E_NodeState.Success;
        }
    }
}

Blackboard node

Part 1 did not mention the blackboard node. The blackboard node is used to read and write public data so that information can be shared between behavior tree nodes, such as storing and transmitting status information, or storing the location of enemies, other environment variables, etc.

namespace BT
{<!-- -->
    public class DataBase
    {<!-- -->
        public Dictionary<string, object> _dataContext = new Dictionary<string, object>();


        public void Setdata(string key, object value)
        {<!-- -->
            if (_dataContext.ContainsKey(key))
            {<!-- -->
                _dataContext[key] = value;
            }
            else
            {<!-- -->
                _dataContext.Add(key, value);
            }
        }

        public object GetData(string key)
        {<!-- -->
            if (_dataContext.ContainsKey(key))
            {<!-- -->
                return _dataContext[key];
            }
            Debug.LogWarning("This data cannot be obtained without this data");
            return null;
        }
        public bool ClearData(string key)
        {<!-- -->
            if (_dataContext.ContainsKey(key))
            {<!-- -->
                _dataContext.Remove(key);

                return true;
            }
            Debug.LogWarning("Without this data, it cannot be cleared");
            return false;
        }
    }
}

Other nodes can be deduced in the same way, and everyone’s writing style and logic are different, so I won’t post the code one by one~

1. Build tree structure and example logic

Tree base class

namespace BT
{<!-- -->
    public abstract class BTTree : MonoBehaviour
    {<!-- -->
        protected BTBaseNode _root;

        protected DataBase _database = new DataBase();//Each tree declares a blackboard node

        public virtual void Start()
        {<!-- -->
            _root = SetUpTree();
        }

        private void Update()
        {<!-- -->
            if(_root != null)
            {<!-- -->
                _root.Excute();
            }
        }

        public abstract BTBaseNode SetUpTree();

    }
}

Enemy code

public class Moster : BTTree
{<!-- -->
    public float moveSpeed;
    public float potrolRange;
    public float radius;
    private Vector3 waypoint;
    private Vector3 gardPos;
    private GameObject attackTarget;
    public bool isFound;
    public Vector3 currentPatrolPos;

    public override void Start()
    {<!-- -->
        base.Start();
        gardPos = transform.position;//Set patrol point
        _database.Setdata("Patrol Point", GetwayPoint());//Set the patrol point at the beginning
        currentPatrolPos = (Vector3)_database.GetData("Patrol Point");
    }
    /// <summary>
    /// Build a behavior tree and fill in logic into condition nodes and behavior nodes
    /// </summary>
    /// <returns></returns>
    public override BTBaseNode SetUpTree()
    {<!-- -->
        BTSelectNode bt_Select = new BTSelectNode();
        BTSequenceNode SeqPatrol = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//Need to decorate the node
        BTSequenceNode Seqpursuit = SeqBTInit(FoundPlayer, Pursuit);
        BTSequenceNode SeqBack = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//Need to decorate the node
        BTSequenceNode SeqAtt = SeqBTInit(AttackRange, Attack);
        bt_Select.AddChild(SeqPatrol, Seqpursuit, SeqBack, SeqPatrol);
        return bt_Select;
    }
    //instantiate sequence node
    public BTSequenceNode SeqBTInit(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
    {<!-- -->
        BTSequenceNode seq = new BTSequenceNode();
        
        BTConditionNode bTCondition = new BTConditionNode(actionCondi);

        BTActionNode btActionNode = new BTActionNode(action);
        //Add the condition node and behavior node to the sequence node as child nodes
        seq.AddChild(bTCondition, btActionNode);

        return seq;
    }

    public BTSequenceNode SeqBTInitNot(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
    {<!-- -->
        BTSequenceNode seq = new BTSequenceNode();

        BTDecoratorNot bTCondition = new BTDecoratorNot(actionCondi);

        BTActionNode btActionNode = new BTActionNode(action);

        seq.AddChild(bTCondition, btActionNode);

        return seq;
    }
    //Pursuit
    public E_NodeState Pursuit()
    {<!-- -->
        if (FoundPlayer())//In the logic function, interrupt the Running state of the behavior node
        {<!-- -->
            GameObject attTarget = _database.GetData("Attack Object") as GameObject;
            if (_database.GetData("Attack Object") != null)
            {<!-- -->
                MoveToTarget(attTarget.transform.position);
                return E_NodeState.Running;
            }
        }

        return E_NodeState.Failed;
    }

    bool FoundPlayer()//Find the enemy
    {<!-- -->
        Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
        foreach (var target in colliders)
        {<!-- -->
            if (target.CompareTag("Player"))
            {<!-- -->
                attackTarget = target.gameObject;
                _database.Setdata("attack target", attackTarget);
                isFound = true;
                return isFound;
            }
        }
        if (_database.GetData("Attack Object") != null)
        {<!-- -->
            _database.ClearData("Attack object");
        }
        isFound = false;
        return isFound;
    }

    Vector3 GetwayPoint()//Random patrol point
    {<!-- -->
        float RandomX = Random.Range(-potrolRange, potrolRange);
        float RandomZ = Random.Range(-potrolRange, potrolRange);
        Vector3 RandomPoint = new Vector3(gardPos.x + RandomX, transform.position.y, gardPos.z + RandomZ);
        waypoint = RandomPoint;
        _database.Setdata("Patrol point", waypoint);//
        return waypoint;
    }

    public E_NodeState MoveToPatrolPoint()//Move to patrol point
    {<!-- -->
        if (!FoundPlayer())
        {<!-- -->
            if (Vector3.Distance(currentPatrolPos, transform.position) < 2f)
            {<!-- -->
                _database.Setdata("Patrol Point", GetwayPoint());
                currentPatrolPos = (Vector3)_database.GetData("Patrol Point");
                MoveToTarget(currentPatrolPos);
                Debug.Log("Switch patrol point");
                return E_NodeState.Running;
            }
            else
            {<!-- -->
                MoveToTarget(currentPatrolPos);
                return E_NodeState.Running;
            }
        }
        return E_NodeState.Failed;
    }

    public void MoveToTarget(Vector3 targetPos)
    {<!-- -->
        transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);

        Debug.Log("Move method");

    }


    private void OnDrawGizmos()
    {<!-- -->
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(transform.position, radius);
        Gizmos.DrawWireSphere(transform.position, potrolRange);
    }
    //The attack here produces a print hhhh
    public E_NodeState Attack()
    {<!-- -->
        Debug.Log("Attack player");
        return E_NodeState.Success;
    }
    //Determine attack range
    bool AttackRange()
    {<!-- -->
        if ((_database.GetData("Attack Object") as GameObject) != null)
        {<!-- -->
            if (Vector3.Distance((_database.GetData("Attack Object") as GameObject).transform.position, transform.position) < 2f)
            {<!-- -->
                return true;
            }
        }

        return false;
    }
}

The code for player movement will not be posted here. The implementation methods are simple and diverse.