docs(ai): BrainGraph 标签方案改为 C#9 兼容(显式可选字符串)
Unity 2022.3=C#9,不用 C#10 的 CallerArgumentExpression;When(cond,label)显式可选, 默认回退"cond";spec 示例与 Phase1 计划(Task4/5 测试)同步更新. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -542,9 +542,9 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
b.Entry("Patrol");
|
||||
b.Global().To("Dead").OnEvent(AiSignal.Died);
|
||||
b.State("Patrol")
|
||||
.To("Chase").When(c => c.Sensor.SeesPlayer());
|
||||
.To("Chase").When(c => c.Sensor.SeesPlayer(), "SeesPlayer");
|
||||
b.State("Chase")
|
||||
.To("Patrol").When(c => c.Sensor.LostFor(2f));
|
||||
.To("Patrol").When(c => c.Sensor.LostFor(2f), "LostFor(2s)");
|
||||
b.State("Dead");
|
||||
return b.Build();
|
||||
}
|
||||
@@ -565,11 +565,22 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Build_CapturesConditionLabelFromExpressionText()
|
||||
public void Build_UsesExplicitConditionLabel()
|
||||
{
|
||||
var g = BuildSample();
|
||||
var t = g.GetState("Patrol").Transitions[0];
|
||||
StringAssert.Contains("SeesPlayer", t.Label);
|
||||
Assert.AreEqual("SeesPlayer", t.Label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Build_UnlabeledCondition_FallsBackToCond()
|
||||
{
|
||||
var b = new BrainBuilder();
|
||||
b.Entry("A");
|
||||
b.State("A").To("B").When(c => true);
|
||||
b.State("B");
|
||||
var g = b.Build();
|
||||
Assert.AreEqual("cond", g.GetState("A").Transitions[0].Label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -639,7 +650,6 @@ namespace BaseGames.AI
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace BaseGames.AI
|
||||
{
|
||||
@@ -716,11 +726,14 @@ namespace BaseGames.AI
|
||||
internal TransitionBuilder(BrainBuilder owner, StateBuilder sb, AiState state, string target)
|
||||
{ _owner = owner; _stateBuilder = sb; _state = state; _target = target; }
|
||||
|
||||
/// <summary>条件转换。label 默认由 [CallerArgumentExpression] 抓取 cond 的源码文本。</summary>
|
||||
public StateBuilder When(Func<IAiContext, bool> cond,
|
||||
[CallerArgumentExpression("cond")] string label = null)
|
||||
/// <summary>
|
||||
/// 条件转换。label 为可读标签(用于 Mermaid 边 / trace 触发原因),
|
||||
/// C# 9 环境下需显式传入;不传则回退为 "cond"。
|
||||
/// (升级到 C# 10 后可改用 [CallerArgumentExpression] 自动抓取源码文本。)
|
||||
/// </summary>
|
||||
public StateBuilder When(Func<IAiContext, bool> cond, string label = null)
|
||||
{
|
||||
_state.Transitions.Add(Transition.OnCondition(_target, cond, label));
|
||||
_state.Transitions.Add(Transition.OnCondition(_target, cond, label ?? "cond"));
|
||||
return _stateBuilder;
|
||||
}
|
||||
|
||||
@@ -754,10 +767,9 @@ namespace BaseGames.AI
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlobalBuilder When(Func<IAiContext, bool> cond,
|
||||
[CallerArgumentExpression("cond")] string label = null)
|
||||
public GlobalBuilder When(Func<IAiContext, bool> cond, string label = null)
|
||||
{
|
||||
_owner.AddGlobal(Transition.OnCondition(_pendingTarget, cond, label));
|
||||
_owner.AddGlobal(Transition.OnCondition(_pendingTarget, cond, label ?? "cond"));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -767,19 +779,7 @@ namespace BaseGames.AI
|
||||
|
||||
> 注:`After()` 引用了 `AiRuntime.TimeInState(c)`(Task 5 定义的静态辅助,通过 context 取当前 runtime 的状态计时器)。若执行到本任务时 `AiRuntime` 尚未定义,先临时把 `After` 的实现体改为 `throw new NotImplementedException()` 以通过编译,Task 5 完成后回填(回填是同一函数体,非占位)。测试 `BrainBuilderTests` 不覆盖 `After`,不受影响。
|
||||
>
|
||||
> **CallerArgumentExpression 兼容性**:需 C# 10。若项目 Unity 版本的语言级别不支持,添加一个 polyfill:新建 `Assets/_Game/Scripts/AI/CallerArgumentExpressionAttribute.cs`:
|
||||
> ```csharp
|
||||
> #if !NET5_0_OR_GREATER
|
||||
> namespace System.Runtime.CompilerServices {
|
||||
> [AttributeUsage(AttributeTargets.Parameter)]
|
||||
> internal sealed class CallerArgumentExpressionAttribute : Attribute {
|
||||
> public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName;
|
||||
> public string ParameterName { get; }
|
||||
> }
|
||||
> }
|
||||
> #endif
|
||||
> ```
|
||||
> 若 polyfill 后编译器仍不填充(语言版本 < 10),标签会退化为默认值 —— 此时把 `Build_CapturesConditionLabelFromExpressionText` 标记为 `[Ignore("需 C# 10")]` 并在第 6 阶段处理,不阻塞核心。
|
||||
> **标签方案(C# 9 环境,Unity 2022.3)**:转换的可读标签走**显式可选字符串**(`When(cond, "SeesPlayer")`)。声明时建议给关键条件转换传标签,使 Mermaid 边 / trace 触发原因可读;不传则回退为 `"cond"`。**不使用 `[CallerArgumentExpression]`**(那是 C# 10 特性,本项目 Unity 2022.3 用 C# 9,编译器不会填充)。将来升级语言级别后可无痛切换为自动抓取。
|
||||
|
||||
- [ ] **Step 5: 运行测试确认通过**
|
||||
|
||||
@@ -891,11 +891,11 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
b.Global().To("Dead").OnEvent(AiSignal.Died);
|
||||
b.State("Patrol")
|
||||
.Tick(c => c.Mover.WalkRandom())
|
||||
.To("Chase").When(c => c.Sensor.SeesPlayer());
|
||||
.To("Chase").When(c => c.Sensor.SeesPlayer(), "SeesPlayer");
|
||||
b.State("Chase")
|
||||
.OnEnter(c => c.Mover.FacePlayer())
|
||||
.Tick(c => c.Mover.MoveTo(c.Sensor.LastKnown))
|
||||
.To("Search").When(c => c.Sensor.LostFor(2f));
|
||||
.To("Search").When(c => c.Sensor.LostFor(2f), "LostFor(2s)");
|
||||
b.State("Search")
|
||||
.To("Patrol").After(3f);
|
||||
b.State("Dead");
|
||||
@@ -1307,4 +1307,4 @@ git commit -m "chore(ai): BrainGraph 第1阶段核心运行时收尾——全测
|
||||
- **Spec 覆盖**:本阶段对应 spec §3.1(共享图)/§3.2(轻量运行时)/§3.3(条件+事件转换)/§3.6(IsControllable门)/§3.8(池复用重置、无实例捕获约束经 IAiContext 落实)。§3.4/§3.5黑板已建/§3.7/§4完整/§6/§7/§8 明确列入后续阶段路线图,无遗漏。
|
||||
- **占位扫描**:无 TBD/TODO 占位;`After` 的临时抛异常 + Task 5 回填是明确的两步实现(非占位),已注明回填内容与顺序。
|
||||
- **类型一致性**:`IAiContext.Sensor/Mover/Combat/Vitals/Blackboard`、`AiRuntime.CurrentStateName/IsSuspended/Trace/Send/Reset/Tick`、`Transition.OnCondition/OnEvent/IsEvent/Label`、`BrainBuilder.Entry/State/Global/Build`、`StateBuilder.OnEnter/Tick/OnExit/To`、`TransitionBuilder.When/OnEvent/After` 跨任务命名一致。
|
||||
- **已知风险**:CallerArgumentExpression 需 C# 10,已给 polyfill + 降级方案(Task 4 Step 4 注)。
|
||||
- **环境适配**:Unity 2022.3 = C# 9。转换标签走显式可选字符串(不用 C# 10 的 CallerArgumentExpression);`After` 用状态计时器、事件转换用 `AiSignal`,均 C# 9 兼容。
|
||||
|
||||
Reference in New Issue
Block a user