重做杂兵
This commit is contained in:
@@ -20,7 +20,7 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
[Tooltip("Agent 正在追踪的目标对象。")]
|
||||
[SerializeField] protected SharedVariable<GameObject> m_Target;
|
||||
[Tooltip("指定提前预测目标的距离的程度。")]
|
||||
[SerializeField] protected SharedVariable<float> m_DistancePrediction = 20;
|
||||
[SerializeField] protected SharedVariable<float> m_DistancePrediction = 10;
|
||||
[Tooltip("预测提前距离的乘数。")]
|
||||
[SerializeField] protected SharedVariable<float> m_DistancePredictionMultiplier = 1;
|
||||
#endregion
|
||||
@@ -163,8 +163,8 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
base.Reset();
|
||||
m_StartSpeed = 10f;
|
||||
m_Target = null;
|
||||
m_DistancePrediction = 20;
|
||||
m_DistancePredictionMultiplier = 2; // 与声明一致
|
||||
m_DistancePrediction = 10;
|
||||
m_DistancePredictionMultiplier = 1;
|
||||
m_SlowDownDistance = 6f;
|
||||
m_MinSpeed = 0.5f;
|
||||
m_SpeedCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Opsive.BehaviorDesigner.AddOns.MovementPack.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Utility;
|
||||
using Opsive.GraphDesigner.Runtime;
|
||||
@@ -6,6 +7,7 @@ using Opsive.GraphDesigner.Runtime.Variables;
|
||||
using Opsive.Shared.Utility;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace Cielonos.MainGame.Characters.AI
|
||||
{
|
||||
@@ -14,8 +16,11 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
[Category("Cielonos/Movement")]
|
||||
public class WanderInRange : MovementBase
|
||||
{
|
||||
[Tooltip("初始移动速度。")]
|
||||
[SerializeField] protected SharedVariable<float> m_StartSpeed = 10f;
|
||||
|
||||
[Tooltip("以初始出生点为圆心的游走半径(单位:米)。")]
|
||||
[SerializeField] protected SharedVariable<float> m_WanderRadius = 8f;
|
||||
[SerializeField] protected SharedVariable<float> m_WanderRadius = 10f;
|
||||
|
||||
[Tooltip("到达目标点后等待的时间范围(秒)。Min 与 Max 均为 0 时不等待,直接返回 Success。")]
|
||||
[SerializeField] protected SharedVariable<RangeFloat> m_WaitAtDestinationDuration = new RangeFloat(0f, 0f);
|
||||
@@ -33,6 +38,8 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
|
||||
// 记录行为树初始化时的出生点位置
|
||||
private Vector3 m_SpawnPosition;
|
||||
private NavMeshAgentPathfinder _navPathfinder;
|
||||
private NavMeshAgent _agent;
|
||||
|
||||
// 到达后等待状态
|
||||
private float m_WaitDuration = -1f;
|
||||
@@ -47,6 +54,13 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
public override void OnAwake()
|
||||
{
|
||||
base.OnAwake();
|
||||
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
|
||||
if (_navPathfinder == null)
|
||||
{
|
||||
Debug.LogError("[PrecisePursue] Requires NavMeshAgentPathfinder.");
|
||||
return;
|
||||
}
|
||||
_agent = _navPathfinder.m_NavMeshAgent;
|
||||
m_SpawnPosition = transform.position;
|
||||
}
|
||||
|
||||
@@ -75,6 +89,8 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
_agent.speed = m_StartSpeed.Value;
|
||||
|
||||
// 到达判定:HasArrived() 或剩余距离小于阈值
|
||||
if (HasArrived() || RemainingDistance < m_ArrivalDistance.Value)
|
||||
{
|
||||
@@ -94,9 +110,9 @@ namespace Cielonos.MainGame.Characters.AI
|
||||
|
||||
// 等待中:检查是否已等待足够时间
|
||||
if (Time.time >= m_DestinationReachedTime + m_WaitDuration)
|
||||
{
|
||||
return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
return TaskStatus.Running;
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Components;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Utility;
|
||||
using Opsive.GraphDesigner.Runtime;
|
||||
using Opsive.GraphDesigner.Runtime.Variables.ECS;
|
||||
using Opsive.Shared.Utility;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Characters.AI
|
||||
{
|
||||
[Description("可配置成功/失败策略的高级并行节点。\n" +
|
||||
"OneSuccess:任一子节点 Success 即整体成功并中止其余分支。\n" +
|
||||
"AllFailure:单个失败不中止其余分支,全部失败才整体失败。")]
|
||||
[NodeIcon("f612c025389b22640b1b6df88f4502e7", "8a4a401bcfb527a48a08351efaf92e14")]
|
||||
[Category("Cielonos")]
|
||||
public class AdvancedParallel : ECSCompositeTask<AdvancedParallelTaskSystem, AdvancedParallelComponent, AdvancedParallelFlag>, IParentNode, IParallelNode
|
||||
{
|
||||
[Tooltip("OneSuccess:任一子节点成功即整体成功。AllSuccess:全部子节点成功才整体成功。")]
|
||||
[SerializeField] private SuccessPolicy m_SuccessPolicy = SuccessPolicy.AllSuccess;
|
||||
|
||||
[Tooltip("OneFailure:任一子节点失败即整体失败。AllFailure:所有子节点失败才整体失败,单个失败不中止其它分支。")]
|
||||
[SerializeField] private FailurePolicy m_FailurePolicy = FailurePolicy.AllFailure;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the IBufferElementData and interrupt components to the entity.
|
||||
/// </summary>
|
||||
public override int AddBufferElement(World world, Entity entity, ECSVariableRegistry registry, GameObject gameObject)
|
||||
{
|
||||
var index = base.AddBufferElement(world, entity, registry, gameObject);
|
||||
ComponentUtility.AddInterruptComponents(world.EntityManager, entity);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Passes task configuration into the ECS buffer element at tree initialization.
|
||||
/// </summary>
|
||||
public override AdvancedParallelComponent GetBufferElement()
|
||||
{
|
||||
return new AdvancedParallelComponent
|
||||
{
|
||||
Index = RuntimeIndex,
|
||||
OneSuccessPolicy = m_SuccessPolicy == SuccessPolicy.OneSuccess,
|
||||
AllFailurePolicy = m_FailurePolicy == FailurePolicy.AllFailure
|
||||
};
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
m_SuccessPolicy = SuccessPolicy.OneSuccess;
|
||||
m_FailurePolicy = FailurePolicy.AllFailure;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ECS buffer element storing the runtime index and policy flags for AdvancedParallel.
|
||||
/// </summary>
|
||||
public struct AdvancedParallelComponent : IBufferElementData
|
||||
{
|
||||
[SerializeField] private ushort m_Index;
|
||||
[SerializeField] private bool m_OneSuccessPolicy;
|
||||
[SerializeField] private bool m_AllFailurePolicy;
|
||||
|
||||
public ushort Index { get => m_Index; set => m_Index = value; }
|
||||
public bool OneSuccessPolicy { get => m_OneSuccessPolicy; set => m_OneSuccessPolicy = value; }
|
||||
public bool AllFailurePolicy { get => m_AllFailurePolicy; set => m_AllFailurePolicy = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ECS tag indicating an AdvancedParallel node is currently active.
|
||||
/// </summary>
|
||||
public struct AdvancedParallelFlag : IComponentData, IEnableableComponent { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the AdvancedParallel evaluation logic each frame via Burst-compiled ECS job.
|
||||
/// </summary>
|
||||
[DisableAutoCreation]
|
||||
public partial struct AdvancedParallelTaskSystem : ISystem
|
||||
{
|
||||
private EntityQuery m_Query;
|
||||
|
||||
private void OnCreate(ref SystemState state)
|
||||
{
|
||||
m_Query = SystemAPI.QueryBuilder()
|
||||
.WithAllRW<BranchComponent>()
|
||||
.WithAllRW<TaskComponent>()
|
||||
.WithAllRW<AdvancedParallelComponent>()
|
||||
.WithAll<AdvancedParallelFlag, EvaluateFlag>()
|
||||
.Build();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
|
||||
.CreateCommandBuffer(state.WorldUnmanaged);
|
||||
state.Dependency = new AdvancedParallelJob
|
||||
{
|
||||
EntityCommandBuffer = ecb.AsParallelWriter()
|
||||
}.ScheduleParallel(m_Query, state.Dependency);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private partial struct AdvancedParallelJob : IJobEntity
|
||||
{
|
||||
public EntityCommandBuffer.ParallelWriter EntityCommandBuffer;
|
||||
|
||||
[BurstCompile]
|
||||
public void Execute(
|
||||
Entity entity,
|
||||
[EntityIndexInQuery] int entityIndex,
|
||||
ref DynamicBuffer<AdvancedParallelComponent> parallelComponents,
|
||||
ref DynamicBuffer<TaskComponent> taskComponents,
|
||||
ref DynamicBuffer<BranchComponent> branchComponents)
|
||||
{
|
||||
for (int i = 0; i < parallelComponents.Length; ++i)
|
||||
{
|
||||
var parallelComponent = parallelComponents[i];
|
||||
var taskComponent = taskComponents[parallelComponent.Index];
|
||||
var taskStatus = taskComponent.Status;
|
||||
|
||||
if (taskStatus != TaskStatus.Queued && taskStatus != TaskStatus.Running)
|
||||
continue;
|
||||
|
||||
var branchComponent = branchComponents[taskComponent.BranchIndex];
|
||||
if (branchComponent.InterruptType != InterruptType.None || !branchComponent.CanExecute)
|
||||
continue;
|
||||
|
||||
ushort childIndex;
|
||||
TaskComponent childTaskComponent;
|
||||
|
||||
// ── Initialize all children on first activation ──────────────────────────
|
||||
if (taskStatus == TaskStatus.Queued)
|
||||
{
|
||||
taskComponent.Status = TaskStatus.Running;
|
||||
taskComponents[taskComponent.Index] = taskComponent;
|
||||
|
||||
childIndex = (ushort)(parallelComponent.Index + 1);
|
||||
while (childIndex != ushort.MaxValue)
|
||||
{
|
||||
childTaskComponent = taskComponents[childIndex];
|
||||
if (childTaskComponent.Status != TaskStatus.Queued)
|
||||
{
|
||||
childTaskComponent.Status = TaskStatus.Queued;
|
||||
taskComponents[childIndex] = childTaskComponent;
|
||||
}
|
||||
var initBranch = branchComponents[childTaskComponent.BranchIndex];
|
||||
if (initBranch.NextIndex != childTaskComponent.Index)
|
||||
{
|
||||
initBranch.NextIndex = childTaskComponent.Index;
|
||||
branchComponents[childTaskComponent.BranchIndex] = initBranch;
|
||||
}
|
||||
childIndex = childTaskComponent.SiblingIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Scan children and collect status flags ───────────────────────────────
|
||||
var anyRunning = false;
|
||||
var anySuccess = false;
|
||||
var anyFailure = false;
|
||||
|
||||
childIndex = (ushort)(parallelComponent.Index + 1);
|
||||
while (childIndex != ushort.MaxValue)
|
||||
{
|
||||
childTaskComponent = taskComponents[childIndex];
|
||||
|
||||
if (childTaskComponent.Status == TaskStatus.Queued ||
|
||||
childTaskComponent.Status == TaskStatus.Running)
|
||||
{
|
||||
anyRunning = true;
|
||||
}
|
||||
else if (childTaskComponent.Status == TaskStatus.Success)
|
||||
{
|
||||
anySuccess = true;
|
||||
// Mark this branch as done so it doesn't keep executing.
|
||||
var childBranch = branchComponents[childTaskComponent.BranchIndex];
|
||||
if (childBranch.ActiveIndex != ushort.MaxValue && childBranch.NextIndex != ushort.MaxValue)
|
||||
{
|
||||
childBranch.NextIndex = ushort.MaxValue;
|
||||
branchComponents[childTaskComponent.BranchIndex] = childBranch;
|
||||
}
|
||||
}
|
||||
else if (childTaskComponent.Status == TaskStatus.Failure)
|
||||
{
|
||||
anyFailure = true;
|
||||
// Stop this branch. Other branches continue when AllFailure policy is active.
|
||||
var childBranch = branchComponents[childTaskComponent.BranchIndex];
|
||||
if (childBranch.NextIndex != ushort.MaxValue)
|
||||
{
|
||||
childBranch.NextIndex = ushort.MaxValue;
|
||||
branchComponents[childTaskComponent.BranchIndex] = childBranch;
|
||||
}
|
||||
}
|
||||
|
||||
childIndex = childTaskComponent.SiblingIndex;
|
||||
}
|
||||
|
||||
// ── Apply success/failure policies ───────────────────────────────────────
|
||||
bool allTerminated = !anyRunning;
|
||||
|
||||
// Success condition
|
||||
bool shouldSucceed = parallelComponent.OneSuccessPolicy
|
||||
? anySuccess // OneSuccess: any child succeeded
|
||||
: allTerminated && !anyFailure; // AllSuccess: all done with no failures
|
||||
|
||||
// Failure condition (only evaluated when success hasn't triggered)
|
||||
bool shouldFail = false;
|
||||
if (!shouldSucceed)
|
||||
{
|
||||
shouldFail = !parallelComponent.AllFailurePolicy
|
||||
? anyFailure // OneFailure: any single failure → fail immediately
|
||||
: allTerminated; // AllFailure: fail only when nothing is still running
|
||||
}
|
||||
|
||||
if (!shouldSucceed && !shouldFail)
|
||||
continue;
|
||||
|
||||
// ── Stop all remaining running/queued children ───────────────────────────
|
||||
ushort maxChildIndex = taskComponent.ChildUpperIndex > taskComponent.Index
|
||||
? taskComponent.ChildUpperIndex
|
||||
: (ushort)taskComponent.Index;
|
||||
|
||||
var interruptedFlagSet = false;
|
||||
for (ushort j = (ushort)(taskComponent.Index + 1); j <= maxChildIndex; ++j)
|
||||
{
|
||||
childTaskComponent = taskComponents[j];
|
||||
if (childTaskComponent.Status == TaskStatus.Running ||
|
||||
childTaskComponent.Status == TaskStatus.Queued)
|
||||
{
|
||||
childTaskComponent.Status = TaskStatus.Failure;
|
||||
taskComponents[j] = childTaskComponent;
|
||||
|
||||
if (!interruptedFlagSet)
|
||||
{
|
||||
EntityCommandBuffer.SetComponentEnabled<InterruptedFlag>(entityIndex, entity, true);
|
||||
interruptedFlagSet = true;
|
||||
}
|
||||
|
||||
branchComponent = branchComponents[childTaskComponent.BranchIndex];
|
||||
if (branchComponent.ActiveIndex == childTaskComponent.Index &&
|
||||
branchComponent.NextIndex != ushort.MaxValue)
|
||||
{
|
||||
branchComponent.NextIndex = ushort.MaxValue;
|
||||
branchComponents[childTaskComponent.BranchIndex] = branchComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Propagate result to parent ────────────────────────────────────────────
|
||||
branchComponent = branchComponents[taskComponent.BranchIndex];
|
||||
if (branchComponent.NextIndex != taskComponent.ParentIndex)
|
||||
{
|
||||
branchComponent.NextIndex = taskComponent.ParentIndex;
|
||||
branchComponents[taskComponent.BranchIndex] = branchComponent;
|
||||
}
|
||||
|
||||
taskComponent.Status = shouldSucceed ? TaskStatus.Success : TaskStatus.Failure;
|
||||
taskComponents[taskComponent.Index] = taskComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb6e5b52a5f967844acb31d6f0bb7f1e
|
||||
@@ -0,0 +1,333 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Components;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Utility;
|
||||
using Opsive.GraphDesigner.Runtime;
|
||||
using Opsive.GraphDesigner.Runtime.Variables.ECS;
|
||||
using Opsive.Shared.Utility;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Cielonos.MainGame.Characters.AI
|
||||
{
|
||||
[Description("可配置成功/失败策略的高级顺序节点。\n" +
|
||||
"按顺序逐个执行子节点,根据策略决定整体结果。\n" +
|
||||
"OneSuccess:任一子节点 Success 即整体成功。\n" +
|
||||
"AllFailure:单个失败不中止后续子节点,全部失败才整体失败。")]
|
||||
[NodeIcon("f612c025389b22640b1b6df88f4502e7", "8a4a401bcfb527a48a08351efaf92e14")]
|
||||
[Category("Cielonos")]
|
||||
public class AdvancedSequence : ECSCompositeTask<AdvancedSequenceTaskSystem, AdvancedSequenceComponent, AdvancedSequenceFlag>,
|
||||
IParentNode, IConditionalAbortParent, IInterruptResponder, ISavableTask, ICloneable
|
||||
{
|
||||
[Tooltip("AllSuccess:全部子节点成功才整体成功。OneSuccess:任一子节点成功即整体成功。")]
|
||||
[SerializeField] private SuccessPolicy m_SuccessPolicy = SuccessPolicy.AllSuccess;
|
||||
|
||||
[Tooltip("OneFailure:任一子节点失败即整体失败。AllFailure:所有子节点失败才整体失败,单个失败不中止后续子节点。")]
|
||||
[SerializeField] private FailurePolicy m_FailurePolicy = FailurePolicy.OneFailure;
|
||||
|
||||
[Tooltip("指定子节点条件中断的重新评估方式。")]
|
||||
[SerializeField] private ConditionalAbortType m_AbortType;
|
||||
|
||||
private ushort m_ComponentIndex;
|
||||
|
||||
public ConditionalAbortType AbortType { get => m_AbortType; set => m_AbortType = value; }
|
||||
public Type InterruptSystemType => typeof(AdvancedSequenceInterruptSystem);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new buffer element for use by the ECS system.
|
||||
/// </summary>
|
||||
public override AdvancedSequenceComponent GetBufferElement()
|
||||
{
|
||||
return new AdvancedSequenceComponent
|
||||
{
|
||||
Index = RuntimeIndex,
|
||||
ActiveChildIndex = 0,
|
||||
OneSuccessPolicy = m_SuccessPolicy == SuccessPolicy.OneSuccess,
|
||||
AllFailurePolicy = m_FailurePolicy == FailurePolicy.AllFailure,
|
||||
AnyChildSucceeded = false,
|
||||
AnyChildFailed = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the IBufferElementData and interrupt components to the entity.
|
||||
/// </summary>
|
||||
public override int AddBufferElement(World world, Entity entity, ECSVariableRegistry registry, GameObject gameObject)
|
||||
{
|
||||
m_ComponentIndex = (ushort)base.AddBufferElement(world, entity, registry, gameObject);
|
||||
ComponentUtility.AddInterruptComponents(world.EntityManager, entity);
|
||||
return m_ComponentIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that no reflection-based save is needed.
|
||||
/// </summary>
|
||||
public MemberVisibility GetSaveReflectionType(int index) { return MemberVisibility.None; }
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current task state.
|
||||
/// </summary>
|
||||
public object Save(World world, Entity entity)
|
||||
{
|
||||
var components = world.EntityManager.GetBuffer<AdvancedSequenceComponent>(entity);
|
||||
var comp = components[m_ComponentIndex];
|
||||
return new object[] { comp.ActiveChildIndex, comp.AnyChildSucceeded, comp.AnyChildFailed };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the previous task state.
|
||||
/// </summary>
|
||||
public void Load(object saveData, World world, Entity entity)
|
||||
{
|
||||
var arr = (object[])saveData;
|
||||
var components = world.EntityManager.GetBuffer<AdvancedSequenceComponent>(entity);
|
||||
var comp = components[m_ComponentIndex];
|
||||
comp.ActiveChildIndex = (ushort)arr[0];
|
||||
comp.AnyChildSucceeded = (bool)arr[1];
|
||||
comp.AnyChildFailed = (bool)arr[2];
|
||||
components[m_ComponentIndex] = comp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the task.
|
||||
/// </summary>
|
||||
public object Clone()
|
||||
{
|
||||
var clone = Activator.CreateInstance<AdvancedSequence>();
|
||||
clone.Index = Index;
|
||||
clone.ParentIndex = ParentIndex;
|
||||
clone.SiblingIndex = SiblingIndex;
|
||||
clone.Enabled = Enabled;
|
||||
clone.AbortType = AbortType;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the task fields to their default values.
|
||||
/// </summary>
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
m_SuccessPolicy = SuccessPolicy.AllSuccess;
|
||||
m_FailurePolicy = FailurePolicy.OneFailure;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ECS buffer element storing the runtime state and policy flags for AdvancedSequence.
|
||||
/// </summary>
|
||||
public struct AdvancedSequenceComponent : IBufferElementData
|
||||
{
|
||||
[SerializeField] private ushort m_Index;
|
||||
[SerializeField] private ushort m_ActiveChildIndex;
|
||||
[SerializeField] private bool m_OneSuccessPolicy;
|
||||
[SerializeField] private bool m_AllFailurePolicy;
|
||||
[SerializeField] private bool m_AnyChildSucceeded;
|
||||
[SerializeField] private bool m_AnyChildFailed;
|
||||
|
||||
public ushort Index { get => m_Index; set => m_Index = value; }
|
||||
public ushort ActiveChildIndex { get => m_ActiveChildIndex; set => m_ActiveChildIndex = value; }
|
||||
public bool OneSuccessPolicy { get => m_OneSuccessPolicy; set => m_OneSuccessPolicy = value; }
|
||||
public bool AllFailurePolicy { get => m_AllFailurePolicy; set => m_AllFailurePolicy = value; }
|
||||
public bool AnyChildSucceeded { get => m_AnyChildSucceeded; set => m_AnyChildSucceeded = value; }
|
||||
public bool AnyChildFailed { get => m_AnyChildFailed; set => m_AnyChildFailed = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ECS tag indicating an AdvancedSequence node is currently active.
|
||||
/// </summary>
|
||||
public struct AdvancedSequenceFlag : IComponentData, IEnableableComponent { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the AdvancedSequence evaluation logic each frame via Burst-compiled ECS job.
|
||||
/// </summary>
|
||||
[DisableAutoCreation]
|
||||
public partial struct AdvancedSequenceTaskSystem : ISystem
|
||||
{
|
||||
private EntityQuery m_Query;
|
||||
|
||||
private void OnCreate(ref SystemState state)
|
||||
{
|
||||
m_Query = SystemAPI.QueryBuilder()
|
||||
.WithAllRW<BranchComponent>()
|
||||
.WithAllRW<TaskComponent>()
|
||||
.WithAllRW<AdvancedSequenceComponent>()
|
||||
.WithAll<AdvancedSequenceFlag, EvaluateFlag>()
|
||||
.Build();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private void OnUpdate(ref SystemState state)
|
||||
{
|
||||
state.Dependency = new AdvancedSequenceJob()
|
||||
.ScheduleParallel(m_Query, state.Dependency);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private partial struct AdvancedSequenceJob : IJobEntity
|
||||
{
|
||||
[BurstCompile]
|
||||
public void Execute(
|
||||
ref DynamicBuffer<BranchComponent> branchComponents,
|
||||
ref DynamicBuffer<TaskComponent> taskComponents,
|
||||
ref DynamicBuffer<AdvancedSequenceComponent> sequenceComponents)
|
||||
{
|
||||
for (int i = 0; i < sequenceComponents.Length; ++i)
|
||||
{
|
||||
var seqComp = sequenceComponents[i];
|
||||
var taskComp = taskComponents[seqComp.Index];
|
||||
var taskStatus = taskComp.Status;
|
||||
|
||||
if (taskStatus != TaskStatus.Queued && taskStatus != TaskStatus.Running)
|
||||
continue;
|
||||
|
||||
var branchComp = branchComponents[taskComp.BranchIndex];
|
||||
if (branchComp.InterruptType != InterruptType.None || !branchComp.CanExecute)
|
||||
continue;
|
||||
|
||||
// ── First activation: queue the first child ─────────────────────────
|
||||
if (taskStatus == TaskStatus.Queued)
|
||||
{
|
||||
taskComp.Status = TaskStatus.Running;
|
||||
taskComponents[taskComp.Index] = taskComp;
|
||||
|
||||
seqComp.ActiveChildIndex = (ushort)(taskComp.Index + 1);
|
||||
seqComp.AnyChildSucceeded = false;
|
||||
seqComp.AnyChildFailed = false;
|
||||
sequenceComponents[i] = seqComp;
|
||||
|
||||
branchComp.NextIndex = seqComp.ActiveChildIndex;
|
||||
branchComponents[taskComp.BranchIndex] = branchComp;
|
||||
|
||||
var firstChild = taskComponents[branchComp.NextIndex];
|
||||
if (firstChild.Status != TaskStatus.Queued)
|
||||
{
|
||||
firstChild.Status = TaskStatus.Queued;
|
||||
taskComponents[branchComp.NextIndex] = firstChild;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check the active child's status ─────────────────────────────────
|
||||
var childComp = taskComponents[seqComp.ActiveChildIndex];
|
||||
if (childComp.Status == TaskStatus.Queued || childComp.Status == TaskStatus.Running)
|
||||
continue; // Still running, wait.
|
||||
|
||||
// Treat Inactive (disabled) children as Success.
|
||||
var childSucceeded = childComp.Status == TaskStatus.Success || childComp.Status == TaskStatus.Inactive;
|
||||
var childFailed = childComp.Status == TaskStatus.Failure;
|
||||
|
||||
if (childSucceeded) seqComp.AnyChildSucceeded = true;
|
||||
if (childFailed) seqComp.AnyChildFailed = true;
|
||||
|
||||
// ── Early termination checks ────────────────────────────────────────
|
||||
|
||||
// OneSuccess: any child succeeding ends the sequence with Success.
|
||||
if (childSucceeded && seqComp.OneSuccessPolicy)
|
||||
{
|
||||
taskComp.Status = TaskStatus.Success;
|
||||
taskComponents[seqComp.Index] = taskComp;
|
||||
|
||||
branchComp.NextIndex = taskComp.ParentIndex;
|
||||
branchComponents[taskComp.BranchIndex] = branchComp;
|
||||
|
||||
sequenceComponents[i] = seqComp;
|
||||
continue;
|
||||
}
|
||||
|
||||
// OneFailure (default): any child failing ends the sequence with Failure.
|
||||
if (childFailed && !seqComp.AllFailurePolicy)
|
||||
{
|
||||
taskComp.Status = TaskStatus.Failure;
|
||||
taskComponents[seqComp.Index] = taskComp;
|
||||
|
||||
branchComp.NextIndex = taskComp.ParentIndex;
|
||||
branchComponents[taskComp.BranchIndex] = branchComp;
|
||||
|
||||
sequenceComponents[i] = seqComp;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Advance to next sibling or finalize ─────────────────────────────
|
||||
if (childComp.SiblingIndex == ushort.MaxValue)
|
||||
{
|
||||
// No more children. Determine final result based on policies.
|
||||
bool shouldSucceed = seqComp.OneSuccessPolicy
|
||||
? seqComp.AnyChildSucceeded
|
||||
: !seqComp.AnyChildFailed;
|
||||
|
||||
taskComp.Status = shouldSucceed ? TaskStatus.Success : TaskStatus.Failure;
|
||||
seqComp.ActiveChildIndex = (ushort)(seqComp.Index + 1);
|
||||
taskComponents[seqComp.Index] = taskComp;
|
||||
|
||||
branchComp.NextIndex = taskComp.ParentIndex;
|
||||
branchComponents[taskComp.BranchIndex] = branchComp;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move to the next sibling.
|
||||
var siblingComp = taskComponents[childComp.SiblingIndex];
|
||||
if (siblingComp.Status != TaskStatus.Queued)
|
||||
{
|
||||
siblingComp.Status = TaskStatus.Queued;
|
||||
taskComponents[childComp.SiblingIndex] = siblingComp;
|
||||
}
|
||||
|
||||
seqComp.ActiveChildIndex = childComp.SiblingIndex;
|
||||
|
||||
branchComp.NextIndex = seqComp.ActiveChildIndex;
|
||||
branchComponents[taskComp.BranchIndex] = branchComp;
|
||||
}
|
||||
|
||||
sequenceComponents[i] = seqComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles interrupt recovery for AdvancedSequence — ensures ActiveChildIndex
|
||||
/// points to the correct running child after a conditional abort.
|
||||
/// </summary>
|
||||
[DisableAutoCreation]
|
||||
public partial struct AdvancedSequenceInterruptSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
private void OnUpdate(ref SystemState state)
|
||||
{
|
||||
foreach (var (taskComponents, sequenceComponents) in
|
||||
SystemAPI.Query<DynamicBuffer<TaskComponent>, DynamicBuffer<AdvancedSequenceComponent>>()
|
||||
.WithAll<InterruptFlag>())
|
||||
{
|
||||
for (int i = 0; i < sequenceComponents.Length; ++i)
|
||||
{
|
||||
var seqComp = sequenceComponents[i];
|
||||
if (seqComp.ActiveChildIndex == ushort.MaxValue ||
|
||||
seqComp.ActiveChildIndex >= taskComponents.Length)
|
||||
continue;
|
||||
|
||||
if (taskComponents[seqComp.ActiveChildIndex].Status != TaskStatus.Running)
|
||||
{
|
||||
var childIndex = (ushort)(seqComp.Index + 1);
|
||||
while (childIndex != ushort.MaxValue &&
|
||||
childIndex < taskComponents.Length &&
|
||||
taskComponents[childIndex].Status != TaskStatus.Running)
|
||||
{
|
||||
childIndex = taskComponents[childIndex].SiblingIndex;
|
||||
}
|
||||
|
||||
if (childIndex != ushort.MaxValue && childIndex < taskComponents.Length)
|
||||
{
|
||||
seqComp.ActiveChildIndex = childIndex;
|
||||
}
|
||||
|
||||
var buffer = sequenceComponents;
|
||||
buffer[i] = seqComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5815aace5461ab4a8855a6dae763aa0
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Characters.AI
|
||||
{
|
||||
public enum SuccessPolicy
|
||||
{
|
||||
/// <summary> 所有子节点成功才整体成功。</summary>
|
||||
AllSuccess,
|
||||
|
||||
/// <summary> 任一子节点成功即整体成功,并中止其余分支。</summary>
|
||||
OneSuccess
|
||||
}
|
||||
|
||||
public enum FailurePolicy
|
||||
{
|
||||
/// <summary> 任一子节点失败即中止所有分支并整体失败。</summary>
|
||||
OneFailure,
|
||||
|
||||
/// <summary> 单个失败不中止其余分支,所有分支均失败后才整体失败。</summary>
|
||||
AllFailure
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cd54d881a842e04b892e6ffc669b2cb
|
||||
@@ -159,7 +159,7 @@ namespace Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding
|
||||
base.Reset();
|
||||
|
||||
m_UpdateRotation = true;
|
||||
m_StoppingDistance = 0.2f;
|
||||
m_StoppingDistance = 2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user