/// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Samples { using Opsive.BehaviorDesigner.Runtime.Components; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.GraphDesigner.Runtime.Variables; using Opsive.GraphDesigner.Runtime.Variables.ECS; using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; using UnityEngine; [Opsive.Shared.Utility.Description("Uses DOTS to rotate around the center. This task will always return a status of running.")] [Shared.Utility.Category("Behavior Designer Samples/DOTS")] public class RotateTowardsEntity : ECSActionTask { [Tooltip("The angular speed of the agent.")] [SerializeField] float m_AngularSpeed; [Tooltip("The entity that should be targeted.")] [SerializeField] [RequireShared] SharedVariable m_TargetEntity; private ECSSharedVariableIndex m_TargetEntityIndex; /// /// Registers the target SharedVariable and adds the buffer element to the entity. /// /// The world that the entity exists in. /// The entity that the IBufferElementData should be assigned to. /// The ECS variable registry for registering SharedVariable fields. /// The GameObject that the entity is attached to. /// The index of the element within the buffer. public override int AddBufferElement(World world, Entity entity, ECSVariableRegistry registry, GameObject gameObject) { m_TargetEntityIndex = new ECSSharedVariableIndex(registry.Register(m_TargetEntity)); return base.AddBufferElement(world, entity, registry, gameObject); } /// /// Returns a new TBufferElement for use by the system. /// /// A new TBufferElement for use by the system. public override RotateTowardsEntityComponent GetBufferElement() { return new RotateTowardsEntityComponent() { Index = RuntimeIndex, AngularSpeed = m_AngularSpeed, TargetEntityVariableIndex = m_TargetEntityIndex.Index, }; } /// /// Resets the task to its default values. /// public override void Reset() { m_AngularSpeed = 2; } } /// /// The DOTS data structure for the RotateTowardsEntity struct. /// public struct RotateTowardsEntityComponent : IBufferElementData { [Tooltip("The index of the node.")] public ushort Index; [Tooltip("The angular speed of the agent.")] public float AngularSpeed; [Tooltip("Buffer index into SharedVariableElement for the target entity.")] public int TargetEntityVariableIndex; } /// /// A DOTS flag indicating when a RotateTowardsEntity node is active. /// public struct RotateTowardsEntityFlag : IComponentData, IEnableableComponent { } /// /// Runs the RotateTowardsEntity logic. /// [DisableAutoCreation] public partial struct RotateTowardsEntityTaskSystem : ISystem { /// /// Updates the logic. /// /// The current state of the system. [BurstCompile] private void OnUpdate(ref SystemState state) { var deltaTime = SystemAPI.Time.DeltaTime; var localTransformLookup = SystemAPI.GetComponentLookup(true); foreach (var (branchComponents, localTransform, taskComponents, rotateTorwardsTargetComponents, sharedVariables) in SystemAPI.Query, RefRW, DynamicBuffer, DynamicBuffer, DynamicBuffer>().WithAll()) { for (int i = 0; i < rotateTorwardsTargetComponents.Length; ++i) { var rotateTowardsEntityComponent = rotateTorwardsTargetComponents[i]; var taskComponent = taskComponents[rotateTowardsEntityComponent.Index]; var branchComponent = branchComponents[taskComponent.BranchIndex]; if (!branchComponent.CanExecute) { continue; } if (taskComponent.Status == TaskStatus.Queued) { taskComponent.Status = TaskStatus.Running; var taskComponentBuffer = taskComponents; taskComponentBuffer[rotateTowardsEntityComponent.Index] = taskComponent; } if (taskComponent.Status != TaskStatus.Running) { continue; } var targetEntity = sharedVariables.Get(rotateTowardsEntityComponent.TargetEntityVariableIndex); if (targetEntity != Entity.Null && localTransformLookup.HasComponent(targetEntity)) { var targetTransform = localTransformLookup[targetEntity]; var target = quaternion.EulerXYZ(0, -GetAngle(targetTransform.Position), 0); localTransform.ValueRW.Rotation = RotateTowards(localTransform.ValueRO.Rotation, target, rotateTowardsEntityComponent.AngularSpeed * deltaTime); } } } } /// /// Returns the angle between the target point and the center. /// /// The target point. /// The angle between the target point and the center. This will be in the range of 0 - 2PI (radians). [BurstCompile] private float GetAngle(float3 target) { var n = 180 - (math.atan2(-target.x, -target.z)) * 180 / math.PI; return (n % 360) * math.TORADIANS; } /// /// Rotates the specified target. /// /// The original rotation. /// The target rotation. /// The maximum delta amount. /// The computed rotation. private quaternion RotateTowards(quaternion from, quaternion to, float maxDelta) { var angle = math.angle(from, to); if (angle <= 0) { return to; } return math.slerp(from, to, math.clamp(maxDelta / angle, 0, 1)); } } }