1927 lines
85 KiB
C#
1927 lines
85 KiB
C#
![]() |
#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;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Container for managing behavior tree logic.
|
|||
|
/// </summary>
|
|||
|
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<int, int> m_NodeIndexByRuntimeIndex = new Dictionary<int, int>();
|
|||
|
private static Dictionary<Entity, BehaviorTree> s_BehaviorTreeByEntity = new Dictionary<Entity, BehaviorTree>();
|
|||
|
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<TaskComponent>(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<bool> OnBehaviorTreeStopped;
|
|||
|
public Action OnBehaviorTreeDestroyed;
|
|||
|
|
|||
|
// Physics callbacks.
|
|||
|
public Action<Collision> OnBehaviorTreeCollisionEnter;
|
|||
|
public Action<Collision> OnBehaviorTreeCollisionExit;
|
|||
|
public Action<Collision2D> OnBehaviorTreeCollisionEnter2D;
|
|||
|
public Action<Collision2D> OnBehaviorTreeCollisionExit2D;
|
|||
|
public Action<Collider> OnBehaviorTreeTriggerEnter;
|
|||
|
public Action<Collider> OnBehaviorTreeTriggerExit;
|
|||
|
public Action<Collider2D> OnBehaviorTreeTriggerEnter2D;
|
|||
|
public Action<Collider2D> OnBehaviorTreeTriggerExit2D;
|
|||
|
public Action<ControllerColliderHit> OnBehaviorTreeControllerColliderHit;
|
|||
|
|
|||
|
// Event callbacks.
|
|||
|
public Action<BehaviorTree> OnWillSave;
|
|||
|
public Action<BehaviorTree, bool> OnDidSave;
|
|||
|
public Action<BehaviorTree> OnWillLoad;
|
|||
|
public Action<BehaviorTree, bool> OnDidLoad;
|
|||
|
|
|||
|
// Coroutine support.
|
|||
|
private Dictionary<string, List<TaskCoroutine>> m_ActiveTaskCoroutines;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Serializes the behavior tree.
|
|||
|
/// </summary>
|
|||
|
public void Serialize()
|
|||
|
{
|
|||
|
Data.Serialize();
|
|||
|
if (m_Subtree != null) {
|
|||
|
m_Data.SerializeSharedVariables();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Deserialize the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <param name="force">Should the behavior tree be force deserialized?</param>
|
|||
|
/// <returns>True if the tree was deserialized.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Inherits the subtree tasks and variables.
|
|||
|
/// </summary>
|
|||
|
/// <param name="force">Should the behavior tree be force deserialized?</param>
|
|||
|
/// <returns>True if the subtree was inherited.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds the specified node.
|
|||
|
/// </summary>
|
|||
|
/// <param name="node">The node that should be added.</param>
|
|||
|
public void AddNode(ILogicNode node)
|
|||
|
{
|
|||
|
Data.AddNode(node);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes the specified node.
|
|||
|
/// </summary>
|
|||
|
/// <param name="node">The node that should be removed.</param>
|
|||
|
/// <returns>True if the node was removed.</returns>
|
|||
|
public bool RemoveNode(ILogicNode node)
|
|||
|
{
|
|||
|
return Data.RemoveNode(node);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds the specified event node.
|
|||
|
/// </summary>
|
|||
|
/// <param name="eventNode">The event node that should be added.</param>
|
|||
|
public void AddNode(IEventNode eventNode)
|
|||
|
{
|
|||
|
Data.AddNode(eventNode);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes the specified event node.
|
|||
|
/// </summary>
|
|||
|
/// <param name="eventNode">The event node that should be removed.</param>
|
|||
|
/// <returns>True if the event node was removed.</returns>
|
|||
|
public bool RemoveNode(IEventNode eventNode)
|
|||
|
{
|
|||
|
return Data.RemoveNode(eventNode);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the Node of the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="type">The type of Node that should be retrieved.</typeparam>
|
|||
|
/// <returns>The Node of the specified type (can be null).</returns>
|
|||
|
public ILogicNode GetNode(Type type)
|
|||
|
{
|
|||
|
return Data.GetNode(type);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the EventNode of the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="type">The type of EventNode that should be retrieved.</typeparam>
|
|||
|
/// <returns>The EventNode of the specified type (can be null). If the node is found the index will also be returned.</returns>
|
|||
|
public (IEventNode, ushort) GetEventNode(Type type)
|
|||
|
{
|
|||
|
return Data.GetEventNode(type);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The component has been enabled.
|
|||
|
/// </summary>
|
|||
|
private void OnEnable()
|
|||
|
{
|
|||
|
if (m_World != null && m_StartWhenEnabled) {
|
|||
|
StartBehavior();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The component has started.
|
|||
|
/// </summary>
|
|||
|
private void Start()
|
|||
|
{
|
|||
|
if (m_StartWhenEnabled) {
|
|||
|
StartBehavior();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the behavior tree component specified by the entity.
|
|||
|
/// </summary>
|
|||
|
/// <param name="entity">The entity that should be retrieved.</param>
|
|||
|
/// <returns>The behavior tree component specified by the ID.</returns>
|
|||
|
public static BehaviorTree GetBehaviorTree(Entity entity)
|
|||
|
{
|
|||
|
if (s_BehaviorTreeByEntity.TryGetValue(entity, out var behaviorTree)) {
|
|||
|
return behaviorTree;
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <returns>True if the behavior tree was started.</returns>
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that should contain the behavior tree.</param>
|
|||
|
/// <param name="entity">The entity that should contain the behavior tree.</param>
|
|||
|
/// <returns>True if the behavior tree was started.</returns>
|
|||
|
public bool StartBehavior(World world, Entity entity)
|
|||
|
{
|
|||
|
return StartBehavior(world, entity, typeof(Start));
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that contains tthe entity.</param>
|
|||
|
/// <param name="entity">The entity that should contain the behavior tree.</param>
|
|||
|
/// <param name="startBranchType">The type of branch that should be started.</param>
|
|||
|
/// <returns>True if the behavior tree was started.</returns>
|
|||
|
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<EnabledTag>(entity) && !world.EntityManager.IsComponentEnabled<EnabledTag>(entity)) {
|
|||
|
world.EntityManager.SetComponentEnabled<EnabledTag>(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);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes the tree within DOTS for all of the event tasks.
|
|||
|
/// </summary>
|
|||
|
/// <returns>True if the behavior tree was initialized.</returns>
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes the tree within DOTS for all of the event tasks.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that contains tthe entity.</param>
|
|||
|
/// <param name="entity">The entity that should contain the behavior tree.</param>
|
|||
|
/// <returns>True if the behavior tree was initialized.</returns>
|
|||
|
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<EvaluationComponent>(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<IEventNode> disabledNodes = null;
|
|||
|
if (m_Data.DisabledEventNodes != null && m_Data.DisabledEventNodes.Length > 0) {
|
|||
|
disabledNodes = new HashSet<IEventNode>();
|
|||
|
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<IEventNode, NodeProperties>(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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Comparer for IEventNode.
|
|||
|
/// </summary>
|
|||
|
private class EventNodeComparer : IComparer<IEventNode>
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Compares the ConnectedIndex of two event nodes.
|
|||
|
/// </summary>
|
|||
|
/// <param name="eventNode1">The first event node</param>
|
|||
|
/// <param name="eventNode2">The second event node.</param>
|
|||
|
/// <returns>The CompareTo value between the two event nodes.</returns>
|
|||
|
public int Compare(IEventNode eventNode1, IEventNode eventNode2)
|
|||
|
{
|
|||
|
return eventNode1.ConnectedIndex.CompareTo(eventNode2.ConnectedIndex);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initialize the specified event branch within DOTS.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that the branch should be added to.</param>
|
|||
|
/// <param name="eventTask">The task that should be setup.</param>
|
|||
|
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<TaskComponent>(entity)) {
|
|||
|
world.EntityManager.AddBuffer<TaskComponent>(entity);
|
|||
|
}
|
|||
|
|
|||
|
DynamicBuffer<BranchComponent> branchComponents;
|
|||
|
if (world.EntityManager.HasBuffer<BranchComponent>(entity)) {
|
|||
|
branchComponents = world.EntityManager.GetBuffer<BranchComponent>(entity);
|
|||
|
} else {
|
|||
|
branchComponents = world.EntityManager.AddBuffer<BranchComponent>(entity);
|
|||
|
}
|
|||
|
var startBranchIndex = (ushort)branchComponents.Length;
|
|||
|
branchComponents.Add(new BranchComponent() { ActiveIndex = ushort.MaxValue, NextIndex = ushort.MaxValue });
|
|||
|
|
|||
|
world.EntityManager.AddComponent<EnabledTag>(entity);
|
|||
|
world.EntityManager.AddComponent<EvaluationComponent>(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<EnabledTag>(entity, false);
|
|||
|
world.EntityManager.SetComponentEnabled<EvaluationComponent>(entity, false);
|
|||
|
}
|
|||
|
|
|||
|
// Get the required parent system groups. The systems are always added to the default world.
|
|||
|
var traversalTaskSystemGroup = world.GetOrCreateSystemManaged<TraversalTaskSystemGroup>();
|
|||
|
var reevaluateTaskSystemGroup = world.GetOrCreateSystemManaged<ReevaluateTaskSystemGroup>();
|
|||
|
var interruptTaskSystemGroup = world.GetOrCreateSystemManaged<InterruptTaskSystemGroup>();
|
|||
|
|
|||
|
var taskComponents = world.EntityManager.GetBuffer<TaskComponent>(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<TaskComponent>(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<BranchComponent>(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<TaskComponent>(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<TaskComponent>(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<ReevaluateTaskComponent>(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();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adjusts the index by the specified offset.
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">The index that should be adjusted.</param>
|
|||
|
/// <param name="offset">The offset that should be adjusted by.</param>
|
|||
|
/// <returns>THe index by the specified offset.</returns>
|
|||
|
private ushort AdjustByIndexOffset(ushort index, ushort offset)
|
|||
|
{
|
|||
|
if (index == ushort.MaxValue) { return index; }
|
|||
|
return (ushort)(index - offset);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stars the branch with the specified event task type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="eventTaskType">The branch that should be started.</param>
|
|||
|
/// <returns>True if the branch was started.</returns>
|
|||
|
public bool StartBranch(Type eventTaskType)
|
|||
|
{
|
|||
|
if (m_World == null || m_Entity == Entity.Null) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return StartBranch(m_World, m_Entity, eventTaskType);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stars the branch with the specified event task type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the branch.</param>
|
|||
|
/// <param name="eventTaskType">The branch that should be started.</param>
|
|||
|
/// <returns>True if the branch was started.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the branch with the specified event task.
|
|||
|
/// </summary>
|
|||
|
/// <param name="eventTask">The branch that should be started.</param>
|
|||
|
/// <returns>True if the branch was started.</returns>
|
|||
|
public bool StartBranch(IEventNode eventTask)
|
|||
|
{
|
|||
|
if (m_World == null || m_Entity == Entity.Null) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return StartBranch(m_World, m_Entity, eventTask);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the branch with the specified event task.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the branch.</param>
|
|||
|
/// <param name="eventTask">The branch that should be started.</param>
|
|||
|
/// <returns>True if the branch was started.</returns>
|
|||
|
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<BranchComponent>(entity)) {
|
|||
|
InitializeTree();
|
|||
|
}
|
|||
|
|
|||
|
var connectedIndex = m_Data.LogicNodes[eventTask.ConnectedIndex].RuntimeIndex;
|
|||
|
return StartBranch(world, entity, connectedIndex, m_UpdateMode == UpdateMode.EveryFrame);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the branch. This method is static allowing for alread-baked entities to be able to start the branch.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the branch.</param>
|
|||
|
/// <param name="connectedIndex">The index of the starting task.</param>
|
|||
|
/// <param name="startEvaluation">Should the branch start evaluation? If false the tree will manually need to be ticked.</param>
|
|||
|
/// <returns>True if the branch was started.</returns>
|
|||
|
internal static bool StartBranch(World world, Entity entity, ushort connectedIndex, bool startEvaluation)
|
|||
|
{
|
|||
|
var taskComponents = world.EntityManager.GetBuffer<TaskComponent>(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<BehaviorTreeSystemGroup>();
|
|||
|
if (systemGroup == null) {
|
|||
|
systemGroup = World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged<BehaviorTreeSystemGroup>();
|
|||
|
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<BranchComponent>(entity);
|
|||
|
var branchComponent = branchComponents[branchIndex];
|
|||
|
branchComponent.ActiveIndex = branchComponent.NextIndex = connectedIndex;
|
|||
|
branchComponent.ActiveTagComponentType = activeTag;
|
|||
|
branchComponents[branchIndex] = branchComponent;
|
|||
|
|
|||
|
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent>(entity);
|
|||
|
evaluateComponent.EvaluationCount = 0;
|
|||
|
world.EntityManager.SetComponentData(entity, evaluateComponent);
|
|||
|
|
|||
|
if (startEvaluation) {
|
|||
|
world.EntityManager.SetComponentEnabled<EnabledTag>(entity, true);
|
|||
|
world.EntityManager.SetComponentEnabled<EvaluationComponent>(entity, true);
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the task at the specified index.
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">The index of the task.</param>
|
|||
|
/// <returns>The task at the specified index.</returns>
|
|||
|
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];
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Finds the task with the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The first task found with the specified type (can be null).</returns>
|
|||
|
public T FindTask<T>() 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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Finds the tasks with the specified type. This method does not have any allocations.
|
|||
|
/// </summary>
|
|||
|
/// <param name="foundTasks">A pre-initialized array that will contain the found tasks.</param>
|
|||
|
/// <returns>The number of tasks found with the specified type.</returns>
|
|||
|
public int FindTasks<T>(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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Finds the tasks with the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <returns>An array containing the found tasks.</returns>
|
|||
|
public T[] FindTasks<T>() 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<T>(foundTasks);
|
|||
|
if (foundTasks.Length != count) {
|
|||
|
Array.Resize<T>(ref foundTasks, count);
|
|||
|
}
|
|||
|
return foundTasks;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 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.
|
|||
|
/// </summary>
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 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.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the behavior tree.</param>
|
|||
|
public static void Tick(World world, Entity entity)
|
|||
|
{
|
|||
|
if (world == null || entity.Index == 0) {
|
|||
|
return;
|
|||
|
}
|
|||
|
world.EntityManager.SetComponentEnabled<EvaluationComponent>(entity, true);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reevaluates the SubtreeReferences by calling the EvaluateSubtrees method.
|
|||
|
/// </summary>
|
|||
|
public void ReevaluateSubtreeReferences()
|
|||
|
{
|
|||
|
if (!m_Data.ReevaluateSubtreeReferences(this, this, ClearTree)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Restart the tree.
|
|||
|
InitializeTree();
|
|||
|
if (enabled && m_GameObject.activeSelf) {
|
|||
|
StartBehavior();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops or pauses the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <param name="pause">Should the behavior tree be paused?</param>
|
|||
|
/// <returns>True if the behavior tree was stopped or paused.</returns>
|
|||
|
public bool StopBehavior(bool pause = false)
|
|||
|
{
|
|||
|
return StopBehavior(m_World, m_Entity, pause);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops or pauses the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the behavior tree.</param>
|
|||
|
/// <param name="pause">Should the behavior tree be paused?</param>
|
|||
|
/// <returns>True if the behavior tree was stopped or paused.</returns>
|
|||
|
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<EnabledTag>(entity)) {
|
|||
|
world.EntityManager.SetComponentEnabled<EnabledTag>(entity, false);
|
|||
|
}
|
|||
|
world.EntityManager.SetComponentEnabled<EvaluationComponent>(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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops the behavior tree. This method should only be called from an ECS system.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the behavior tree.</param>
|
|||
|
public static void StopBehavior(World world, Entity entity)
|
|||
|
{
|
|||
|
if (world == null || entity.Index == 0) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var branchComponents = world.EntityManager.GetBuffer<BranchComponent>(entity);
|
|||
|
var taskComponents = world.EntityManager.GetBuffer<TaskComponent>(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<ReevaluateTaskComponent>(entity)) {
|
|||
|
var reevaluateTaskComponents = world.EntityManager.GetBuffer<ReevaluateTaskComponent>(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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Restarts the behavior tree.
|
|||
|
/// </summary>
|
|||
|
/// <returns>True if the behavior tree was restarted.</returns>
|
|||
|
public bool RestartBehavior()
|
|||
|
{
|
|||
|
if (!IsActive()) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (!StopBehavior()) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
return StartBehavior();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clears all of the tree components.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the behavior tree.</param>
|
|||
|
private void ClearTree()
|
|||
|
{
|
|||
|
if (m_Entity == Entity.Null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
ClearTree(m_World, m_Entity);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clears all of the tree components.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the entity exists in.</param>
|
|||
|
/// <param name="entity">The entity that contains the behavior tree.</param>
|
|||
|
private void ClearTree(World world, Entity entity)
|
|||
|
{
|
|||
|
if (Data.LogicNodes == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
StopBehavior(world, entity, false);
|
|||
|
|
|||
|
world.EntityManager.RemoveComponent<EnabledTag>(entity);
|
|||
|
world.EntityManager.RemoveComponent<EvaluationComponent>(entity);
|
|||
|
|
|||
|
var branchComponents = world.EntityManager.GetBuffer<BranchComponent>(entity);
|
|||
|
var taskComponents = world.EntityManager.GetBuffer<TaskComponent>(entity);
|
|||
|
branchComponents.Clear();
|
|||
|
taskComponents.Clear();
|
|||
|
if (world.EntityManager.HasBuffer<ReevaluateTaskComponent>(entity)) {
|
|||
|
var reevaluateTaskComponents = world.EntityManager.GetBuffer<ReevaluateTaskComponent>(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();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the SharedVariable with the specified name.
|
|||
|
/// </summary>
|
|||
|
/// <param name="name">The name of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <returns>The SharedVariable with the specified name (can be null).</returns>
|
|||
|
public SharedVariable GetVariable(string name)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
return m_Data.GetVariable(this, name, SharedVariable.SharingScope.Graph);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the SharedVariable with the specified name and scope.
|
|||
|
/// </summary>
|
|||
|
/// <param name="name">The name of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <param name="scope">The scope of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <returns>The SharedVariable with the specified name (can be null).</returns>
|
|||
|
public SharedVariable GetVariable(string name, SharedVariable.SharingScope scope)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
return m_Data.GetVariable(this, name, scope);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the SharedVariable of the specified name.
|
|||
|
/// </summary>
|
|||
|
/// <param name="name">The name of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <returns>The SharedVariable with the specified name (can be null).</returns>
|
|||
|
public SharedVariable<T> GetVariable<T>(string name)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
return m_Data.GetVariable<T>(this, name, SharedVariable.SharingScope.Graph);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the SharedVariable of the specified name and scope.
|
|||
|
/// </summary>
|
|||
|
/// <param name="name">The name of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <param name="scope">The scope of the SharedVariable that should be retrieved.</param>
|
|||
|
/// <returns>The SharedVariable with the specified name (can be null).</returns>
|
|||
|
public SharedVariable<T> GetVariable<T>(string name, SharedVariable.SharingScope scope)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
return m_Data.GetVariable<T>(this, name, scope);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the value of the SharedVariable.
|
|||
|
/// </summary>
|
|||
|
/// <typeparam name="T">The type of SharedVarible.</typeparam>
|
|||
|
/// <param name="name">The name of the SharedVariable.</param>
|
|||
|
/// <param name="value">The value of the SharedVariable.</param>
|
|||
|
public void SetVariableValue<T>(string name, T value)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
m_Data.SetVariableValue<T>(this, name, value, SharedVariable.SharingScope.Graph);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the value of the SharedVariable.
|
|||
|
/// </summary>
|
|||
|
/// <typeparam name="T">The type of SharedVarible.</typeparam>
|
|||
|
/// <param name="name">The name of the SharedVariable.</param>
|
|||
|
/// <param name="value">The value of the SharedVariable.</param>
|
|||
|
/// <param name="scope">The scope of the SharedVariable that should be set.</typeparam>
|
|||
|
public void SetVariableValue<T>(string name, T value, SharedVariable.SharingScope scope)
|
|||
|
{
|
|||
|
Deserialize();
|
|||
|
|
|||
|
m_Data.SetVariableValue<T>(this, name, value, scope);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the behavior tree save data.
|
|||
|
/// </summary>
|
|||
|
/// <param name="variableSaveScope">Specifies which variables should be saved. Graph variables will automatically be saved.</param>
|
|||
|
/// <returns>The save data if the behavior tree can be saved.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Saves the behavior tree at the specified file path.
|
|||
|
/// </summary>
|
|||
|
/// <param name="filePath">The file path to save the behavior tree at. The file will be replaced if it already exists.</param>
|
|||
|
/// <param name="variableSaveScope">Specifies which variables should be saved. Graph variables will automatically be saved.</param>
|
|||
|
/// <returns>True if the behavior tree was successfully saved.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Loads the behavior tree from the specified file path.
|
|||
|
/// </summary>
|
|||
|
/// <param name="filePath">The file path to load the behavior tree at.</param>
|
|||
|
/// <param name="afterVariablesRestored">Optional callback after the graph variables have been restored.</param>
|
|||
|
/// <returns>True if the behavior tree was successfully loaded.</returns>
|
|||
|
public bool Load(string filePath, Action<BehaviorTree> afterVariablesRestored = null)
|
|||
|
{
|
|||
|
if (OnWillLoad != null) {
|
|||
|
OnWillLoad(this);
|
|||
|
}
|
|||
|
var success = SaveManager.Load(new BehaviorTree[] { this }, filePath, afterVariablesRestored);
|
|||
|
if (OnDidLoad != null) {
|
|||
|
OnDidLoad(this, success);
|
|||
|
}
|
|||
|
return success;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Loads the behavior tree from the specified save data.
|
|||
|
/// </summary>
|
|||
|
/// <param name="saveData">The data associated with the behavior tree.</param>
|
|||
|
/// <param name="afterVariablesRestored">Optional callback after the graph variables have been restored.</param>
|
|||
|
/// <returns>True if the behavior tree was successfully loaded.</returns>
|
|||
|
public bool Load(SaveManager.SaveData saveData, Action<BehaviorTree> afterVariablesRestored = null)
|
|||
|
{
|
|||
|
if (OnWillLoad != null) {
|
|||
|
OnWillLoad(this);
|
|||
|
}
|
|||
|
var success = SaveManager.Load(new BehaviorTree[] { this }, saveData, afterVariablesRestored);
|
|||
|
if (OnDidLoad != null) {
|
|||
|
OnDidLoad(this, success);
|
|||
|
}
|
|||
|
return success;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the task courtine with the specified name.
|
|||
|
/// </summary>
|
|||
|
/// <param name="task">The task that the coroutine belongs to.</param>
|
|||
|
/// <param name="coroutineName">The name of the coroutine method.</param>
|
|||
|
/// <returns>The created routine (can be null).</returns>
|
|||
|
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<string, List<TaskCoroutine>>();
|
|||
|
}
|
|||
|
var taskCoroutine = new TaskCoroutine(this, (IEnumerator)method.Invoke(task, new object[] { }), coroutineName);
|
|||
|
if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) {
|
|||
|
taskCoroutines = new List<TaskCoroutine>();
|
|||
|
m_ActiveTaskCoroutines.Add(coroutineName, taskCoroutines);
|
|||
|
}
|
|||
|
taskCoroutines.Add(taskCoroutine);
|
|||
|
|
|||
|
return taskCoroutine.Coroutine;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the task courtine with the specified name.
|
|||
|
/// </summary>
|
|||
|
/// <param name="task">The task that the coroutine belongs to.</param>
|
|||
|
/// <param name="coroutineName">The name of the coroutine method.</param>
|
|||
|
/// <param name="value">The input parameter to the coroutine.</param>
|
|||
|
/// <returns>The created routine (can be null).</returns>
|
|||
|
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<string, List<TaskCoroutine>>();
|
|||
|
}
|
|||
|
var taskCoroutine = new TaskCoroutine(this, (IEnumerator)method.Invoke(task, new object[] { value }), coroutineName);
|
|||
|
if (!m_ActiveTaskCoroutines.TryGetValue(coroutineName, out var taskCoroutines)) {
|
|||
|
taskCoroutines = new List<TaskCoroutine>();
|
|||
|
m_ActiveTaskCoroutines.Add(coroutineName, taskCoroutines);
|
|||
|
}
|
|||
|
taskCoroutines.Add(taskCoroutine);
|
|||
|
|
|||
|
return taskCoroutine.Coroutine;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops the task courtine with the specified name.
|
|||
|
/// </summary>
|
|||
|
/// <param name="coroutineName">The name of the coroutine method.</param>
|
|||
|
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();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops all of the task coroutines.
|
|||
|
/// </summary>
|
|||
|
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();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The TaskCoroutine has ended.
|
|||
|
/// </summary>
|
|||
|
/// <param name="taskCoroutine">The coroutine that has ended.</param>
|
|||
|
/// <param name="coroutineName">The name of the coroutine.</param>
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnCollisionEnter callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collision">The resulting collision.</param>
|
|||
|
private void OnCollisionEnter(Collision collision)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeCollisionEnter != null) {
|
|||
|
OnBehaviorTreeCollisionEnter(collision);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnCollisionExit callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collision">The resulting collision.</param>
|
|||
|
private void OnCollisionExit(Collision collision)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeCollisionExit != null) {
|
|||
|
OnBehaviorTreeCollisionExit(collision);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnCollisionEnter2D callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collision">The resulting collision.</param>
|
|||
|
private void OnCollisionEnter2D(Collision2D collision)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeCollisionEnter2D != null) {
|
|||
|
OnBehaviorTreeCollisionEnter2D(collision);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnCollisionExit2D callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="collision">The resulting collision.</param>
|
|||
|
private void OnCollisionExit2D(Collision2D collision)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeCollisionExit2D != null) {
|
|||
|
OnBehaviorTreeCollisionExit2D(collision);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnTriggerEnter callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="other">The overlapping collider.</param>
|
|||
|
private void OnTriggerEnter(Collider other)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeTriggerEnter != null) {
|
|||
|
OnBehaviorTreeTriggerEnter(other);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnTriggerExit callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="other">The collider that is no longer overlapping.</param>
|
|||
|
private void OnTriggerExit(Collider other)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeTriggerExit != null) {
|
|||
|
OnBehaviorTreeTriggerExit(other);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnTriggerEnter2D callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="other">The overlapping collider.</param>
|
|||
|
private void OnTriggerEnter2D(Collider2D other)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeTriggerEnter2D != null) {
|
|||
|
OnBehaviorTreeTriggerEnter2D(other);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnTriggerExit2D callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="other">The collider that is no longer overlapping.</param>
|
|||
|
private void OnTriggerExit2D(Collider2D other)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeTriggerExit2D != null) {
|
|||
|
OnBehaviorTreeTriggerExit2D(other);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnControllerColliderHit callback.
|
|||
|
/// </summary>
|
|||
|
/// <param name="hit">The hit result.</param>
|
|||
|
private void OnControllerColliderHit(ControllerColliderHit hit)
|
|||
|
{
|
|||
|
if (OnBehaviorTreeControllerColliderHit != null) {
|
|||
|
OnBehaviorTreeControllerColliderHit(hit);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#if UNITY_EDITOR
|
|||
|
/// <summary>
|
|||
|
/// OnDrawGizmos callback.
|
|||
|
/// </summary>
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// OnDrawGizmos callback.
|
|||
|
/// </summary>
|
|||
|
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
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The behavior tree has been disabled.
|
|||
|
/// </summary>
|
|||
|
private void OnDisable()
|
|||
|
{
|
|||
|
if (m_Entity == Entity.Null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
StopBehavior(m_World, m_Entity, m_PauseWhenDisabled);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// The behavior tree has been destroyed.
|
|||
|
/// </summary>
|
|||
|
private void OnDestroy()
|
|||
|
{
|
|||
|
if (m_Entity == Entity.Null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (OnBehaviorTreeDestroyed != null) {
|
|||
|
OnBehaviorTreeDestroyed();
|
|||
|
}
|
|||
|
StopBehavior(m_World, m_Entity, false);
|
|||
|
m_GameObject = null;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is the node with the specified index enabled?
|
|||
|
/// </summary>
|
|||
|
/// <param name="logicNode">Is the node a LogicNode?</param>
|
|||
|
/// <param name="index">The index of the node.</param>
|
|||
|
/// <returns>True if the node with the specified index is enabled.</returns>
|
|||
|
public bool IsNodeEnabled(bool logicNode, int index)
|
|||
|
{
|
|||
|
return Data.IsNodeEnabled(logicNode, index);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Is the node with the specified index active?
|
|||
|
/// </summary>
|
|||
|
/// <param name="logicNode">Is the node a LogicNode?</param>
|
|||
|
/// <param name="index">The index of the node.</param>
|
|||
|
/// <returns>True if the node with the specified index is active.</returns>
|
|||
|
public bool IsNodeActive(bool logicNode, int index)
|
|||
|
{
|
|||
|
if (!Application.isPlaying || m_Entity == Entity.Null) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
var taskComponents = m_World.EntityManager.GetBuffer<TaskComponent>(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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns true if the behavior tree is active.
|
|||
|
/// </summary>
|
|||
|
/// <returns>True if the behavior tree is active.</returns>
|
|||
|
public bool IsActive()
|
|||
|
{
|
|||
|
if (m_Entity == Entity.Null) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
return s_BehaviorTreeByEntity.ContainsKey(m_Entity);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Copies the graph onto the current graph.
|
|||
|
/// </summary>
|
|||
|
/// <param name="other">The graph that should be copied.</param>
|
|||
|
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();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Overrides ToString.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The desired string value.</returns>
|
|||
|
public override string ToString()
|
|||
|
{
|
|||
|
return $"{m_GraphName} (Index {m_Index})";
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Callback when the domain should be reloaded.
|
|||
|
/// </summary>
|
|||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|||
|
private static void Reinitialize()
|
|||
|
{
|
|||
|
s_BehaviorTreeByEntity = new Dictionary<Entity, BehaviorTree>();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Enables the baked behavior tree system.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the system has been added to.</param>
|
|||
|
public static void EnableBakedBehaviorTreeSystem(World world)
|
|||
|
{
|
|||
|
if (world == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
EnableBakedBehaviorTreeSystem(world.Unmanaged);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Enables the baked behavior tree system.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The unmanged world that the system has been added to.</param>
|
|||
|
public static void EnableBakedBehaviorTreeSystem(WorldUnmanaged world)
|
|||
|
{
|
|||
|
world.GetExistingSystemState<StartBakedBehaviorTreeSystem>().Enabled = true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Converts the behavior tree to a DOTS entity.
|
|||
|
/// </summary>
|
|||
|
public class BehaviorTreeBaker : Baker<BehaviorTree>
|
|||
|
{
|
|||
|
private static MethodInfo s_GetTypeOfSystemMethod;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Bakes the behavior tree to the DOTS entity.
|
|||
|
/// </summary>
|
|||
|
/// <param name="behaviorTree">The authoring behavior tree.</param>
|
|||
|
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<TaskComponent>(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<ReevaluateTaskComponent>(entity)) {
|
|||
|
var reevaluateTaskComponents = worlds[i].EntityManager.GetBuffer<ReevaluateTaskComponent>(entity);
|
|||
|
reevaluateTagStableTypeHash = new ulong[reevaluateTaskComponents.Length];
|
|||
|
for (int j = 0; j < reevaluateTaskComponents.Length; ++j) {
|
|||
|
reevaluateTagStableTypeHash[j] = TypeManager.GetTypeInfo(reevaluateTaskComponents[j].ReevaluateTagComponentType.TypeIndex).StableTypeHash;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
AddComponentObject<BakedBehaviorTree>(entity, new BakedBehaviorTree
|
|||
|
{
|
|||
|
StartEventConnectedIndex = connectedIndex,
|
|||
|
StartEvaluation = behaviorTree.UpdateMode == UpdateMode.EveryFrame,
|
|||
|
ReevaluateTaskSystems = GetTaskSystems<ReevaluateTaskSystemGroup>(worlds[i]),
|
|||
|
InterruptTaskSystems = GetTaskSystems<InterruptTaskSystemGroup>(worlds[i]),
|
|||
|
TraversalTaskSystems = GetTaskSystems<TraversalTaskSystemGroup>(worlds[i]),
|
|||
|
TagStableTypeHashes = tagStableTypeHash,
|
|||
|
ReevaluateTagStableTypeHashes = reevaluateTagStableTypeHash,
|
|||
|
});
|
|||
|
behaviorTree.Baked = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the index of the node connection for the start event task.
|
|||
|
/// </summary>
|
|||
|
/// <param name="behaviorTree">The interested behavior tree.</param>
|
|||
|
/// <returns>The index of the node connection for the start event task.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns all of the system type indicies within the systems of the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="world">The world that the systems were added to.</param>
|
|||
|
/// <returns>The system type indicies within the systems of the specified type (can be null).</returns>
|
|||
|
private string[] GetTaskSystems<T>(World world) where T : ComponentSystemGroup
|
|||
|
{
|
|||
|
var systemGroup = world.GetExistingSystemManaged<T>();
|
|||
|
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
|