Files
zeling_v2/Packages/com.opsive.behaviordesigner/Runtime/Tasks/Templates/ECSNodes.cs
2026-05-08 11:04:00 +08:00

541 lines
19 KiB
C#

#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime
{
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Opsive.Shared.Utility;
using System;
/// <summary>
/// Helper class for ECS node templates.
/// </summary>
public static class ECSNodeUtility
{
/// <summary>
/// Converts a name to camelCase format.
/// </summary>
/// <param name="name">The name to convert.</param>
/// <returns>The name in camelCase format.</returns>
public static string ToCamelCase(string name)
{
if (string.IsNullOrEmpty(name))
return name;
return char.ToLowerInvariant(name[0]) + name.Substring(1);
}
}
/// <summary>
/// Template for creating a custom action node.
/// </summary>
[Category("ECS")]
[DisplayName("Action")]
[Description("Create a new ECS action node.")]
public class ECSActionNode : INodeTemplate
{
public Type BaseType => typeof(IAction);
public bool IsLogicNode => true;
/// <summary>
/// Returns the script that should be used for the template file.
/// </summary>
/// <param name="name">The name of the node.</param>
/// <returns>The node script.</returns>
public string GetScript(string name)
{
var variableName = ECSNodeUtility.ToCamelCase(name);
return $@"using Opsive.BehaviorDesigner.Runtime.Components;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Unity.Entities;
using Unity.Burst;
using UnityEngine;
/// <summary>
/// A custom ECS action node.
/// </summary>
public class {name} : ECSActionTask<{name}TaskSystem, {name}Component>
{{
/// <summary>
/// The type of flag that should be enabled when the task is running.
/// </summary>
public override ComponentType Flag {{ get => typeof({name}Flag); }}
/// <summary>
/// Returns a new {name}Component for use by the system.
/// </summary>
/// <returns>A new {name}Component for use by the system.</returns>
public override {name}Component GetBufferElement()
{{
return new {name}Component() {{
Index = RuntimeIndex,
}};
}}
}}
/// <summary>
/// The DOTS data structure for the {name} class.
/// </summary>
public struct {name}Component : IBufferElementData
{{
[Tooltip(""The index of the node."")]
public ushort Index;
}}
/// <summary>
/// A DOTS flag indicating when a {name} node is active.
/// </summary>
public struct {name}Flag : IComponentData, IEnableableComponent {{ }}
/// <summary>
/// Runs the {name} logic.
/// </summary>
[DisableAutoCreation]
public partial struct {name}TaskSystem : ISystem
{{
/// <summary>
/// Creates the job.
/// </summary>
/// <param name=""state"">The current state of the system.</param>
[BurstCompile]
private void OnUpdate(ref SystemState state)
{{
var query = SystemAPI.QueryBuilder().WithAllRW<BranchComponent>().WithAllRW<TaskComponent>().WithAllRW<{name}Component>().WithAll<{name}Flag, EvaluateFlag>().Build();
state.Dependency = new {name}Job().ScheduleParallel(query, state.Dependency);
}}
/// <summary>
/// Job which executes the task logic.
/// </summary>
[BurstCompile]
private partial struct {name}Job : IJobEntity
{{
/// <summary>
/// Executes the {name} logic.
/// </summary>
/// <param name=""branchComponents"">An array of BranchComponents.</param>
/// <param name=""taskComponents"">An array of TaskComponents.</param>
/// <param name=""{variableName}Components"">An array of {name}Components.</param>
[BurstCompile]
public void Execute(ref DynamicBuffer<BranchComponent> branchComponents, ref DynamicBuffer<TaskComponent> taskComponents, ref DynamicBuffer<{name}Component> {variableName}Components)
{{
for (int i = 0; i < {variableName}Components.Length; ++i) {{
var {variableName}Component = {variableName}Components[i];
var taskComponent = taskComponents[{variableName}Component.Index];
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (!branchComponent.CanExecute) {{
continue;
}}
if (taskComponent.Status == TaskStatus.Queued) {{
taskComponent.Status = TaskStatus.Success;
taskComponents[{variableName}Component.Index] = taskComponent;
}}
}}
}}
}}
}}";
}
}
/// <summary>
/// Template for creating a custom composite node.
/// </summary>
[Category("ECS")]
[DisplayName("Composite")]
[Description("Create a new ECS composite node.")]
public class ECSCompositeNode : IParentNodeTemplate
{
public Type BaseType => typeof(IComposite);
public bool IsLogicNode => true;
/// <summary>
/// Returns the script that should be used for the template file.
/// </summary>
/// <param name="name">The name of the node.</param>
/// <returns>The node script.</returns>
public string GetScript(string name)
{
var variableName = ECSNodeUtility.ToCamelCase(name);
return $@"using Opsive.BehaviorDesigner.Runtime.Components;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Unity.Entities;
using Unity.Burst;
using UnityEngine;
/// <summary>
/// A custom ECS composite node.
/// </summary>
public class {name} : ECSCompositeTask<{name}TaskSystem, {name}Component>
{{
/// <summary>
/// The type of tag that should be enabled when the task is running.
/// </summary>
public override ComponentType Flag {{ get => typeof({name}Flag); }}
/// <summary>
/// Returns a new {name}Component for use by the system.
/// </summary>
/// <returns>A new {name}Component for use by the system.</returns>
public override {name}Component GetBufferElement()
{{
return new {name}Component() {{
Index = RuntimeIndex,
}};
}}
}}
/// <summary>
/// The DOTS data structure for the {name} class.
/// </summary>
public struct {name}Component : IBufferElementData
{{
[Tooltip(""The index of the node."")]
public ushort Index;
[Tooltip(""The index of the child that is currently active."")]
public ushort ActiveChildIndex;
}}
/// <summary>
/// A DOTS flag indicating when a {name} node is active.
/// </summary>
public struct {name}Flag : IComponentData, IEnableableComponent {{ }}
/// <summary>
/// Runs the {name} logic.
/// </summary>
[DisableAutoCreation]
public partial struct {name}TaskSystem : ISystem
{{
/// <summary>
/// Creates the job.
/// </summary>
/// <param name=""state"">The current state of the system.</param>
[BurstCompile]
private void OnUpdate(ref SystemState state)
{{
var query = SystemAPI.QueryBuilder().WithAllRW<BranchComponent>().WithAllRW<TaskComponent>().WithAllRW<{name}Component>().WithAll<{name}Flag, EvaluateFlag>().Build();
state.Dependency = new {name}Job().ScheduleParallel(query, state.Dependency);
}}
/// <summary>
/// Job which executes the task logic.
/// </summary>
[BurstCompile]
private partial struct {name}Job : IJobEntity
{{
/// <summary>
/// Executes the {name} logic.
/// </summary>
/// <param name=""branchComponents"">An array of BranchComponents.</param>
/// <param name=""taskComponents"">An array of TaskComponents.</param>
/// <param name=""{variableName}Components"">An array of {name}Components.</param>
[BurstCompile]
public void Execute(ref DynamicBuffer<BranchComponent> branchComponents, ref DynamicBuffer<TaskComponent> taskComponents, ref DynamicBuffer<{name}Component> {variableName}Components)
{{
for (int i = 0; i < {variableName}Components.Length; ++i) {{
var {variableName}Component = {variableName}Components[i];
var taskComponent = taskComponents[{variableName}Component.Index];
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (!branchComponent.CanExecute) {{
continue;
}}
if (taskComponent.Status == TaskStatus.Queued) {{
taskComponent.Status = TaskStatus.Success;
taskComponents[{variableName}Component.Index] = taskComponent;
branchComponent.NextIndex = taskComponent.ParentIndex;
branchComponents[taskComponent.BranchIndex] = branchComponent;
}}
}}
}}
}}
}}";
}
}
/// <summary>
/// Template for creating a custom conditional node.
/// </summary>
[Category("ECS")]
[DisplayName("Conditional")]
[Description("Create a new ECS conditional node.")]
public class ECSConditionalNode : INodeTemplate
{
public Type BaseType => typeof(IConditional);
public bool IsLogicNode => true;
/// <summary>
/// Returns the script that should be used for the template file.
/// </summary>
/// <param name="name">The name of the node.</param>
/// <returns>The node script.</returns>
public string GetScript(string name)
{
var variableName = ECSNodeUtility.ToCamelCase(name);
return $@"using Opsive.BehaviorDesigner.Runtime.Components;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
/// <summary>
/// A custom ECS conditional node.
/// </summary>
public class {name} : ECSConditionalTask<{name}TaskSystem, {name}Component>, IReevaluateResponder
{{
/// <summary>
/// The type of flag that should be enabled when the task is running.
/// </summary>
public override ComponentType Flag {{ get => typeof({name}Flag); }}
/// <summary>
/// The type of flag that should be enabled when the task is being reevaluated.
/// </summary>
public ComponentType ReevaluateFlag {{ get => typeof({name}ReevaluateFlag); }}
/// <summary>
/// The system type that the reevaluation component uses.
/// </summary>
public System.Type ReevaluateSystemType {{ get => typeof({name}ReevaluateTaskSystem); }}
/// <summary>
/// Returns a new {name}Component for use by the system.
/// </summary>
/// <returns>A new {name}Component for use by the system.</returns>
public override {name}Component GetBufferElement()
{{
return new {name}Component() {{
Index = RuntimeIndex,
}};
}}
}}
/// <summary>
/// The DOTS data structure for the {name} class.
/// </summary>
public struct {name}Component : IBufferElementData
{{
[Tooltip(""The index of the node."")]
public ushort Index;
}}
/// <summary>
/// A DOTS flag indicating when a {name} node is active.
/// </summary>
public struct {name}Flag : IComponentData, IEnableableComponent {{ }}
/// <summary>
/// Runs the {name} logic.
/// </summary>
[DisableAutoCreation]
public partial struct {name}TaskSystem : ISystem
{{
/// <summary>
/// Creates the job.
/// </summary>
/// <param name=""state"">The current state of the system.</param>
[BurstCompile]
private void OnUpdate(ref SystemState state)
{{
var query = SystemAPI.QueryBuilder().WithAllRW<BranchComponent>().WithAllRW<TaskComponent>().WithAllRW<{name}Component>().WithAll<{name}Flag, EvaluateFlag>().Build();
state.Dependency = new {name}Job().ScheduleParallel(query, state.Dependency);
}}
/// <summary>
/// Job which executes the task logic.
/// </summary>
[BurstCompile]
private partial struct {name}Job : IJobEntity
{{
/// <summary>
/// Executes the {name} logic.
/// </summary>
/// <param name=""branchComponents"">An array of BranchComponents.</param>
/// <param name=""taskComponents"">An array of TaskComponents.</param>
/// <param name=""{variableName}Components"">An array of {name}Components.</param>
[BurstCompile]
public void Execute(ref DynamicBuffer<BranchComponent> branchComponents, ref DynamicBuffer<TaskComponent> taskComponents, ref DynamicBuffer<{name}Component> {variableName}Components)
{{
for (int i = 0; i < {variableName}Components.Length; ++i) {{
var {variableName}Component = {variableName}Components[i];
var taskComponent = taskComponents[{variableName}Component.Index];
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (!branchComponent.CanExecute) {{
continue;
}}
if (taskComponent.Status == TaskStatus.Queued || taskComponent.Status == TaskStatus.Running) {{ // Conditional aborts can set the status to Running.
taskComponent.Status = TaskStatus.Success;
taskComponents[{variableName}Component.Index] = taskComponent;
}}
}}
}}
}}
}}
/// <summary>
/// A DOTS flag indicating when an {name} node needs to be reevaluated.
/// </summary>
public struct {name}ReevaluateFlag : IComponentData, IEnableableComponent {{ }}
/// <summary>
/// Runs the {name} reevaluation logic.
/// </summary>
[DisableAutoCreation]
public partial struct {name}ReevaluateTaskSystem : ISystem
{{
/// <summary>
/// Updates the reevaluation logic.
/// </summary>
/// <param name=""state"">The current state of the system.</param>
[BurstCompile]
private void OnUpdate(ref SystemState state)
{{
foreach (var (branchComponents, taskComponents, {variableName}Components) in
SystemAPI.Query<DynamicBuffer<BranchComponent>, DynamicBuffer<TaskComponent>, DynamicBuffer<{name}Component>>().WithAll<{name}ReevaluateFlag, EvaluateFlag>()) {{
for (int i = 0; i < {variableName}Components.Length; ++i) {{
var {variableName}Component = {variableName}Components[i];
var taskComponent = taskComponents[{variableName}Component.Index];
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (!branchComponent.CanExecute) {{
continue;
}}
if (!taskComponent.Reevaluate) {{
continue;
}}
var status = TaskStatus.Success;
if (status != taskComponent.Status) {{
taskComponent.Status = status;
var buffer = taskComponents;
buffer[taskComponent.Index] = taskComponent;
}}
}}
}}
}}
}}";
}
}
/// <summary>
/// Template for creating a custom decorator node.
/// </summary>
[Category("ECS")]
[DisplayName("Decorator")]
[Description("Create a new ECS decorator node.")]
public class ECSDecoratorNode : IParentNodeTemplate
{
public Type BaseType => typeof(IDecorator);
public bool IsLogicNode => true;
/// <summary>
/// Returns the script that should be used for the template file.
/// </summary>
/// <param name="name">The name of the node.</param>
/// <returns>The node script.</returns>
public string GetScript(string name)
{
var variableName = ECSNodeUtility.ToCamelCase(name);
return $@"using Opsive.BehaviorDesigner.Runtime.Components;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Unity.Entities;
using Unity.Burst;
using UnityEngine;
/// <summary>
/// A custom ECS decorator node.
/// </summary>
public class {name} : ECSDecoratorTask<{name}TaskSystem, {name}Component>
{{
/// <summary>
/// The type of flag that should be enabled when the task is running.
/// </summary>
public override ComponentType Flag {{ get => typeof({name}Flag); }}
/// <summary>
/// Returns a new {name}Component for use by the system.
/// </summary>
/// <returns>A new {name}Component for use by the system.</returns>
public override {name}Component GetBufferElement()
{{
return new {name}Component() {{
Index = RuntimeIndex,
}};
}}
}}
/// <summary>
/// The DOTS data structure for the {name} class.
/// </summary>
public struct {name}Component : IBufferElementData
{{
[Tooltip(""The index of the node."")]
public ushort Index;
}}
/// <summary>
/// A DOTS flag indicating when a {name} node is active.
/// </summary>
public struct {name}Flag : IComponentData, IEnableableComponent {{ }}
/// <summary>
/// Runs the {name} logic.
/// </summary>
[DisableAutoCreation]
public partial struct {name}TaskSystem : ISystem
{{
/// <summary>
/// Creates the job.
/// </summary>
/// <param name=""state"">The current state of the system.</param>
[BurstCompile]
private void OnUpdate(ref SystemState state)
{{
var query = SystemAPI.QueryBuilder().WithAllRW<BranchComponent>().WithAllRW<TaskComponent>().WithAllRW<{name}Component>().WithAll<{name}Flag, EvaluateFlag>().Build();
state.Dependency = new {name}Job().ScheduleParallel(query, state.Dependency);
}}
/// <summary>
/// Job which executes the task logic.
/// </summary>
[BurstCompile]
private partial struct {name}Job : IJobEntity
{{
/// <summary>
/// Executes the {name} logic.
/// </summary>
/// <param name=""branchComponents"">An array of BranchComponents.</param>
/// <param name=""taskComponents"">An array of TaskComponents.</param>
/// <param name=""{variableName}Components"">An array of {name}Components.</param>
[BurstCompile]
public void Execute(ref DynamicBuffer<BranchComponent> branchComponents, ref DynamicBuffer<TaskComponent> taskComponents, ref DynamicBuffer<{name}Component> {variableName}Components)
{{
for (int i = 0; i < {variableName}Components.Length; ++i) {{
var {variableName}Component = {variableName}Components[i];
var taskComponent = taskComponents[{variableName}Component.Index];
var branchComponent = branchComponents[taskComponent.BranchIndex];
if (!branchComponent.CanExecute) {{
continue;
}}
if (taskComponent.Status == TaskStatus.Queued) {{
taskComponent.Status = TaskStatus.Success;
taskComponents[{variableName}Component.Index] = taskComponent;
}}
}}
}}
}}
}}";
}
}
}
#endif