72 lines
3.5 KiB
C#
72 lines
3.5 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using BaseGames.AI;
|
||
using BaseGames.Core;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
/// <summary>
|
||
/// 感知型 AI 配方:一个 SO 类型覆盖所有走「未发现 → 警觉 → 交战」规则的敌人。
|
||
/// 两个下拉各选一个模块即可,无需写代码。有独门机制的敌人改写 AiScript 子类。
|
||
/// </summary>
|
||
[CreateAssetMenu(menuName = "BaseGames/AI/感知型 AI 配方", fileName = "ENM_")]
|
||
public sealed class PerceptionRecipeSO : AiRecipeSO, IValidatable
|
||
{
|
||
[Tooltip("未发现层:玩家尚未被发现时的行为形状")]
|
||
[SerializeReference, SubclassSelector] IUnawareModule _unaware = new SinglePost();
|
||
|
||
[Tooltip("交战层:发现玩家之后怎么打")]
|
||
[SerializeReference, SubclassSelector] IEngagementModule _engagement = new RushEngagement();
|
||
|
||
// 死亡不做成第三层可插拔构件:死亡归物理层,骨架自己声明 Death 终态。
|
||
// PerformDeath 先 ForceState(Dead) 再发 Died 信号,此后 IsControllable 永久为假,
|
||
// 死亡链里的条件边永不被求值——所以死亡不能做成可插拔层。
|
||
|
||
protected override void Build(BrainBuilder b)
|
||
=> PerceptionSkeleton.Add(b, _unaware, _engagement);
|
||
|
||
/// <summary>装配模块。供脚手架向导与 EditMode 测试使用,运行时不应调用
|
||
/// (本资产是 flyweight,运行时改它会影响所有引用该配方的敌人)。</summary>
|
||
public void AssignModules(IUnawareModule unaware, IEngagementModule engagement)
|
||
{
|
||
_unaware = unaware; _engagement = engagement;
|
||
InvalidateGraph();
|
||
}
|
||
|
||
/// <summary>SOValidationRunner 自动扫描调用。结构性校验(模块是否选、建图是否成功)
|
||
/// 由配方负责;模块自身的配置(能力引用、冷却语义等)委托给模块的 Validate()——
|
||
/// 新增一种打法不需要回来改这里。</summary>
|
||
public IEnumerable<ValidationResult> Validate()
|
||
{
|
||
string who = name;
|
||
|
||
if (_unaware == null)
|
||
yield return ValidationResult.Error($"{who}:未选择未发现层模块。");
|
||
if (_engagement == null)
|
||
yield return ValidationResult.Error($"{who}:未选择交战层模块。");
|
||
if (_unaware == null || _engagement == null) yield break;
|
||
|
||
bool moduleHasError = false;
|
||
foreach (var r in _engagement.Validate())
|
||
{
|
||
if (r.Severity == ValidationSeverity.Error) moduleHasError = true;
|
||
yield return new ValidationResult(r.Severity, $"{who}:{r.Message}");
|
||
}
|
||
if (moduleHasError) yield break; // 模块层已定位到问题,探测只会重复同一条
|
||
|
||
// 结构性校验:用一次性 builder 试建,模块与骨架的建图期守卫会把问题抛出来
|
||
// (能力漏配、Rest 不在 States、态未声明、跨模块状态名冲突等)。
|
||
// 走 ProbeBuild 而非 GetOrBuildGraph:后者在缓存已热时会跳过 Build(等于没探测),
|
||
// 且事后置空缓存会破坏 flyweight 不变量。
|
||
ValidationResult? buildFailure = null;
|
||
try { ProbeBuild(); }
|
||
catch (System.Exception e)
|
||
{
|
||
buildFailure = ValidationResult.Error($"{who}:建图失败 —— {e.Message}");
|
||
}
|
||
|
||
if (buildFailure.HasValue) yield return buildFailure.Value;
|
||
}
|
||
}
|
||
}
|