feat(enemy): BossFightTrigger——补上缺失的开战触发
开战事件频道此前全项目零发送方,Boss 永远不开战。 触发时广播开战事件并向 Boss 决策层发 AiSignal.Engaged。 漏配目标 Brain / bossId / 两个频道 / 玩家层掩码 / isTrigger 一律逐条点名报错并停用组件, 不做部分执行——半开的战斗比彻底不开更难查。
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.AI;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// Boss 开战触发:玩家进入竞技场触发区时,广播开战事件并让 Boss 的决策层从静置态进战。
|
||||
/// 此前开战事件频道全项目零发送方,Boss 永远不会开战。
|
||||
///
|
||||
/// 放在 Boss 房间入口的触发区上(Collider2D + isTrigger)。只触发一次。
|
||||
/// 漏配一律在 Awake 显式报错——静默不触发会表现为"Boss 站着不动",极难定位。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public sealed class BossFightTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("目标 Boss")]
|
||||
[Tooltip("要唤醒的 Boss 的决策层组件")]
|
||||
[SerializeField] private EnemyAiBrain _bossBrain;
|
||||
|
||||
[Header("事件频道")]
|
||||
[Tooltip("EVT_BossFightStarted — payload 为 bossId,驱动全局状态机 / 后处理 / HUD")]
|
||||
[SerializeField] private StringEventChannelSO _onBossFightStarted;
|
||||
[Tooltip("EVT_BossFightEnded — 布尔开关频道,true 表示开始,驱动 Boss 血条与 BGM 切换")]
|
||||
[SerializeField] private BoolEventChannelSO _onBossFightToggled;
|
||||
|
||||
[Header("标识")]
|
||||
[Tooltip("Boss id,与 Boss 身上配置的 bossId 一致")]
|
||||
[SerializeField] private string _bossId;
|
||||
|
||||
[Header("触发条件")]
|
||||
[Tooltip("玩家所在的层")]
|
||||
[SerializeField] private LayerMask _playerLayers;
|
||||
|
||||
private bool _fired;
|
||||
|
||||
// 漏配处理沿用 EnemyAiBrain 的形态:逐条点名报错 + 停用组件。
|
||||
// 不做「缺谁就跳过谁」的部分执行——半开的战斗(有 BGM 没血条、Boss 不动)
|
||||
// 比彻底不开更难查,问题必须停在配置这一层。
|
||||
private void Awake()
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
var col = GetComponent<Collider2D>();
|
||||
if (!col.isTrigger)
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 的 Collider2D 未勾选 Is Trigger,无法触发开战。", this);
|
||||
valid = false;
|
||||
}
|
||||
if (_bossBrain == null)
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 未指定目标 Boss 的 EnemyAiBrain,开战不会生效。", this);
|
||||
valid = false;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_bossId))
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 未填 bossId,BGM / 血条无法定位该 Boss。", this);
|
||||
valid = false;
|
||||
}
|
||||
if (_onBossFightStarted == null)
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 未绑定开战事件频道,全局状态机 / 后处理不会响应开战。", this);
|
||||
valid = false;
|
||||
}
|
||||
if (_onBossFightToggled == null)
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 未绑定开战开关频道,Boss 血条与战斗 BGM 不会切换。", this);
|
||||
valid = false;
|
||||
}
|
||||
// 掩码为空 = 任何层都过不了下面的判定,触发区形同虚设。
|
||||
if (_playerLayers.value == 0)
|
||||
{
|
||||
Debug.LogError($"[BossFightTrigger] {name} 的玩家层掩码为空,触发区永远不会被触发。", this);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) enabled = false;
|
||||
}
|
||||
|
||||
// 能走到这里说明 Awake 的配置校验全过了,故不再判空——
|
||||
// 判空只会把已经报过错的漏配再盖一层。
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (_fired) return;
|
||||
if ((_playerLayers.value & (1 << other.gameObject.layer)) == 0) return;
|
||||
|
||||
_fired = true;
|
||||
_onBossFightStarted.Raise(_bossId);
|
||||
_onBossFightToggled.Raise(true);
|
||||
_bossBrain.Send(AiSignal.Engaged);
|
||||
}
|
||||
|
||||
/// <summary>供存档回载 / 重试时复位(Boss 未被击败则重新可触发)。</summary>
|
||||
public void ResetTrigger() => _fired = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd8a9d00a149c124a9bd90ffba663a32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user