#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Runtime.Components { using Opsive.BehaviorDesigner.Runtime.Groups; using Opsive.BehaviorDesigner.Runtime.Systems; using Opsive.BehaviorDesigner.Runtime.Utility; using Unity.Entities; using UnityEngine; /// /// Indicates that the behavior tree was baked. /// public class BakedBehaviorTree : IComponentData { [Tooltip("The index of the connected start task.")] public int StartEventConnectedIndex; [Tooltip("Should the behavior tree be started after it has been initialized?")] public bool StartWhenEnabled; [Tooltip("Should the behavior tree be started after it has been baked?")] public bool StartEvaluation; [Tooltip("The indicies of the reevaluate task systems.")] public string[] ReevaluateTaskSystems; [Tooltip("The indicies of the interrupt task systems.")] public string[] InterruptTaskSystems; [Tooltip("The indicies of the traversal task systems.")] public string[] TraversalTaskSystems; [Tooltip("The hashes that correspond to the TaskComponent's ComponentType.")] public ulong[] TagStableTypeHashes; [Tooltip("The hashes that correspond to the ReevaluateTaskComponent's ComponentType.")] public ulong[] ReevaluateFlagStableTypeHashes; } /// /// Baked editor-only metadata used to link a baked entity back to its authoring BehaviorTree for runtime debugging. /// Populated by BehaviorTreeBaker only in editor builds and stripped from entity scenes before they are packaged into a player build by StripEditorBehaviorTreeReferenceSystem. /// public class BakedEditorReference : IComponentData { [Tooltip("The GlobalObjectId string for the authoring BehaviorTree component.")] public string AuthoringBehaviorTreeGlobalObjectId; [Tooltip("The design-time graph unique ID.")] public int DesignGraphUniqueID; [Tooltip("Maps design-time logic node index to runtime task index.")] public ushort[] LogicNodeRuntimeIndices; } /// /// Stores the start data for a baked behavior tree that should be started manually. /// public struct DeferredBakedBehaviorTreeStart : IComponentData { [Tooltip("The index of the connected start task.")] public ushort StartEventConnectedIndex; [Tooltip("Should the behavior tree start evaluation after the branch has started?")] public bool StartEvaluation; } /// /// The behavior tree has been baked. Start the tree using the baked data. /// public partial struct StartBakedBehaviorTreeSystem : ISystem { /// /// Restricts when the system should run. /// /// The current SystemState. private void OnCreate(ref SystemState state) { state.RequireForUpdate(); } /// /// Starts the baked behavior tree. /// /// The current SystemState. private void OnUpdate(ref SystemState state) { // The components are baked, but systems are not baked. Create the required systems within the current world. var reevaluateTaskSystemGroup = state.World.GetOrCreateSystemManaged(); var interruptTaskSystemGroup = state.World.GetOrCreateSystemManaged(); var traversalTaskSystemGroup = state.World.GetOrCreateSystemManaged(); // Add the necessary cleanup systems. var behaviorTreeSystemGroup = state.World.GetOrCreateSystemManaged(); behaviorTreeSystemGroup.AddSystemToUpdateList(state.World.GetOrCreateSystem()); behaviorTreeSystemGroup.AddSystemToUpdateList(state.World.GetOrCreateSystem()); var canReevaluate = false; var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator); #if UNITY_EDITOR // Collected during the foreach and applied after ecb.Playback() because ECB.AddComponentObject only supports EntityQuery, not individual Entity. var editorGraphReferences = new System.Collections.Generic.List<(Entity entity, EditorBehaviorTreeGraphReference reference)>(); #endif foreach (var (bakedBehaviorTree, entity) in SystemAPI.Query().WithEntityAccess()) { AddSystems(state.World, reevaluateTaskSystemGroup, bakedBehaviorTree.ReevaluateTaskSystems); AddSystems(state.World, interruptTaskSystemGroup, bakedBehaviorTree.InterruptTaskSystems); AddSystems(state.World, traversalTaskSystemGroup, bakedBehaviorTree.TraversalTaskSystems); // ComponentTypes cannot be serialized. Convert the StableTypeHash to a ComponentType. var taskComponents = state.World.EntityManager.GetBuffer(entity); for (int i = 0; i < taskComponents.Length; ++i) { var taskComponent = taskComponents[i]; taskComponent.FlagComponentType = ComponentType.FromTypeIndex(TypeManager.GetTypeIndexFromStableTypeHash(bakedBehaviorTree.TagStableTypeHashes[i])); taskComponents[i] = taskComponent; } TraversalUtility.PopulateChildUpperIndices(ref taskComponents); if (state.World.EntityManager.HasBuffer(entity)) { var reevaluateComponents = state.World.EntityManager.GetBuffer(entity); canReevaluate = true; for (int i = 0; i < reevaluateComponents.Length; ++i) { var reevaluateComponent = reevaluateComponents[i]; reevaluateComponent.ReevaluateFlagComponentType = ComponentType.FromTypeIndex(TypeManager.GetTypeIndexFromStableTypeHash(bakedBehaviorTree.ReevaluateFlagStableTypeHashes[i])); reevaluateComponents[i] = reevaluateComponent; } } // All of the systems have been added. Start the behavior tree or defer the start. if (bakedBehaviorTree.StartWhenEnabled) { BehaviorTree.StartBranch(state.World, entity, (ushort)bakedBehaviorTree.StartEventConnectedIndex, bakedBehaviorTree.StartEvaluation); } else { ecb.AddComponent(entity, new DeferredBakedBehaviorTreeStart { StartEventConnectedIndex = (ushort)bakedBehaviorTree.StartEventConnectedIndex, StartEvaluation = bakedBehaviorTree.StartEvaluation }); } #if UNITY_EDITOR // BakedEditorReference is stripped from player build entity scenes by StripEditorBehaviorTreeReferenceSystem before serialization, so it only ever reaches // this system during editor play mode. if (state.EntityManager.HasComponent(entity)) { var editorRef = state.EntityManager.GetComponentObject(entity); if (!string.IsNullOrEmpty(editorRef.AuthoringBehaviorTreeGlobalObjectId)) { editorGraphReferences.Add((entity, new EditorBehaviorTreeGraphReference { AuthoringBehaviorTreeGlobalObjectId = editorRef.AuthoringBehaviorTreeGlobalObjectId, DesignGraphUniqueID = editorRef.DesignGraphUniqueID, LogicNodeRuntimeIndices = editorRef.LogicNodeRuntimeIndices, })); } ecb.RemoveComponent(entity); } #endif ecb.RemoveComponent(entity); } if (canReevaluate) { state.World.GetOrCreateSystemManaged().AddSystemToUpdateList(state.World.GetOrCreateSystem(typeof(ReevaluateSystem))); } reevaluateTaskSystemGroup.SortSystems(); interruptTaskSystemGroup.SortSystems(); traversalTaskSystemGroup.SortSystems(); ecb.Playback(state.EntityManager); ecb.Dispose(); #if UNITY_EDITOR foreach (var (entity, reference) in editorGraphReferences) { state.EntityManager.AddComponentObject(entity, reference); } #endif } /// /// Adds the systems indicated by the SystemTypeIndex to the specified group. /// /// The current World. /// The group that the systems should be added to. /// The types of systems that should be added. private void AddSystems(World world, ComponentSystemGroup group, string[] systemTypes) { if (systemTypes == null) { return; } for (int i = 0; i < systemTypes.Length; ++i) { group.AddSystemToUpdateList(world.GetOrCreateSystem(Shared.Utility.TypeUtility.GetType(systemTypes[i]))); } } } #if UNITY_EDITOR /// /// Strips BakedEditorReference from all entities during the entity scene optimization pass, which runs /// after baking but before the .entities file is serialized to disk for player builds. This ensures the /// type hash for BakedEditorReference never appears in a standalone build's entity scene, while the /// component remains available during editor play mode for StartBakedBehaviorTreeSystem to consume. /// [WorldSystemFilter(WorldSystemFilterFlags.EntitySceneOptimizations)] public partial class StripEditorBehaviorTreeReferenceSystem : SystemBase { protected override void OnUpdate() { var query = GetEntityQuery(ComponentType.ReadOnly()); EntityManager.RemoveComponent(query); } } #endif } #endif