#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Opsive.BehaviorDesigner.Editor")] namespace Opsive.BehaviorDesigner.Runtime { using Opsive.BehaviorDesigner.Runtime.Components; using Opsive.BehaviorDesigner.Runtime.Groups; using Opsive.BehaviorDesigner.Runtime.Systems; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks.Events; using Opsive.BehaviorDesigner.Runtime.Utility; using Opsive.GraphDesigner.Runtime; using Opsive.GraphDesigner.Runtime.Variables; using Unity.Entities; using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Reflection; /// /// Container for managing behavior tree logic. /// public class BehaviorTree : MonoBehaviour, IGraph, IGraphComponent, ISharedVariableContainer { [Tooltip("The name of the behavior tree.")] [SerializeField] [Delayed] private string m_GraphName = "Behavior Tree"; [Tooltip("A user specified ID for the graph.")] [SerializeField] [Delayed] private int m_Index; [Tooltip("The graph data.")] [SerializeField] private BehaviorTreeData m_Data = new BehaviorTreeData(); [Tooltip("Should the behavior tree start when the component enabled?")] [SerializeField] private bool m_StartWhenEnabled = true; [Tooltip("Should the behavior tree pause when the tree is disabled?")] [SerializeField] private bool m_PauseWhenDisabled; [Tooltip("Specifies when the behavior tree should be updated.")] [SerializeField] private UpdateMode m_UpdateMode; [Tooltip("Specifies how many tasks should be updated during a single tick.")] [SerializeField] private EvaluationType m_EvaluationType; [Tooltip("The maximum number of tasks that can run if the evaluation type is set to EvaluationType.Count.")] [SerializeField] [Range(1, ushort.MaxValue)] private int m_MaxEvaluationCount = 1; [Tooltip("A reference to the subtree.")] [SerializeField] private Subtree m_Subtree; private GameObject m_GameObject; private World m_World; private Entity m_Entity; private Dictionary m_NodeIndexByRuntimeIndex = new Dictionary(); private static Dictionary s_BehaviorTreeByEntity = new Dictionary(); private static EventNodeComparer s_EventNodeComparer = new EventNodeComparer(); private bool m_SubtreeOverride; public string Name { get => m_GraphName; set => m_GraphName = value; } public int Index { get => m_Index; set => m_Index = value; } public int UniqueID { get => m_Data.UniqueID; } public UnityEngine.Object Parent { get { if (this == null) { return null; } return Application.isPlaying && m_GameObject != null ? m_GameObject : gameObject; } } private BehaviorTreeData Data { get => !Application.isPlaying && m_Subtree != null ? m_Subtree.Data : m_Data; } public bool StartWhenEnabled { get => m_StartWhenEnabled; set => m_StartWhenEnabled = value; } public bool PauseWhenDisabled { get => m_PauseWhenDisabled; set => m_PauseWhenDisabled = value; } public UpdateMode UpdateMode { get => m_UpdateMode; set => m_UpdateMode = value; } public EvaluationType EvaluationType { get => m_EvaluationType; set => m_EvaluationType = value; } public int MaxEvaluationCount { get => m_MaxEvaluationCount; set => m_MaxEvaluationCount = Mathf.Max(1, Mathf.Min(ushort.MaxValue, value)); } public IGraph Subgraph { get => m_Subtree; set { if (m_Subtree == (Subtree)value) { return; } ClearTree(); m_Subtree = value as Subtree; m_SubtreeOverride = false; InheritSubtree(false); InitializeTree(); if (enabled) { StartBehavior(); } } } public ILogicNode[] LogicNodes { get => Data.LogicNodes; set => Data.LogicNodes = value; } public IEventNode[] EventNodes { get => Data.EventNodes; set => Data.EventNodes = value; } public SharedVariable[] SharedVariables { get => m_Data.SharedVariables; set => m_Data.SharedVariables = value; } public ushort[] DisabledLogicNodes { get => Data.DisabledLogicNodes; set => Data.DisabledLogicNodes = value; } public ushort[] DisabledEventNodes { get => Data.DisabledEventNodes; set => Data.DisabledEventNodes = value; } public SharedVariable.SharingScope VariableScope { get => SharedVariable.SharingScope.Graph; } public World World { get => m_World; set { m_World = value; } } public Entity Entity { get => m_Entity; set { m_Entity = value; } } public bool Baked { get; set; } public static int BehaviorTreeCount { get => s_BehaviorTreeByEntity.Count; } public LogicNodeProperties[] LogicNodeProperties { #if UNITY_EDITOR get => Data.LogicNodeProperties; set => Data.LogicNodeProperties = value; #else get => null; set { } #endif } public NodeProperties[] EventNodeProperties { #if UNITY_EDITOR get => Data.EventNodeProperties; set => Data.EventNodeProperties = value; #else get => null; set { } #endif } public GroupProperties[] GroupProperties { #if UNITY_EDITOR get => Data.GroupProperties; set => Data.GroupProperties = value; #else get => null; set { } #endif } public TaskStatus Status { get { if (!Application.isPlaying || m_World == null || m_Entity == null || m_Data == null || m_Data.LogicNodes == null || Data.EventNodes == null) { return TaskStatus.Inactive; } // Find the Start event node. Start startEventNode = null; for (int i = 0; i < m_Data.EventNodes.Length; ++i) { if (m_Data.EventNodes[i].GetType() == typeof(Start)) { startEventNode = m_Data.EventNodes[i] as Start; } } if (startEventNode == null || startEventNode.ConnectedIndex >= m_Data.LogicNodes.Length) { return TaskStatus.Inactive; } // The runtime index will match with the correct Entity TaskComponent. var runtimeIndex = m_Data.LogicNodes[startEventNode.ConnectedIndex].RuntimeIndex; var taskComponents = m_World.EntityManager.GetBuffer(m_Entity); if (runtimeIndex >= taskComponents.Length) { return TaskStatus.Inactive; } // Retun the status of the task that the Start node is connected to. This is the current tree status. return taskComponents[runtimeIndex].Status; } } // Flow callbacks. public Action OnBehaviorTreeStarted; public Action OnBehaviorTreeStopped; public Action OnBehaviorTreeDestroyed; // Physics callbacks. public Action OnBehaviorTreeCollisionEnter; public Action OnBehaviorTreeCollisionExit; public Action OnBehaviorTreeCollisionEnter2D; public Action OnBehaviorTreeCollisionExit2D; public Action OnBehaviorTreeTriggerEnter; public Action OnBehaviorTreeTriggerExit; public Action OnBehaviorTreeTriggerEnter2D; public Action OnBehaviorTreeTriggerExit2D; public Action OnBehaviorTreeControllerColliderHit; // Event callbacks. public Action OnWillSave; public Action OnDidSave; public Action OnWillLoad; public Action OnDidLoad; // Coroutine support. private Dictionary> m_ActiveTaskCoroutines; /// /// Serializes the behavior tree. /// public void Serialize() { Data.Serialize(); if (m_Subtree != null) { m_Data.SerializeSharedVariables(); } } /// /// Deserialize the behavior tree. /// /// Should the behavior tree be force deserialized? /// True if the tree was deserialized. public bool Deserialize(bool force = false) { if (this == null) { return false; } // Force the deserialization if the tree hasn't been deserialized yet at runtime. if (Application.isPlaying && m_GameObject == null) { force = true; m_GameObject = gameObject; } var deserialized = false; if (m_Subtree != null) { deserialized = InheritSubtree(force); } else if (m_Data != null) { deserialized = m_Data.Deserialize(this, this, force, true, Application.isPlaying); } // Initialize tasks after deserialization. This is only needed at edit time as the tasks are initialized elsewhere at runtime. if (!Application.isPlaying && deserialized && m_Data != null && m_Data.LogicNodes != null) { for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is Task task) { task.Initialize(this, (ushort)i); } } } return deserialized; } /// /// Inherits the subtree tasks and variables. /// /// Should the behavior tree be force deserialized? /// True if the subtree was inherited. private bool InheritSubtree(bool force) { if (m_Subtree == null) { if (Application.isPlaying) { m_Data.EventNodes = null; m_Data.LogicNodes = null; m_Data.SubtreeNodesReferences = null; #if UNITY_EDITOR m_Data.EventNodeProperties = null; m_Data.LogicNodeProperties = null; m_Data.GroupProperties = null; #endif } return false; } // The local behavior tree variables should be used. m_Data.DeserializeSharedVariables(force); if (!m_Subtree.Deserialize(force, Application.isPlaying, false)) { return false; } // Copy the deserialized objects at runtime to ensure each object is unique. if (Application.isPlaying && !m_SubtreeOverride) { m_Data.EventNodes = m_Subtree.Data.EventNodes; m_Data.LogicNodes = m_Subtree.Data.LogicNodes; m_Data.SubtreeNodesReferences = m_Subtree.Data.SubtreeNodesReferences; var originalSharedVariables = m_Data.SharedVariables; m_Data.SharedVariables = m_Subtree.Data.SharedVariables; m_Data.VariableByNameMap = m_Subtree.Data.VariableByNameMap; // The original tree variable value should override the subtree variable value. if (originalSharedVariables != null) { for (int i = 0; i < originalSharedVariables.Length; ++i) { m_Data.OverrideVariableValue(this, originalSharedVariables[i]); } } #if UNITY_EDITOR m_Data.EventNodeProperties = m_Subtree.Data.EventNodeProperties; m_Data.LogicNodeProperties = m_Subtree.Data.LogicNodeProperties; m_Data.GroupProperties = m_Subtree.Data.GroupProperties; #endif m_GameObject = gameObject; m_SubtreeOverride = true; } return true; } /// /// Adds the specified node. /// /// The node that should be added. public void AddNode(ILogicNode node) { Data.AddNode(node); } /// /// Removes the specified node. /// /// The node that should be removed. /// True if the node was removed. public bool RemoveNode(ILogicNode node) { return Data.RemoveNode(node); } /// /// Adds the specified event node. /// /// The event node that should be added. public void AddNode(IEventNode eventNode) { Data.AddNode(eventNode); } /// /// Removes the specified event node. /// /// The event node that should be removed. /// True if the event node was removed. public bool RemoveNode(IEventNode eventNode) { return Data.RemoveNode(eventNode); } /// /// Returns the Node of the specified type. /// /// The type of Node that should be retrieved. /// The Node of the specified type (can be null). public ILogicNode GetNode(Type type) { return Data.GetNode(type); } /// /// Returns the EventNode of the specified type. /// /// The type of EventNode that should be retrieved. /// The EventNode of the specified type (can be null). If the node is found the index will also be returned. public (IEventNode, ushort) GetEventNode(Type type) { return Data.GetEventNode(type); } /// /// The component has been enabled. /// private void OnEnable() { if (m_World != null && m_StartWhenEnabled) { StartBehavior(); } } /// /// The component has started. /// private void Start() { if (m_StartWhenEnabled) { StartBehavior(); } } /// /// Returns the behavior tree component specified by the entity. /// /// The entity that should be retrieved. /// The behavior tree component specified by the ID. public static BehaviorTree GetBehaviorTree(Entity entity) { if (s_BehaviorTreeByEntity.TryGetValue(entity, out var behaviorTree)) { return behaviorTree; } return null; } /// /// Starts the behavior tree. /// /// True if the behavior tree was started. public bool StartBehavior() { var world = m_World == null ? World.DefaultGameObjectInjectionWorld : m_World; var entity = m_Entity == Entity.Null ? world.EntityManager.CreateEntity() : m_Entity; return StartBehavior(world, entity); } /// /// Starts the behavior tree. /// /// The world that should contain the behavior tree. /// The entity that should contain the behavior tree. /// True if the behavior tree was started. public bool StartBehavior(World world, Entity entity) { return StartBehavior(world, entity, typeof(Start)); } /// /// Starts the behavior tree. /// /// The world that contains tthe entity. /// The entity that should contain the behavior tree. /// The type of branch that should be started. /// True if the behavior tree was started. public bool StartBehavior(World world, Entity entity, Type startBranchType) { if (world == null) { return false; } if (s_BehaviorTreeByEntity.ContainsKey(entity)) { // The behavior tree may be paused. if (world.EntityManager.HasComponent(entity) && !world.EntityManager.IsComponentEnabled(entity)) { world.EntityManager.SetComponentEnabled(entity, true); if (OnBehaviorTreeStarted != null) { OnBehaviorTreeStarted(); } // Tasks can implement a pause specific interface. var tasks = Data.LogicNodes; for (int i = 0; i < tasks.Length; ++i) { if (!(tasks[i] is IPausableTask pausableTask)) { continue; } pausableTask.Resume(world, entity); } return true; } // The tree cannot be started multiple times. return false; } s_BehaviorTreeByEntity.Add(entity, this); if (!InitializeTree(world, entity)) { return false; } if (OnBehaviorTreeStarted != null) { OnBehaviorTreeStarted(); } return StartBranch(startBranchType); } /// /// Initializes the tree within DOTS for all of the event tasks. /// /// True if the behavior tree was initialized. internal bool InitializeTree() { var world = m_World == null ? World.DefaultGameObjectInjectionWorld : m_World; var entity = m_Entity == Entity.Null ? world.EntityManager.CreateEntity() : m_Entity; return InitializeTree(world, entity); } /// /// Initializes the tree within DOTS for all of the event tasks. /// /// The world that contains tthe entity. /// The entity that should contain the behavior tree. /// True if the behavior tree was initialized. internal bool InitializeTree(World world, Entity entity) { if (!Deserialize(false)) { enabled = false; return false; } if (m_Data.EventNodes == null || world == null) { return false; } // The tree may be reinitialized with the same world/entity. m_World = world; m_Entity = entity; // The tree may have already been initialized. if (world.EntityManager.HasComponent(entity)) { return true; } // Initialize the branch according to the connected index. This will ensure when the task is referencing other // tasks the index will be correct. var eventNodes = m_Data.EventNodes; HashSet disabledNodes = null; if (m_Data.DisabledEventNodes != null && m_Data.DisabledEventNodes.Length > 0) { disabledNodes = new HashSet(); for (int i = 0; i < m_Data.DisabledEventNodes.Length; ++i) { disabledNodes.Add(eventNodes[m_Data.DisabledEventNodes[i]]); } } #if UNITY_EDITOR var eventNodeProperties = Data.EventNodeProperties; Array.Sort(eventNodes, eventNodeProperties, s_EventNodeComparer); Data.EventNodeProperties = eventNodeProperties; #endif Array.Sort(eventNodes, s_EventNodeComparer); // The disabled event nodes array only stores the index. Update the index with the sorted value. if (disabledNodes != null) { var index = 0; var disabledEventNodes = m_Data.DisabledEventNodes; for (int i = 0; i < eventNodes.Length; ++i) { if (disabledNodes.Contains(eventNodes[i])) { disabledEventNodes[index] = (ushort)i; index++; } } m_Data.DisabledEventNodes = disabledEventNodes; } for (int j = 0; j < eventNodes.Length; ++j) { InitializeBranch(world, entity, eventNodes[j]); } return true; } /// /// Comparer for IEventNode. /// private class EventNodeComparer : IComparer { /// /// Compares the ConnectedIndex of two event nodes. /// /// The first event node /// The second event node. /// The CompareTo value between the two event nodes. public int Compare(IEventNode eventNode1, IEventNode eventNode2) { return eventNode1.ConnectedIndex.CompareTo(eventNode2.ConnectedIndex); } } /// /// Initialize the specified event branch within DOTS. /// /// The world that the entity exists in. /// The entity that the branch should be added to. /// The task that should be setup. private void InitializeBranch(World world, Entity entity, IEventNode eventTask) { if (Data.LogicNodes == null) { return; } // There must be a starting event node. if (eventTask == null || eventTask.ConnectedIndex >= Data.LogicNodes.Length) { return; } if (!world.EntityManager.HasBuffer(entity)) { world.EntityManager.AddBuffer(entity); } DynamicBuffer branchComponents; if (world.EntityManager.HasBuffer(entity)) { branchComponents = world.EntityManager.GetBuffer(entity); } else { branchComponents = world.EntityManager.AddBuffer(entity); } var startBranchIndex = (ushort)branchComponents.Length; branchComponents.Add(new BranchComponent() { ActiveIndex = ushort.MaxValue, NextIndex = ushort.MaxValue }); world.EntityManager.AddComponent(entity); world.EntityManager.AddComponent(entity); world.EntityManager.AddComponentData(entity, new EvaluationComponent() { EvaluationType = m_EvaluationType, MaxEvaluationCount = (ushort)Mathf.Max(1, m_MaxEvaluationCount) }); // A manual update mode will require the user to call the Tick method. if (m_UpdateMode == UpdateMode.Manual) { world.EntityManager.SetComponentEnabled(entity, false); world.EntityManager.SetComponentEnabled(entity, false); } // Get the required parent system groups. The systems are always added to the default world. var traversalTaskSystemGroup = world.GetOrCreateSystemManaged(); var reevaluateTaskSystemGroup = world.GetOrCreateSystemManaged(); var interruptTaskSystemGroup = world.GetOrCreateSystemManaged(); var taskComponents = world.EntityManager.GetBuffer(entity); var taskOffset = (ushort)(eventTask.ConnectedIndex - taskComponents.Length); for (int i = eventTask.ConnectedIndex; i < m_Data.LogicNodes.Length; ++i) { // Don't initialize tasks that aren't connected to the start node. if (i > eventTask.ConnectedIndex && m_Data.LogicNodes[i].ParentIndex == ushort.MaxValue) { break; } taskComponents = world.EntityManager.GetBuffer(entity); // Determine the branch index based off of the parent. If the parent is a parallel node then the node will be part of a new branch. var branchIndex = startBranchIndex; if (m_Data.LogicNodes[i].ParentIndex != ushort.MaxValue) { var parentIndex = m_Data.LogicNodes[i].ParentIndex; if (m_Data.LogicNodes[parentIndex] is IParallelNode) { branchIndex = (ushort)(taskComponents[i - taskOffset - 1].BranchIndex + 1); } else { branchIndex = taskComponents[parentIndex - taskOffset].BranchIndex; } // A new branch component may need to be added to keep track of the active task index for that branch. if (branchIndex > 0) { branchComponents = world.EntityManager.GetBuffer(entity); if (branchIndex >= branchComponents.Length) { branchComponents.Add(new BranchComponent() { ActiveIndex = ushort.MaxValue, NextIndex = ushort.MaxValue }); } } } // The TaskComponents index will be different from the LogicNode index if the tree has a gap of tasks that are not connected. // The RuntimeIndex maps the LogicNode index to the TaskComponent index. var node = m_Data.LogicNodes[i]; node.RuntimeIndex = (ushort)(node.Index - taskOffset); m_Data.LogicNodes[i] = node; if (!m_NodeIndexByRuntimeIndex.ContainsKey(node.RuntimeIndex)) { // The index will already exist if multiple entities use the same MonoBehaviour. m_NodeIndexByRuntimeIndex.Add(node.RuntimeIndex, node.Index); } if (m_Data.LogicNodes[i] is ITaskComponentData taskComponentData) { taskComponentData.AddBufferElement(world, entity); taskComponents = world.EntityManager.GetBuffer(entity); taskComponents.Add(new TaskComponent { Status = TaskStatus.Inactive, Index = node.RuntimeIndex, ParentIndex = AdjustByIndexOffset(m_Data.LogicNodes[i].ParentIndex, taskOffset), SiblingIndex = AdjustByIndexOffset(m_Data.LogicNodes[i].SiblingIndex, taskOffset), BranchIndex = branchIndex, TagComponentType = taskComponentData.Tag, Disabled = !IsNodeEnabled(true, m_Data.LogicNodes[i].ParentIndex) || !IsNodeEnabled(true, i), }); world.EntityManager.AddComponent(entity, taskComponentData.Tag); world.EntityManager.SetComponentEnabled(entity, taskComponentData.Tag, false); traversalTaskSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(taskComponentData.SystemType)); } else if (m_Data.LogicNodes[i] is Task taskObject) { taskObject.AddBufferElement(world, entity, GetHashCode(), node.RuntimeIndex); taskComponents = world.EntityManager.GetBuffer(entity); taskComponents.Add(new TaskComponent { Status = TaskStatus.Inactive, Index = node.RuntimeIndex, ParentIndex = AdjustByIndexOffset(m_Data.LogicNodes[i].ParentIndex, taskOffset), SiblingIndex = AdjustByIndexOffset(m_Data.LogicNodes[i].SiblingIndex, taskOffset), BranchIndex = branchIndex, TagComponentType = typeof(TaskObjectTag), Disabled = !IsNodeEnabled(true, m_Data.LogicNodes[i].ParentIndex) || !IsNodeEnabled(true, i), }); world.EntityManager.AddComponent(entity, typeof(TaskObjectTag)); world.EntityManager.SetComponentEnabled(entity, typeof(TaskObjectTag), false); traversalTaskSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(typeof(TaskObjectSystem))); taskObject.Initialize(this, node.RuntimeIndex); } else { Debug.LogError("Error: Unknown Task Type", this); continue; } // Conditional tasks can be reevaluated. if (m_Data.LogicNodes[i] is IConditional && m_Data.LogicNodes[i].ParentIndex != ushort.MaxValue) { var reevaluateTag = new ComponentType(); Type reevaluateSystem; if (m_Data.LogicNodes[i] is ITaskComponentData conditionalTaskComponentData) { if (m_Data.LogicNodes[i] is IReevaluateResponder reevaluateTask) { reevaluateTag = reevaluateTask.ReevaluateTag; reevaluateSystem = reevaluateTask.ReevaluateSystemType; } else { Debug.LogWarning($"Warning: The task {m_Data.LogicNodes[i]} doesn't have a separate reevaluation tag. This may lead to unexpected results. It is recommend " + $"that the task implements the IReevaluate interface."); reevaluateTag = conditionalTaskComponentData.Tag; reevaluateSystem = conditionalTaskComponentData.SystemType; } } else { reevaluateTag = typeof(TaskObjectReevaluateTag); reevaluateSystem = typeof(TaskObjectReevaluateSystem); } world.EntityManager.AddComponent(entity, reevaluateTag); world.EntityManager.SetComponentEnabled(entity, reevaluateTag, false); var entityManager = world.EntityManager; ComponentUtility.AddInterruptComponents(entityManager, entity); reevaluateTaskSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(reevaluateSystem)); // Ignore any decorator tasks when determining the parent. Composite tasks can only be a conditional abort parent. IComposite parentComposite = null; ushort parentIndex; var compositeParentIndex = m_Data.LogicNodes[i].ParentIndex; do { parentComposite = m_Data.LogicNodes[compositeParentIndex] as IComposite; parentIndex = compositeParentIndex; compositeParentIndex = m_Data.LogicNodes[compositeParentIndex].ParentIndex; } while (compositeParentIndex != ushort.MaxValue && parentComposite == null); var abortParent = parentComposite as IConditionalAbortParent; if (abortParent != null && abortParent.AbortType != ConditionalAbortType.None) { var lowerPriorityLowerIndex = ushort.MaxValue; var lowerPriorityUpperIndex = ushort.MaxValue; // Lower Priority aborts are recursive allowing a nested conditional task to be reevaluated even if the direct // parent's sibling isn't active. if (abortParent.AbortType == ConditionalAbortType.LowerPriority || abortParent.AbortType == ConditionalAbortType.Both) { var parentChildCount = m_Data.GetChildCount(m_Data.LogicNodes[parentIndex], m_Data.LogicNodes); lowerPriorityLowerIndex = AdjustByIndexOffset((ushort)(parentIndex + parentChildCount), taskOffset); var parentTranversalIndex = parentIndex; IConditionalAbortParent parentAbortParent = null; while (parentTranversalIndex != ushort.MaxValue && ((parentAbortParent = m_Data.LogicNodes[parentTranversalIndex] as IConditionalAbortParent) != null || m_Data.LogicNodes[parentTranversalIndex] is IDecorator)) { if (parentAbortParent != null && parentAbortParent.AbortType != ConditionalAbortType.LowerPriority && parentAbortParent.AbortType != ConditionalAbortType.Both) { break; } parentIndex = parentTranversalIndex; parentTranversalIndex = m_Data.LogicNodes[parentTranversalIndex].ParentIndex; } // The conditional abort can reevaluate up to the rightmost task of the parent. parentTranversalIndex = parentTranversalIndex != ushort.MaxValue ? parentTranversalIndex : parentIndex; parentChildCount = m_Data.GetChildCount(m_Data.LogicNodes[parentTranversalIndex], m_Data.LogicNodes); lowerPriorityUpperIndex = AdjustByIndexOffset((ushort)(parentTranversalIndex + parentChildCount), taskOffset); } var selfPriorityUpperIndex = ushort.MaxValue; if (abortParent.AbortType == ConditionalAbortType.Self || abortParent.AbortType == ConditionalAbortType.Both) { if (m_Data.LogicNodes[parentIndex].SiblingIndex == ushort.MaxValue) { selfPriorityUpperIndex = (ushort)(parentIndex + m_Data.GetChildCount(m_Data.LogicNodes[parentIndex], m_Data.LogicNodes)); } else { selfPriorityUpperIndex = (ushort)(m_Data.LogicNodes[parentIndex].SiblingIndex - 1); } } var reevaluateTaskComponents = world.EntityManager.AddBuffer(entity); var reevaluateTaskComponent = new ReevaluateTaskComponent() { Index = node.RuntimeIndex, AbortType = abortParent.AbortType, ReevaluateTagComponentType = reevaluateTag, LowerPriorityLowerIndex = lowerPriorityLowerIndex, LowerPriorityUpperIndex = lowerPriorityUpperIndex, SelfPriorityUpperIndex = selfPriorityUpperIndex, }; reevaluateTaskComponents.Add(reevaluateTaskComponent); } } // Add any systems that respond to interrupts. if (m_Data.LogicNodes[i] is IInterruptResponder interruptResponder) { interruptTaskSystemGroup.AddSystemToUpdateList(world.GetOrCreateSystem(interruptResponder.InterruptSystemType)); } } // The event task may perform its own logic. if (eventTask is IEventNodeEntityReceiver entityReceiver) { entityReceiver.AddBufferElement(world, entity); } if (eventTask is IEventNodeGameObjectReceiver gameObjectReceiver) { gameObjectReceiver.Initialize(this); } traversalTaskSystemGroup.SortSystems(); } /// /// Adjusts the index by the specified offset. /// /// The index that should be adjusted. /// The offset that should be adjusted by. /// THe index by the specified offset. private ushort AdjustByIndexOffset(ushort index, ushort offset) { if (index == ushort.MaxValue) { return index; } return (ushort)(index - offset); } /// /// Stars the branch with the specified event task type. /// /// The branch that should be started. /// True if the branch was started. public bool StartBranch(Type eventTaskType) { if (m_World == null || m_Entity == Entity.Null) { return false; } return StartBranch(m_World, m_Entity, eventTaskType); } /// /// Stars the branch with the specified event task type. /// /// The world that the entity exists in. /// The entity that contains the branch. /// The branch that should be started. /// True if the branch was started. public bool StartBranch(World world, Entity entity, Type eventTaskType) { if (!s_BehaviorTreeByEntity.ContainsKey(entity)) { return StartBehavior(world, entity, eventTaskType); } if (m_Data.EventNodes == null || entity.Index == 0 || !Application.isPlaying) { return false; } for (int i = 0; i < m_Data.EventNodes.Length; ++i) { if (m_Data.EventNodes[i].GetType() == eventTaskType) { // The branch cannot start if it is disabled. if (!IsNodeEnabled(false, i)) { Debug.LogError($"Error: Unable to start the {eventTaskType.Name} branch because the node is disabled.", this); return false; } return StartBranch(world, entity, m_Data.EventNodes[i]); } } Debug.LogError($"Error: Unable to start the {eventTaskType.Name} branch because it can't be found.", this); return false; } /// /// Starts the branch with the specified event task. /// /// The branch that should be started. /// True if the branch was started. public bool StartBranch(IEventNode eventTask) { if (m_World == null || m_Entity == Entity.Null) { return false; } return StartBranch(m_World, m_Entity, eventTask); } /// /// Starts the branch with the specified event task. /// /// The world that the entity exists in. /// The entity that contains the branch. /// The branch that should be started. /// True if the branch was started. public bool StartBranch(World world, Entity entity, IEventNode eventTask) { if (!Application.isPlaying || entity == Entity.Null) { return false; } // The branch can't be started if it's not connected to any tasks. if (eventTask.ConnectedIndex == ushort.MaxValue) { Debug.LogError($"Error: Unable to start the {eventTask.GetType().Name} branch because it doesn't have a connecting task.", this); return false; } // The tree needs to be setup before the branch can start. if (!world.EntityManager.HasBuffer(entity)) { InitializeTree(); } var connectedIndex = m_Data.LogicNodes[eventTask.ConnectedIndex].RuntimeIndex; return StartBranch(world, entity, connectedIndex, m_UpdateMode == UpdateMode.EveryFrame); } /// /// Starts the branch. This method is static allowing for alread-baked entities to be able to start the branch. /// /// The world that the entity exists in. /// The entity that contains the branch. /// The index of the starting task. /// Should the branch start evaluation? If false the tree will manually need to be ticked. /// True if the branch was started. internal static bool StartBranch(World world, Entity entity, ushort connectedIndex, bool startEvaluation) { var taskComponents = world.EntityManager.GetBuffer(entity); if (connectedIndex >= taskComponents.Length) { Debug.LogError($"Error: Unable to start the branch on entity {entity} because the start index is greater than the task count."); return false; } var branchIndex = taskComponents[connectedIndex].BranchIndex; var startTask = taskComponents[connectedIndex]; // The branch can't be started twice or if it is disabled. if (startTask.Status == TaskStatus.Queued || startTask.Status == TaskStatus.Running || startTask.Disabled) { return false; } var systemGroup = world.GetExistingSystemManaged(); if (systemGroup == null) { systemGroup = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged(); if (systemGroup == null) { return false; } } systemGroup.Enabled = true; // The branch can start. startTask.Status = TaskStatus.Queued; taskComponents[connectedIndex] = startTask; var activeTag = taskComponents[connectedIndex].TagComponentType; world.EntityManager.SetComponentEnabled(entity, activeTag, true); var branchComponents = world.EntityManager.GetBuffer(entity); var branchComponent = branchComponents[branchIndex]; branchComponent.ActiveIndex = branchComponent.NextIndex = connectedIndex; branchComponent.ActiveTagComponentType = activeTag; branchComponents[branchIndex] = branchComponent; var evaluateComponent = world.EntityManager.GetComponentData(entity); evaluateComponent.EvaluationCount = 0; world.EntityManager.SetComponentData(entity, evaluateComponent); if (startEvaluation) { world.EntityManager.SetComponentEnabled(entity, true); world.EntityManager.SetComponentEnabled(entity, true); } return true; } /// /// Returns the task at the specified index. /// /// The index of the task. /// The task at the specified index. public ILogicNode GetTask(int index) { Deserialize(); if (m_Data.LogicNodes == null || index < 0 || index >= m_Data.LogicNodes.Length) { return null; } if (Application.isPlaying && m_NodeIndexByRuntimeIndex.Count > 0) { return m_Data.LogicNodes[m_NodeIndexByRuntimeIndex[index]]; } return m_Data.LogicNodes[index]; } /// /// Finds the task with the specified type. /// /// The first task found with the specified type (can be null). public T FindTask() where T : Task { Deserialize(); if (m_Data.LogicNodes == null || m_Data.LogicNodes.Length == 0) { return null; } for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is T task) { return task; } if (m_Data.LogicNodes[i] is IStackedNode stackedNode) { if (stackedNode.Nodes == null) { continue; } for (int j = 0; j < stackedNode.Nodes.Length; ++j) { if (stackedNode.Nodes[j] is T stackedTask) { return stackedTask; } } } } return null; } /// /// Finds the tasks with the specified type. This method does not have any allocations. /// /// A pre-initialized array that will contain the found tasks. /// The number of tasks found with the specified type. public int FindTasks(T[] foundTasks) where T : Task { if (foundTasks == null || foundTasks.Length == 0) { return 0; } Deserialize(); if (m_Data.LogicNodes == null || m_Data.LogicNodes.Length == 0) { return 0; } var count = 0; for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is T task) { foundTasks[count] = task; count++; if (count == foundTasks.Length) { return count; } } if (m_Data.LogicNodes[i] is IStackedNode stackedNode) { if (stackedNode.Nodes == null) { continue; } for (int j = 0; j < stackedNode.Nodes.Length; ++j) { if (stackedNode.Nodes[j] is T stackedTask) { foundTasks[count] = stackedTask; count++; if (count == foundTasks.Length) { return count; } } } } } return count; } /// /// Finds the tasks with the specified type. /// /// An array containing the found tasks. public T[] FindTasks() where T : Task { Deserialize(); if (m_Data.LogicNodes == null || m_Data.LogicNodes.Length == 0) { return null; } // Assume the maximum number of tasks will be found. The array will be resized before returning. var foundTasks = new T[m_Data.LogicNodes.Length]; var count = FindTasks(foundTasks); if (foundTasks.Length != count) { Array.Resize(ref foundTasks, count); } return foundTasks; } /// /// Ticks the behavior tree. The UpdateMode must be set to Manual. /// The behavior tree will not be executed instantaneously. It will instead be ticked the next time the DOTS system group is updated. /// public void Tick() { if (m_UpdateMode != UpdateMode.Manual) { Debug.LogWarning("Warning: The behavior tree UpdateMode must be set to Manual in order to be ticked manually.", this); return; } if (m_World == null || m_Entity == Entity.Null) { Debug.LogWarning("Warning: The behavior tree must be started in order for it to be ticked manually.", this); return; } Tick(m_World, m_Entity); } /// /// Ticks the behavior tree. /// The behavior tree will not be executed instantaneously. It will instead be ticked the next time the DOTS system group is updated. /// /// The world that the entity exists in. /// The entity that contains the behavior tree. public static void Tick(World world, Entity entity) { if (world == null || entity.Index == 0) { return; } world.EntityManager.SetComponentEnabled(entity, true); } /// /// Reevaluates the SubtreeReferences by calling the EvaluateSubtrees method. /// public void ReevaluateSubtreeReferences() { if (!m_Data.ReevaluateSubtreeReferences(this, this, ClearTree)) { return; } // Restart the tree. InitializeTree(); if (enabled && m_GameObject.activeSelf) { StartBehavior(); } } /// /// Stops or pauses the behavior tree. /// /// Should the behavior tree be paused? /// True if the behavior tree was stopped or paused. public bool StopBehavior(bool pause = false) { return StopBehavior(m_World, m_Entity, pause); } /// /// Stops or pauses the behavior tree. /// /// The world that the entity exists in. /// The entity that contains the behavior tree. /// Should the behavior tree be paused? /// True if the behavior tree was stopped or paused. public bool StopBehavior(World world, Entity entity, bool pause = false) { if (world == null || !world.IsCreated || entity == Entity.Null) { return false; } // The tree could be stopped after it has been paused. if (world.EntityManager.HasComponent(entity)) { world.EntityManager.SetComponentEnabled(entity, false); } world.EntityManager.SetComponentEnabled(entity, false); if (!s_BehaviorTreeByEntity.ContainsKey(entity)) { return false; } // Notify those interested that the behavior tree has been stopped. if (OnBehaviorTreeStopped != null) { OnBehaviorTreeStopped(pause); } // Tasks can implement a pause and end specific callback. var tasks = Data.LogicNodes; for (int i = 0; i < tasks.Length; ++i) { if (pause) { if (!(tasks[i] is IPausableTask pausableTask)) { continue; } pausableTask.Pause(world, entity); } else if (m_Data.LogicNodes[i] is Task task) { task.OnEnd(); } } // Removing the EnabledTag and EvaluationComponent is sufficient to pause the tree. if (pause) { return true; } s_BehaviorTreeByEntity.Remove(entity); StopBehavior(world, entity); return true; } /// /// Stops the behavior tree. This method should only be called from an ECS system. /// /// The world that the entity exists in. /// The entity that contains the behavior tree. public static void StopBehavior(World world, Entity entity) { if (world == null || entity.Index == 0) { return; } var branchComponents = world.EntityManager.GetBuffer(entity); var taskComponents = world.EntityManager.GetBuffer(entity); for (int i = 0; i < branchComponents.Length; ++i) { var branchComponent = branchComponents[i]; if (branchComponent.ActiveIndex == ushort.MaxValue) { continue; } // Stop all of the active tasks within the branch. var taskIndex = branchComponent.ActiveIndex; while (taskIndex != ushort.MaxValue) { var taskComponent = taskComponents[taskIndex]; taskComponent.Status = TaskStatus.Inactive; taskComponent.Reevaluate = false; taskComponents[taskIndex] = taskComponent; taskIndex = taskComponent.ParentIndex; } world.EntityManager.SetComponentEnabled(entity, branchComponent.ActiveTagComponentType, false); branchComponent.ActiveIndex = branchComponent.NextIndex = ushort.MaxValue; branchComponent.ActiveTagComponentType = new ComponentType(); branchComponent.InterruptType = InterruptType.None; branchComponent.InterruptIndex = 0; branchComponents[i] = branchComponent; } // Stop all reevaluations. if (world.EntityManager.HasBuffer(entity)) { var reevaluateTaskComponents = world.EntityManager.GetBuffer(entity); for (int i = 0; i < reevaluateTaskComponents.Length; ++i) { if (reevaluateTaskComponents[i].ReevaluateStatus == ReevaluateStatus.Inactive) { continue; } var reevaluateTaskComponent = reevaluateTaskComponents[i]; world.EntityManager.SetComponentEnabled(entity, reevaluateTaskComponent.ReevaluateTagComponentType, false); reevaluateTaskComponent.ReevaluateStatus = ReevaluateStatus.Inactive; reevaluateTaskComponent.OriginalStatus = TaskStatus.Inactive; reevaluateTaskComponents[i] = reevaluateTaskComponent; } } } /// /// Restarts the behavior tree. /// /// True if the behavior tree was restarted. public bool RestartBehavior() { if (!IsActive()) { return false; } if (!StopBehavior()) { return false; } return StartBehavior(); } /// /// Clears all of the tree components. /// /// The world that the entity exists in. /// The entity that contains the behavior tree. private void ClearTree() { if (m_Entity == Entity.Null) { return; } ClearTree(m_World, m_Entity); } /// /// Clears all of the tree components. /// /// The world that the entity exists in. /// The entity that contains the behavior tree. private void ClearTree(World world, Entity entity) { if (Data.LogicNodes == null) { return; } StopBehavior(world, entity, false); world.EntityManager.RemoveComponent(entity); world.EntityManager.RemoveComponent(entity); var branchComponents = world.EntityManager.GetBuffer(entity); var taskComponents = world.EntityManager.GetBuffer(entity); branchComponents.Clear(); taskComponents.Clear(); if (world.EntityManager.HasBuffer(entity)) { var reevaluateTaskComponents = world.EntityManager.GetBuffer(entity); reevaluateTaskComponents.Clear(); } for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is ITaskComponentData taskComponentData) { taskComponentData.ClearBufferElement(world, entity); if (world.EntityManager.HasComponent(entity, taskComponentData.Tag)) { world.EntityManager.RemoveComponent(entity, taskComponentData.Tag); } if (m_Data.LogicNodes[i] is IReevaluateResponder reevaluateTask) { if (world.EntityManager.HasComponent(entity, reevaluateTask.ReevaluateTag)) { world.EntityManager.RemoveComponent(entity, reevaluateTask.ReevaluateTag); } } } else if (m_Data.LogicNodes[i] is Task monoTask) { monoTask.ClearBufferElement(world, entity); if (m_Data.LogicNodes[i] is IConditional) { if (world.EntityManager.HasComponent(entity, typeof(TaskObjectReevaluateTag))) { world.EntityManager.RemoveComponent(entity, typeof(TaskObjectReevaluateTag)); } if (world.EntityManager.HasComponent(entity, typeof(InterruptTag))) { world.EntityManager.RemoveComponent(entity, typeof(InterruptTag)); } } if (world.EntityManager.HasComponent(entity, typeof(TaskObjectTag))) { world.EntityManager.RemoveComponent(entity, typeof(TaskObjectTag)); } } } for (int i = 0; i < m_Data.EventNodes.Length; ++i) { if (m_Data.EventNodes[i] is IEventNodeEntityReceiver entityReceiver) { entityReceiver.ClearBufferElement(world, entity); } } m_NodeIndexByRuntimeIndex.Clear(); } /// /// Returns the SharedVariable with the specified name. /// /// The name of the SharedVariable that should be retrieved. /// The SharedVariable with the specified name (can be null). public SharedVariable GetVariable(string name) { Deserialize(); return m_Data.GetVariable(this, name, SharedVariable.SharingScope.Graph); } /// /// Returns the SharedVariable with the specified name and scope. /// /// The name of the SharedVariable that should be retrieved. /// The scope of the SharedVariable that should be retrieved. /// The SharedVariable with the specified name (can be null). public SharedVariable GetVariable(string name, SharedVariable.SharingScope scope) { Deserialize(); return m_Data.GetVariable(this, name, scope); } /// /// Returns the SharedVariable of the specified name. /// /// The name of the SharedVariable that should be retrieved. /// The SharedVariable with the specified name (can be null). public SharedVariable GetVariable(string name) { Deserialize(); return m_Data.GetVariable(this, name, SharedVariable.SharingScope.Graph); } /// /// Returns the SharedVariable of the specified name and scope. /// /// The name of the SharedVariable that should be retrieved. /// The scope of the SharedVariable that should be retrieved. /// The SharedVariable with the specified name (can be null). public SharedVariable GetVariable(string name, SharedVariable.SharingScope scope) { Deserialize(); return m_Data.GetVariable(this, name, scope); } /// /// Sets the value of the SharedVariable. /// /// The type of SharedVarible. /// The name of the SharedVariable. /// The value of the SharedVariable. public void SetVariableValue(string name, T value) { Deserialize(); m_Data.SetVariableValue(this, name, value, SharedVariable.SharingScope.Graph); } /// /// Sets the value of the SharedVariable. /// /// The type of SharedVarible. /// The name of the SharedVariable. /// The value of the SharedVariable. /// The scope of the SharedVariable that should be set. public void SetVariableValue(string name, T value, SharedVariable.SharingScope scope) { Deserialize(); m_Data.SetVariableValue(this, name, value, scope); } /// /// Gets the behavior tree save data. /// /// Specifies which variables should be saved. Graph variables will automatically be saved. /// The save data if the behavior tree can be saved. public SaveManager.SaveData? Save(SaveManager.VariableSaveScope variableSaveScope = 0) { if (OnWillSave != null) { OnWillSave(this); } var saveData = SaveManager.Save(new BehaviorTree[] { this }, variableSaveScope); if (OnDidSave != null) { OnDidSave(this, saveData.HasValue); } return saveData; } /// /// Saves the behavior tree at the specified file path. /// /// The file path to save the behavior tree at. The file will be replaced if it already exists. /// Specifies which variables should be saved. Graph variables will automatically be saved. /// True if the behavior tree was successfully saved. public bool Save(string filePath, SaveManager.VariableSaveScope variableSaveScope = 0) { if (OnWillSave != null) { OnWillSave(this); } var success = SaveManager.Save(new BehaviorTree[] { this }, filePath, variableSaveScope); if (OnDidSave != null) { OnDidSave(this, success); } return success; } /// /// Loads the behavior tree from the specified file path. /// /// The file path to load the behavior tree at. /// Optional callback after the graph variables have been restored. /// True if the behavior tree was successfully loaded. public bool Load(string filePath, Action afterVariablesRestored = null) { if (OnWillLoad != null) { OnWillLoad(this); } var success = SaveManager.Load(new BehaviorTree[] { this }, filePath, afterVariablesRestored); if (OnDidLoad != null) { OnDidLoad(this, success); } return success; } /// /// Loads the behavior tree from the specified save data. /// /// The data associated with the behavior tree. /// Optional callback after the graph variables have been restored. /// True if the behavior tree was successfully loaded. public bool Load(SaveManager.SaveData saveData, Action afterVariablesRestored = null) { if (OnWillLoad != null) { OnWillLoad(this); } var success = SaveManager.Load(new BehaviorTree[] { this }, saveData, afterVariablesRestored); if (OnDidLoad != null) { OnDidLoad(this, success); } return success; } /// /// Starts the task courtine with the specified name. /// /// The task that the coroutine belongs to. /// The name of the coroutine method. /// The created routine (can be null). public Coroutine StartTaskCoroutine(Task task, string coroutineName) { var method = task.GetType().GetMethod(coroutineName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (method == null) { Debug.LogError($"Error: The coroutine {coroutineName} cannot be started due to the method not being found on {task.GetType()}."); return null; } if (m_ActiveTaskCoroutines == null) { m_ActiveTaskCoroutines = new Dictionary>(); } var taskCoroutine = new TaskCoroutine(this, (IEnumerator)method.Invoke(task, new object[] { }), coroutineName); if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) { taskCoroutines = new List(); m_ActiveTaskCoroutines.Add(coroutineName, taskCoroutines); } taskCoroutines.Add(taskCoroutine); return taskCoroutine.Coroutine; } /// /// Starts the task courtine with the specified name. /// /// The task that the coroutine belongs to. /// The name of the coroutine method. /// The input parameter to the coroutine. /// The created routine (can be null). public Coroutine StartTaskCoroutine(Task task, string coroutineName, object value) { var method = task.GetType().GetMethod(coroutineName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (method == null) { Debug.LogError($"Error: The coroutine {coroutineName} cannot be started due to the method not being found on {task.GetType()}."); return null; } if (m_ActiveTaskCoroutines == null) { m_ActiveTaskCoroutines = new Dictionary>(); } var taskCoroutine = new TaskCoroutine(this, (IEnumerator)method.Invoke(task, new object[] { value }), coroutineName); if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) { taskCoroutines = new List(); m_ActiveTaskCoroutines.Add(coroutineName, taskCoroutines); } taskCoroutines.Add(taskCoroutine); return taskCoroutine.Coroutine; } /// /// Stops the task courtine with the specified name. /// /// The name of the coroutine method. public void StopTaskCoroutine(string coroutineName) { if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) { return; } for (int i = 0; i < taskCoroutines.Count; ++i) { taskCoroutines[i].Stop(); } } /// /// Stops all of the task coroutines. /// public void StopAllTaskCoroutines() { if (m_ActiveTaskCoroutines == null) { return; } foreach (var entry in m_ActiveTaskCoroutines) { var taskCoroutines = entry.Value; for (int i = 0; i < taskCoroutines.Count; ++i) { taskCoroutines[i].Stop(); } } } /// /// The TaskCoroutine has ended. /// /// The coroutine that has ended. /// The name of the coroutine. public void TaskCoroutineEnded(TaskCoroutine taskCoroutine, string coroutineName) { if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) { return; } taskCoroutines.Remove(taskCoroutine); if (taskCoroutines.Count == 0) { m_ActiveTaskCoroutines.Remove(coroutineName); } } /// /// OnCollisionEnter callback. /// /// The resulting collision. private void OnCollisionEnter(Collision collision) { if (OnBehaviorTreeCollisionEnter != null) { OnBehaviorTreeCollisionEnter(collision); } } /// /// OnCollisionExit callback. /// /// The resulting collision. private void OnCollisionExit(Collision collision) { if (OnBehaviorTreeCollisionExit != null) { OnBehaviorTreeCollisionExit(collision); } } /// /// OnCollisionEnter2D callback. /// /// The resulting collision. private void OnCollisionEnter2D(Collision2D collision) { if (OnBehaviorTreeCollisionEnter2D != null) { OnBehaviorTreeCollisionEnter2D(collision); } } /// /// OnCollisionExit2D callback. /// /// The resulting collision. private void OnCollisionExit2D(Collision2D collision) { if (OnBehaviorTreeCollisionExit2D != null) { OnBehaviorTreeCollisionExit2D(collision); } } /// /// OnTriggerEnter callback. /// /// The overlapping collider. private void OnTriggerEnter(Collider other) { if (OnBehaviorTreeTriggerEnter != null) { OnBehaviorTreeTriggerEnter(other); } } /// /// OnTriggerExit callback. /// /// The collider that is no longer overlapping. private void OnTriggerExit(Collider other) { if (OnBehaviorTreeTriggerExit != null) { OnBehaviorTreeTriggerExit(other); } } /// /// OnTriggerEnter2D callback. /// /// The overlapping collider. private void OnTriggerEnter2D(Collider2D other) { if (OnBehaviorTreeTriggerEnter2D != null) { OnBehaviorTreeTriggerEnter2D(other); } } /// /// OnTriggerExit2D callback. /// /// The collider that is no longer overlapping. private void OnTriggerExit2D(Collider2D other) { if (OnBehaviorTreeTriggerExit2D != null) { OnBehaviorTreeTriggerExit2D(other); } } /// /// OnControllerColliderHit callback. /// /// The hit result. private void OnControllerColliderHit(ControllerColliderHit hit) { if (OnBehaviorTreeControllerColliderHit != null) { OnBehaviorTreeControllerColliderHit(hit); } } #if UNITY_EDITOR /// /// OnDrawGizmos callback. /// private void OnDrawGizmos() { if (!enabled) { return; } if (m_Data != null && m_Data.LogicNodes != null) { for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is Task task) { task.OnDrawGizmos(this); } } } } /// /// OnDrawGizmos callback. /// private void OnDrawGizmosSelected() { if (!enabled) { return; } if (m_Data != null && m_Data.LogicNodes != null) { for (int i = 0; i < m_Data.LogicNodes.Length; ++i) { if (m_Data.LogicNodes[i] is Task task) { task.OnDrawGizmosSelected(this); } } } } #endif /// /// The behavior tree has been disabled. /// private void OnDisable() { if (m_Entity == Entity.Null) { return; } StopBehavior(m_World, m_Entity, m_PauseWhenDisabled); } /// /// The behavior tree has been destroyed. /// private void OnDestroy() { if (m_Entity == Entity.Null) { return; } if (OnBehaviorTreeDestroyed != null) { OnBehaviorTreeDestroyed(); } StopBehavior(m_World, m_Entity, false); m_GameObject = null; } /// /// Is the node with the specified index enabled? /// /// Is the node a LogicNode? /// The index of the node. /// True if the node with the specified index is enabled. public bool IsNodeEnabled(bool logicNode, int index) { return Data.IsNodeEnabled(logicNode, index); } /// /// Is the node with the specified index active? /// /// Is the node a LogicNode? /// The index of the node. /// True if the node with the specified index is active. public bool IsNodeActive(bool logicNode, int index) { if (!Application.isPlaying || m_Entity == Entity.Null) { return false; } var taskComponents = m_World.EntityManager.GetBuffer(m_Entity); var logicNodeIndex = index; if (!logicNode && m_Data.EventNodes != null && index < m_Data.EventNodes.Length) { // Find the logic node that the event node is connected to. logicNodeIndex = m_Data.EventNodes[index].ConnectedIndex; } if (logicNodeIndex >= taskComponents.Length) { return false; } var taskComponent = taskComponents[logicNodeIndex]; return taskComponent.Status == TaskStatus.Running; } /// /// Returns true if the behavior tree is active. /// /// True if the behavior tree is active. public bool IsActive() { if (m_Entity == Entity.Null) { return false; } return s_BehaviorTreeByEntity.ContainsKey(m_Entity); } /// /// Copies the graph onto the current graph. /// /// The graph that should be copied. public void Clone(IGraph other) { m_Data = new BehaviorTreeData(); m_Data.EventNodes = other.EventNodes; m_Data.LogicNodes = other.LogicNodes; m_Data.SharedVariables = other.SharedVariables; #if UNITY_EDITOR m_Data.EventNodeProperties = other.EventNodeProperties; m_Data.LogicNodeProperties = other.LogicNodeProperties; m_Data.GroupProperties = other.GroupProperties; #endif m_Data.Serialize(); } /// /// Overrides ToString. /// /// The desired string value. public override string ToString() { return $"{m_GraphName} (Index {m_Index})"; } /// /// Callback when the domain should be reloaded. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void Reinitialize() { s_BehaviorTreeByEntity = new Dictionary(); } /// /// Enables the baked behavior tree system. /// /// The world that the system has been added to. public static void EnableBakedBehaviorTreeSystem(World world) { if (world == null) { return; } EnableBakedBehaviorTreeSystem(world.Unmanaged); } /// /// Enables the baked behavior tree system. /// /// The unmanged world that the system has been added to. public static void EnableBakedBehaviorTreeSystem(WorldUnmanaged world) { world.GetExistingSystemState().Enabled = true; } /// /// Converts the behavior tree to a DOTS entity. /// public class BehaviorTreeBaker : Baker { private static MethodInfo s_GetTypeOfSystemMethod; /// /// Bakes the behavior tree to the DOTS entity. /// /// The authoring behavior tree. public override void Bake(BehaviorTree behaviorTree) { if (!behaviorTree.StartWhenEnabled || !behaviorTree.enabled) { return; } var entity = GetEntity(behaviorTree, TransformUsageFlags.Dynamic); var worlds = World.All; for (int i = 0; i < worlds.Count; ++i) { if (worlds[i].EntityManager.Exists(entity)) { if (!behaviorTree.InitializeTree(worlds[i], entity)) { continue; } var connectedIndex = GetStartTaskConnectedIndex(behaviorTree); if (connectedIndex == -1 || connectedIndex == ushort.MaxValue) { return; } var taskComponents = worlds[i].EntityManager.GetBuffer(entity); var tagStableTypeHash = new ulong[taskComponents.Length]; for (int j = 0; j < taskComponents.Length; ++j) { tagStableTypeHash[j] = TypeManager.GetTypeInfo(taskComponents[j].TagComponentType.TypeIndex).StableTypeHash; } ulong[] reevaluateTagStableTypeHash = null; if (worlds[i].EntityManager.HasBuffer(entity)) { var reevaluateTaskComponents = worlds[i].EntityManager.GetBuffer(entity); reevaluateTagStableTypeHash = new ulong[reevaluateTaskComponents.Length]; for (int j = 0; j < reevaluateTaskComponents.Length; ++j) { reevaluateTagStableTypeHash[j] = TypeManager.GetTypeInfo(reevaluateTaskComponents[j].ReevaluateTagComponentType.TypeIndex).StableTypeHash; } } AddComponentObject(entity, new BakedBehaviorTree { StartEventConnectedIndex = connectedIndex, StartEvaluation = behaviorTree.UpdateMode == UpdateMode.EveryFrame, ReevaluateTaskSystems = GetTaskSystems(worlds[i]), InterruptTaskSystems = GetTaskSystems(worlds[i]), TraversalTaskSystems = GetTaskSystems(worlds[i]), TagStableTypeHashes = tagStableTypeHash, ReevaluateTagStableTypeHashes = reevaluateTagStableTypeHash, }); behaviorTree.Baked = true; } } } /// /// Returns the index of the node connection for the start event task. /// /// The interested behavior tree. /// The index of the node connection for the start event task. private int GetStartTaskConnectedIndex(BehaviorTree behaviorTree) { // The behavior tree has to first be initialized. if (behaviorTree.World == null || behaviorTree.Entity == Entity.Null) { Debug.LogError($"Error: Unable to retrieve the connected index on behavior tree {behaviorTree}. The behavior tree has to first be initialized."); return -1; } var data = behaviorTree.Data; for (int i = 0; i < data.EventNodes.Length; ++i) { if (data.EventNodes[i].GetType() == typeof(Start)) { // The connected index may not be valid. if (data.EventNodes[i].ConnectedIndex == ushort.MaxValue) { return ushort.MaxValue; } // The branch cannot start if it is disabled. if (!behaviorTree.IsNodeEnabled(false, i)) { return -1; } return data.LogicNodes[data.EventNodes[i].ConnectedIndex].RuntimeIndex; } } return -1; } /// /// Returns all of the system type indicies within the systems of the specified type. /// /// The world that the systems were added to. /// The system type indicies within the systems of the specified type (can be null). private string[] GetTaskSystems(World world) where T : ComponentSystemGroup { var systemGroup = world.GetExistingSystemManaged(); if (systemGroup == null) { return null; } var systems = systemGroup.GetAllSystems(); if (systems.Length == 0) { systems.Dispose(); return null; } // Use reflection to call WorldUnmanaged.GetTypeOfSystem. This method is only called during baking at edit time so reflection is ok, though it would be better // if this method was eventually made public. if (s_GetTypeOfSystemMethod == null) { s_GetTypeOfSystemMethod = typeof(WorldUnmanaged).GetMethod("GetTypeOfSystem", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (s_GetTypeOfSystemMethod == null) { Debug.LogError("Error: Unable to find WorldUnmanaged.GetTypeOfSystem. Please email support@opsive.com with your Unity version and Entity package version."); return null; } } var systemTypes = new string[systems.Length]; for (int i = 0; i < systems.Length; ++i) { var systemTypeIndex = TypeManager.GetSystemTypeIndex((Type)s_GetTypeOfSystemMethod.Invoke(world.Unmanaged, new object[] { systems[i] })); systemTypes[i] = systemTypeIndex.ToString(); } systems.Dispose(); return systemTypes; } } } } #endif