This commit is contained in:
2026-07-07 03:34:56 +08:00
parent e7891d7e00
commit ecb20cf370
81 changed files with 2108 additions and 293 deletions
+12 -2
View File
@@ -1,4 +1,5 @@
using UnityEngine;
using Architecture.Core;
namespace IndianOceanAssets.Engine2_5D
{
@@ -46,6 +47,9 @@ namespace IndianOceanAssets.Engine2_5D
[Tooltip("聆听范围:主角在此范围内按E摇铃时,敌人会朝铃铛位置移动")]
[SerializeField] private float listenRange = 15f;
[Header("SO 事件通道(替代 static EchoSystem.OnEchoReleased 事件)")]
[SerializeField] private Vector3Event echoReleasedEvent;
[Header("铃铛追击")]
[Tooltip("到达铃铛位置后的容差距离")]
[SerializeField] private float bellArriveDistance = 1f;
@@ -105,6 +109,12 @@ namespace IndianOceanAssets.Engine2_5D
private HealthSystem _playerHealth; // 玩家血量引用
private Camera _mainCam; // 主相机引用(用于Billboard
private void OnValidate()
{
if (echoReleasedEvent == null)
Debug.LogWarning($"[EnemyAI] Echo Released Event 未接线({gameObject.name})。该敌人不会响应摇铃。", this);
}
private void Start()
{
if (playerTarget == null)
@@ -127,12 +137,12 @@ namespace IndianOceanAssets.Engine2_5D
private void OnEnable()
{
EchoSystem.OnEchoReleased += OnBell;
echoReleasedEvent?.Register(OnBell);
}
private void OnDisable()
{
EchoSystem.OnEchoReleased -= OnBell;
echoReleasedEvent?.Unregister(OnBell);
}
private void LateUpdate()
+27 -30
View File
@@ -1,11 +1,16 @@
using UnityEngine;
using IndianOceanAssets.Engine2_5D;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 敌人头顶血条 —— 挂在敌人 Prefab 上。
/// 血条可视化物体(BG、Fill)已在 Prefab 中预建,
/// 运行时直接引用,无需动态创建。
/// 血条可视化物体(BG、Fill)已在 Prefab 中预建,运行时直接引用,无需动态创建。
///
/// 解耦改造:直接引用同物体上的 HealthSystemRequireComponent 保证存在),
/// 订阅其 OnHealthChanged 事件刷新,彻底移除对私有字段的反射读取。
/// 注意:不使用"全局共享 IntVariable"承载敌人血量——否则所有敌人血条会显示同一份血量,
/// 正确的解法是"每个敌人读自己的 HealthSystem"(同物体组件引用,非 Find / 反射 / 跨对象)。
/// </summary>
[RequireComponent(typeof(HealthSystem))]
public class EnemyHealthBar : MonoBehaviour
@@ -33,52 +38,44 @@ namespace IndianOceanAssets.Engine2_5D
[Tooltip("满血时是否隐藏血条")]
[SerializeField] private bool hideWhenFull = false;
private HealthSystem _healthSystem;
private int _lastHealth = -1;
private int _maxHealth;
// 同物体上的 HealthSystemRequireComponent 保证存在;非 Find / 反射 / 跨对象引用)
private HealthSystem _healthSource;
private void Start()
{
_healthSystem = GetComponent<HealthSystem>();
if (_healthSystem == null)
if (fillRenderer == null || bgRenderer == null)
{
enabled = false;
return;
}
// 获取 maxHealth(通过反射读取私有字段)
var field = typeof(HealthSystem).GetField("maxHealth",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
_maxHealth = (int)field.GetValue(_healthSystem);
_healthSource = GetComponent<HealthSystem>();
if (_healthSource != null)
{
_healthSource.OnHealthChanged += UpdateBar;
UpdateBar(_healthSource.CurrentHealth, _healthSource.MaxHealth);
}
else
_maxHealth = 3;
UpdateBar();
{
UpdateBar(1, 1);
}
}
private void LateUpdate()
private void OnDestroy()
{
UpdateBar();
if (_healthSource != null)
_healthSource.OnHealthChanged -= UpdateBar;
}
/// <summary>
/// 根据当前血量更新血条显示。
/// 根据当前血量更新血条显示(事件驱动,无反射、无每帧 LateUpdate)
/// </summary>
private void UpdateBar()
private void UpdateBar(int currentHealth, int maxHealth)
{
if (_healthSystem == null || fillRenderer == null || bgRenderer == null) return;
if (fillRenderer == null || bgRenderer == null) return;
// 获取当前血量(反射)
var healthField = typeof(HealthSystem).GetField("health",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
int currentHealth = healthField != null ? (int)healthField.GetValue(_healthSystem) : _maxHealth;
// 血量没变化则跳过(性能优化)
if (currentHealth == _lastHealth) return;
_lastHealth = currentHealth;
float ratio = Mathf.Clamp01((float)currentHealth / _maxHealth);
if (maxHealth <= 0) maxHealth = 1;
float ratio = Mathf.Clamp01((float)currentHealth / maxHealth);
// 更新填充条缩放(居中对齐)
fillRenderer.transform.localScale = new Vector3(barWidth * ratio, barHeight, 1f);
+6
View File
@@ -439,6 +439,7 @@ MonoBehaviour:
maxSightDistance: 4
loseSightTime: 3
listenRange: 15
echoReleasedEvent: {fileID: 11400000, guid: 3f85d8e63d6b82342ba94a4b413c21fa, type: 2}
bellArriveDistance: 1
bellChaseTimeout: 10
bellPatrolTime: 5
@@ -477,11 +478,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f9e36f6d2e0ea0541bad29e784e0841b, type: 3}
m_Name:
m_EditorClassIdentifier:
onPlayerDamagedEvent: {fileID: 0}
onPlayerDiedEvent: {fileID: 0}
healthVar: {fileID: 0}
maxHealthVar: {fileID: 0}
maxHealth: 3
deathEffect: {fileID: 0}
isPlayer: 0
dissolveMaterial: {fileID: 2100000, guid: a2207e01903058a4198862e6a7fb73c7, type: 2}
fadeDuration: 2
damageInvincibleDuration: 2
--- !u!114 &1893771889199554109
MonoBehaviour:
m_ObjectHideFlags: 0
+6
View File
@@ -351,6 +351,7 @@ MonoBehaviour:
maxSightDistance: 4
loseSightTime: 3
listenRange: 15
echoReleasedEvent: {fileID: 11400000, guid: 3f85d8e63d6b82342ba94a4b413c21fa, type: 2}
bellArriveDistance: 1
bellChaseTimeout: 10
bellPatrolTime: 5
@@ -389,11 +390,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f9e36f6d2e0ea0541bad29e784e0841b, type: 3}
m_Name:
m_EditorClassIdentifier:
onPlayerDamagedEvent: {fileID: 0}
onPlayerDiedEvent: {fileID: 0}
healthVar: {fileID: 0}
maxHealthVar: {fileID: 0}
maxHealth: 3
deathEffect: {fileID: 0}
isPlayer: 0
dissolveMaterial: {fileID: 2100000, guid: a2207e01903058a4198862e6a7fb73c7, type: 2}
fadeDuration: 2
damageInvincibleDuration: 2
--- !u!114 &1893771889199554109
MonoBehaviour:
m_ObjectHideFlags: 0
+6
View File
@@ -351,6 +351,7 @@ MonoBehaviour:
maxSightDistance: 4
loseSightTime: 3
listenRange: 15
echoReleasedEvent: {fileID: 11400000, guid: 3f85d8e63d6b82342ba94a4b413c21fa, type: 2}
bellArriveDistance: 1
bellChaseTimeout: 10
bellPatrolTime: 5
@@ -389,11 +390,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f9e36f6d2e0ea0541bad29e784e0841b, type: 3}
m_Name:
m_EditorClassIdentifier:
onPlayerDamagedEvent: {fileID: 0}
onPlayerDiedEvent: {fileID: 0}
healthVar: {fileID: 0}
maxHealthVar: {fileID: 0}
maxHealth: 3
deathEffect: {fileID: 0}
isPlayer: 0
dissolveMaterial: {fileID: 2100000, guid: a2207e01903058a4198862e6a7fb73c7, type: 2}
fadeDuration: 2
damageInvincibleDuration: 2
--- !u!114 &1893771889199554109
MonoBehaviour:
m_ObjectHideFlags: 0
+6
View File
@@ -439,6 +439,7 @@ MonoBehaviour:
maxSightDistance: 4
loseSightTime: 3
listenRange: 15
echoReleasedEvent: {fileID: 11400000, guid: 3f85d8e63d6b82342ba94a4b413c21fa, type: 2}
bellArriveDistance: 1
bellChaseTimeout: 10
bellPatrolTime: 5
@@ -477,11 +478,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f9e36f6d2e0ea0541bad29e784e0841b, type: 3}
m_Name:
m_EditorClassIdentifier:
onPlayerDamagedEvent: {fileID: 0}
onPlayerDiedEvent: {fileID: 0}
healthVar: {fileID: 0}
maxHealthVar: {fileID: 0}
maxHealth: 3
deathEffect: {fileID: 0}
isPlayer: 0
dissolveMaterial: {fileID: 2100000, guid: a2207e01903058a4198862e6a7fb73c7, type: 2}
fadeDuration: 2
damageInvincibleDuration: 2
--- !u!114 &1893771889199554109
MonoBehaviour:
m_ObjectHideFlags: 0
+6
View File
@@ -351,6 +351,7 @@ MonoBehaviour:
maxSightDistance: 4
loseSightTime: 3
listenRange: 15
echoReleasedEvent: {fileID: 11400000, guid: 3f85d8e63d6b82342ba94a4b413c21fa, type: 2}
bellArriveDistance: 1
bellChaseTimeout: 10
bellPatrolTime: 5
@@ -389,11 +390,16 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f9e36f6d2e0ea0541bad29e784e0841b, type: 3}
m_Name:
m_EditorClassIdentifier:
onPlayerDamagedEvent: {fileID: 0}
onPlayerDiedEvent: {fileID: 0}
healthVar: {fileID: 0}
maxHealthVar: {fileID: 0}
maxHealth: 3
deathEffect: {fileID: 0}
isPlayer: 0
dissolveMaterial: {fileID: 2100000, guid: a2207e01903058a4198862e6a7fb73c7, type: 2}
fadeDuration: 2
damageInvincibleDuration: 2
--- !u!114 &1893771889199554109
MonoBehaviour:
m_ObjectHideFlags: 0