UI 系统

This commit is contained in:
2026-06-08 16:05:00 +08:00
parent b582317692
commit 247de307c6
150 changed files with 10945 additions and 158 deletions

View File

@@ -47,22 +47,32 @@
- **固定位置敌人E002 簧蛭)不挂 EnemyNavAgent / NavAgent不调用 MoveTo**
- 朝向翻转:`EnemyMovement.UpdateFacing()` 优先用 `SpriteRenderer.flipX`,无 SR 时用 `localScale.x`
### 4. 感知系统约束(SensorToolkit
### 4. 感知系统约束(自研 PhysicsPerceptionSystem
`EnemySensorHub` 标准槽位名称约定:
> ⚠️ **本节已更新**:项目已**自研 `PhysicsPerceptionSystem` 完全替代 SensorToolkit**,不再使用 `EnemySensorHub` / SensorToolkit 命名空间。`_Game` 代码零引用 SensorToolkit。Prefab 上挂的是 `PhysicsPerceptionSystem`(纯物理实现),其 `_slots[]` 槽位数组配置各感知槽。
| 槽名 | Sensor 类型 | 用途 |
|------|------------|------|
| `aggro` | RangeSensor2D | 主要警戒/感知范围 |
| `attack_melee` | RangeSensor2D | 近战攻击距离触发 |
| `attack_range` | RangeSensor2D | 远程攻击距离触发 |
| `los` | LOSSensor2D | 视线检测 |
| `wall_ahead` | RaySensor2D | 前方障碍物检测 |
| `ledge` | RaySensor2D | 前方悬崖检测 |
**架构**
- 接口 `BaseGames.Enemies.Perception.IPerceptionSystem``HasSlot` / `IsDetecting(slot, target)` / `HasAnyDetection(slot)` / `GetFirstDetection(slot)`
- 实现 `PhysicsPerceptionSystem`:支持 7 种槽位类型 `RangeCircle / BatchLOS / FanCast / BoxCast / Sight / RayCast / TriggerZone`含错帧更新tickInterval、动态禁用、Enter/Exit 事件;视线由 `SightBatchSystem` 全局预算管理LOD
- `EnemyBase.Awake()` 通过 `GetComponentInChildren<IPerceptionSystem>()` 注册,对外暴露 `EnemyBase.SensorHub``IPerceptionSystem` 类型)
`BD_IsSensorDetecting` 的 m_SlotName 必须与 Inspector 中 `EnemySensorHub._slots` 对应的 slotName 完全一致。
**标准槽位常量**(定义于 `Perception/SensorSlotNames.cs`,禁止散布魔法字符串):
`BD_Patrol` 优先读取 `wall_ahead``ledge` 槽的 SensorToolkit 结果;若未配置这两个槽,自动回退 Raycast 兜底。
| 常量 | 字符串键 | 槽位类型 | 用途 |
|------|---------|---------|------|
| `SensorSlotNames.Aggro` | `aggro` | RangeCircle | 主要警戒/追击范围 |
| `SensorSlotNames.Alert` | `alert` | RangeCircle | 警觉半径(比 aggro 小,待机/巡逻→Alert 切换) |
| `SensorSlotNames.AttackMelee` | `attack_melee` | RangeCircle | 近战攻击距离触发 |
| `SensorSlotNames.AttackRange` | `attack_range` | RangeCircle | 远程攻击距离触发 |
| `SensorSlotNames.Patrol` | `patrol` | RangeCircle | 巡逻范围限制(超出触发返回) |
| `SensorSlotNames.LOS` | `los` | BatchLOS | 无遮挡视线OverlapCircle + 单射线遮挡) |
| `SensorSlotNames.Sight` | `sight` | Sight | 视锥 + 强制 LOS 遮挡检测("看见玩家"核心传感器) |
> ⚠️ **`wall_ahead` / `ledge` 槽已废除**:前方墙体/悬崖检测**不再走感知系统**,改由 `EnemyMovement.IsWallAhead` / `EnemyMovement.IsLedgeAhead`EnemyMovement 内置物理射线)提供。
`BD_IsSensorDetecting` 实现:查询 `gameObject.GetComponent<IPerceptionSystem>()`;字段 `m_SlotName`(应填 `SensorSlotNames` 对应字符串键)+ `m_AnyTarget`true 时用 `HasAnyDetection`false 时对 `EnemyBase.PlayerTransform``IsDetecting`)。
`BD_Patrol` 直接读取 `EnemyMovement.IsWallAhead / IsLedgeAhead` 触发翻转,**不依赖任何感知槽**(含转身冷却防抖)。
### 5. AI 阶段AiPhase枚举值
@@ -162,7 +172,7 @@
**`ENM_E001_Stats.asset`**EnemyStatsSOMaxHP、WalkSpeed巡逻、RunSpeed追击、DetectRange。
**`ABL_E001_Activate.asset`**abilityId=`"e001_activate"`cooldown=0
**`ABL_E001_Alert.asset`**abilityId=`"e001_alert"`cooldown=1.5(激活/警觉序列PlayClipAbility
**`ABL_E001_Chase.asset`**abilityId=`"e001_chase"`cooldown=0preferredMaxRange=DetectRange
#### 新建 Ability
@@ -187,7 +197,8 @@ protected override IEnumerator ExecuteCoroutine()
[SerializeField] private Animancer.ClipTransition _loopClip;
[SerializeField] private Animancer.ClipTransition _endClip;
[SerializeField] private BodyContactDamage _contactDamage;
[SerializeField] private EnemySensorHub _sensorHub;
[SerializeField] private string _aggroSlotName = SensorSlotNames.Aggro;
// 感知通过 EnemyBase.SensorHubIPerceptionSystem查询不自挂感知组件引用
protected override IEnumerator ExecuteCoroutine()
{
@@ -198,7 +209,8 @@ protected override IEnumerator ExecuteCoroutine()
while (true)
{
if (_enemy.PlayerTransform == null) break;
if (!_sensorHub.IsDetecting("aggro", _enemy.PlayerTransform.gameObject)) break;
if (_enemy.SensorHub != null &&
!_enemy.SensorHub.IsDetecting(_aggroSlotName, _enemy.PlayerTransform.gameObject)) break;
_enemy.MoveTo(_enemy.PlayerTransform.position);
yield return null;
}
@@ -216,12 +228,12 @@ protected override IEnumerator ExecuteCoroutine()
`
E001_CaoZhi
├── [EnemyBase] [EnemyMovement] [EnemyNavAgent] [NavAgent] [TransformBasedMovement]
├── [EnemySensorHub] → slots: [{aggro, RangeSensor2D}, {wall_ahead, RaySensor2D}, {ledge, RaySensor2D}]
├── [PhysicsPerceptionSystem] → _slots: [{aggro, RangeCircle}] ← 墙/崖检测由 EnemyMovement 内置射线,无需槽
├── [Rigidbody2D] Dynamic [Collider2D]
├── HurtBox/ [HurtBox] [Collider2D trigger=true Layer=EnemyHurtBox]
├── ContactDamageZone/ [BodyContactDamage] [HitBox] [Collider2D trigger=true Layer=EnemyHitBox]
└── Abilities/
├── [PlayClipAbility] (_config=ABL_E001_Activate)
├── [PlayClipAbility] (_config=ABL_E001_Alert)
└── [ContactChaseAbility] (_config=ABL_E001_Chase)
`
@@ -235,7 +247,7 @@ Selector
│ │ ├── BD_IsAiPhase(Idle)
│ │ └── BD_IsAiPhase(Patrol)
│ ├── BD_IsSensorDetecting("aggro")
│ ├── BD_UseAbility(ABL_E001_Activate)
│ ├── BD_UseAbility(ABL_E001_Alert)
│ └── BD_SetAiPhase(Chase)
├── Sequence [追击]
│ ├── BD_IsAiPhase(Chase)
@@ -312,7 +324,7 @@ protected override void OnInterrupted(InterruptReason reason)
`
E002_HuangZhi
├── [EnemyBase] ← 无 EnemyMovement无 NavAgent
├── [EnemySensorHub] → slots: [{attack_range, RangeSensor2D(正下方矩形)}]
├── [PhysicsPerceptionSystem] → _slots: [{attack_range, BoxCast(正下方矩形)}]
├── [Rigidbody2D] Kinematic Gravity=0
├── HurtBox/ [HurtBox enabled=false] [Collider2D trigger]
├── AttackHitBox/ [HitBox] [Collider2D trigger]
@@ -441,7 +453,7 @@ public void ActivateFromCeiling()
`
E003_YouZhi
├── [E003_YouZhi] [EnemyMovement] [EnemyNavAgent] [NavAgent] [TransformBasedMovement]
├── [EnemySensorHub] → slots: [{aggro, RangeSensor2D}]
├── [PhysicsPerceptionSystem] → _slots: [{aggro, RangeCircle}]
├── [Rigidbody2D] KinematicAnimatedCeilingDropAbility 切换为 Dynamic
├── HurtBox/
├── ContactDamageZone/ [BodyContactDamage enabled=false] ← 落地后由能力 Enable
@@ -646,7 +658,7 @@ private IEnumerator DeathSequence()
`
E004_ZhiMu
├── [E004_ZhiMu] [EnemyMovement] [EnemyNavAgent] [NavAgent] [TransformBasedMovement]
├── [EnemySensorHub] → slots: [{aggro, RangeSensor2D}, {los, LOSSensor2D}]
├── [PhysicsPerceptionSystem] → _slots: [{aggro, RangeCircle}, {sight, Sight}]
├── [EnemyFeedback] [Rigidbody2D]
├── HurtBox/ BiteHitBox/ SlamHitBox/
├── AcidMuzzle/ [Transform]
@@ -823,7 +835,7 @@ attackSequence[0].clipSkill ClipTransition
`
E006_Huan
├── [EnemyBase] [EnemyMovement] [EnemyNavAgent] [NavAgent] [TransformBasedMovement]
├── [EnemySensorHub] → slots: [{aggro, RangeSensor2D}, {wall_ahead, RaySensor2D}, {ledge, RaySensor2D}]
├── [PhysicsPerceptionSystem] → _slots: [{sight, Sight正面视锥}] ← 墙/崖检测由 EnemyMovement 内置射线
├── [Rigidbody2D] Dynamic
├── HurtBox/ LandingHitBox/
└── Abilities/ [LeapAttackAbility (abilityId="e006_leap")]
@@ -835,10 +847,10 @@ E006_Huan
Selector
├── BD_IsStateMatch(Dead) → BD_StopMovement ← Die() 已自动播放 AnimConfig.Dead
├── Sequence [跳跃攻击]
│ ├── BD_IsSensorDetecting("aggro")
│ ├── BD_IsSensorDetecting("sight") ← 正面视锥感知FanCast/Sight对应设计"视线检测正面扇形"
│ ├── BD_CanUseAbility(ABL_E006_Leap)
│ └── BD_UseAbility(ABL_E006_Leap)
└── BD_Patrol [wall_ahead+ledge槽驱动翻转无传感器则Raycast兜底]
└── BD_Patrol [EnemyMovement.IsWallAhead/IsLedgeAhead 内置射线驱动翻转,无需感知槽]
`
> **Flip 动画**`EnemyMovement` 已原生支持转身动画,无需修改 BT。只需
@@ -1164,8 +1176,8 @@ public class ChaoFengKnockdownCounter : MonoBehaviour
public void OnBossHit(DamageInfo info)
{
if (_inKnockdown || _boss.CurrentPhase != 1) return;
// ⚠️ 玩家是否在空中的判断方式待确认见Q6
// 临时实现:所有命中均计数
// Q6 已实现仅玩家在空中IGroundedActor.IsGrounded==false时计数
if (!IsPlayerAirborne()) return;
_count++;
if (_count >= _threshold) { _count = 0; StartCoroutine(KnockdownSequence()); }
}
@@ -1189,6 +1201,7 @@ public class ChaoFengKnockdownCounter : MonoBehaviour
`
ChaoFeng
├── [ChaoFengBoss] [EnemyMovement] [EnemyNavAgent] [NavAgent] [TransformBasedMovement]
├── [PhysicsPerceptionSystem] → _slots: [{aggro, RangeCircle}, {sight, Sight}]
├── [BossSkillExecutor]
├── [ChaoFengFloatController]
├── [ChaoFengKnockdownCounter]
@@ -1228,7 +1241,7 @@ Assets/_Game/
├── Data/Enemies/
│ ├── E001/
│ │ ├── ENM_E001_Stats.asset ← EnemyStatsSOENM_ 前缀)
│ │ ├── ABL_E001_Activate.asset ← PlayClipAbility SOABL_ 前缀)
│ │ ├── ABL_E001_Alert.asset ← PlayClipAbility SOABL_ 前缀abilityId=e001_alert
│ │ └── ABL_E001_Chase.asset ← ContactChaseAbility SO
│ ├── E002/
│ │ ├── ENM_E002_Stats.asset
@@ -1328,8 +1341,8 @@ Assets/_Game/
| Q1 | 嘲风 Phase 2 能否用 Phase 1 技能? | BossSkillSO.availablePhaseIndices |
| Q2 | 击落计数是否打断风石施法? | ChaoFengKnockdownCounter 中断逻辑 |
| Q3 | 挥扇三连第3击是否有后方碰撞盒 | AttackPatternSO HitBox 形状 |
| Q4 | E004 Flip 背后检测方案Sensor槽还是 FacingDirection 比较)? | EnemySensorHub 配置 |
| Q4 | E004 Flip 背后检测方案Sensor槽还是 FacingDirection 比较)? | 现实现FacePlayerAbility.CanUse 内用 Movement.FacingDirection 比较,不依赖感知槽 |
| Q5 | E005 是否有 Flip 动画? | 是否新建 Flip Ability |
| Q6 | 玩家空中判断方式DamageInfo 携带/PlayerController 事件)? | ChaoFengKnockdownCounter.IsAttackerAirborne |
| Q6 | ~~玩家空中判断方式~~ | ✅ 已实现:`IGroundedActor` 接口Core由 PlayerMovement 实现;`ChaoFengKnockdownCounter``BossBase.PlayerTransform` 查询,仅玩家离地时计数(取不到接口时保守按空中处理) |
| Q7 | 各角色 HP/速度/伤害/CD 数值 | 所有 StatsSO / AbilitySO 填写 |
| Q8 | 嘲风 Phase 2 浮空待机是否复用 Phase 1 Idle | 美术制作量 |