334 lines
15 KiB
C#
334 lines
15 KiB
C#
#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
|