feat(enemy): RushEngagement——两个耦合 flag 合并为 RushExit 语义枚举

This commit is contained in:
2026-07-29 12:32:50 +08:00
parent 1c6ce57648
commit bb1e2f7705
7 changed files with 204 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8dc9b46f90ca9734e85831dd4fc0299c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,64 @@
using System;
using UnityEngine;
using BaseGames.AI;
using BaseGames.Enemies.Abilities;
namespace BaseGames.Enemies
{
/// <summary>接触冲锋:一个能力包办追击与伤害。冲锋能力自己管方向、速度、动画与命中。</summary>
[Serializable]
public sealed class RushEngagement : IEngagementModule
{
public const string Rush = "Rush";
[Tooltip("冲锋能力")]
[SerializeField] AbilityRef _ability;
[Tooltip("脱战语义:OnLostTarget=追不到就放弃;Committed=起手即锁定,打完才脱战")]
[SerializeField] RushExit _exit = RushExit.OnLostTarget;
// 惰性缓存:[SerializeReference] 反序列化不保证走构造函数,不能在 ctor 里预计算。
[NonSerialized] Func<IAiContext, bool> _canEngage;
public RushEngagement() { }
public RushEngagement(AbilityRef ability, RushExit exit = RushExit.OnLostTarget)
{ _ability = ability; _exit = exit; }
public string EntryState => Rush;
public string EngageLabel => _exit == RushExit.Committed ? "InChaseZone+offCD" : "InChaseZone";
public Func<IAiContext, bool> CanEngage
{
get
{
if (_canEngage == null)
{
string id = RequireAbilityId();
_canEngage = _exit == RushExit.Committed
? x => x.Sensor.InChaseZone() && x.Combat.CanUseAbility(id)
: (Func<IAiContext, bool>)(x => x.Sensor.InChaseZone());
}
return _canEngage;
}
}
public void Build(BrainBuilder b, string rest)
{
string id = RequireAbilityId();
var s = AiStateFragments.Ability(b, Rush, id);
if (_exit == RushExit.Committed)
s.To(rest).When(x => !x.Combat.IsAbilityRunning(id), "rushDone→CD");
else
s.To(rest).When(AiStateFragments.LostAllZones, "leftAllZones");
}
// 校验放这里而非构造函数:反序列化路径不走 ctor,漏配必须在建图时暴露。
string RequireAbilityId()
{
string id = _ability.Id;
if (string.IsNullOrEmpty(id))
throw new InvalidOperationException("RushEngagement:未配置冲锋能力。");
return id;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4df22f40788d1624fbcbe0081aea92e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
namespace BaseGames.Enemies
{
/// <summary>冲锋的脱战语义。一个值同时决定"脱战边怎么写"与"进入条件带不带冷却门"。</summary>
public enum RushExit
{
/// <summary>追不到就放弃:脱离全部感知区即停。</summary>
OnLostTarget,
/// <summary>
/// 起手即锁定:整段冲锋跑完才脱战,中途丢失感知不打断。
/// 冷却门由模块自动附加——否则打完回未发现态后,下一帧仍在追逐区会立刻再冲,
/// 出现 冲锋态 ↔ 未发现态 每帧抖动。
/// </summary>
Committed,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62cce901ea34dbd4ba6edc77f15e17f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: