206 lines
8.3 KiB
C#

using UnityEngine;
using BulletHellTemplate.Core.Events;
using System.Threading;
using Cysharp.Threading.Tasks;
using System;
namespace BulletHellTemplate
{
/// <summary>
/// Handles character UI display logic (HP, MP, Buffs, Invincibility icons).
/// Listens to events via EventBus to remain decoupled from gameplay logic.
/// </summary>
[DisallowMultipleComponent]
public partial class CharacterUIHandlerComponent : MonoBehaviour
{
private CharacterEntity _entity;
private CancellationTokenSource _hpCTS;
private CancellationTokenSource _mpCTS;
#region Public ---------------------------------------------------------------------------
/// <summary>
/// Binds this UI handler to a character instance.
/// </summary>
public void Initialize(CharacterEntity entity)
{
_entity = entity;
// Subscribe to all relevant events (they are fired independently).
EventBus.Subscribe<PlayerHealthChangedEvent>(OnHp);
EventBus.Subscribe<PlayerEnergyChangedEvent>(OnMp);
EventBus.Subscribe<PlayerShieldChangedEvent>(OnShield);
EventBus.Subscribe<PlayerEXPChangeEvent>(OnLevel);
EventBus.Subscribe<PlayerInvincibleEvent>(OnInvincible);
EventBus.Subscribe<PlayerDiedEvent>(OnDeath);
EventBus.Subscribe<PlayerRevivedEvent>(OnRevive);
EventBus.Subscribe<BuffReceivedEvent>(OnBuffReceived);
EventBus.Subscribe<BuffRemovedEvent>(OnBuffRemoved);
EventBus.Subscribe<DebuffReceivedEvent>(OnDebuffReceived);
EventBus.Subscribe<DebuffRemovedEvent>(OnDebuffRemoved);
// Prime UI
var stats = entity.GetComponent<CharacterStatsComponent>();
if (stats != null)
{
SetHpImmediate(stats.CurrentHP, stats.MaxHp);
SetMpImmediate(stats.CurrentMP, stats.MaxMp);
SetShieldImmediate(stats.CurrentShield, stats.MaxHp);
}
}
private void Awake()
{
_entity = GetComponent<CharacterEntity>();
}
private void OnDestroy()
{
EventBus.Unsubscribe<PlayerHealthChangedEvent>(OnHp);
EventBus.Unsubscribe<PlayerEnergyChangedEvent>(OnMp);
EventBus.Unsubscribe<PlayerShieldChangedEvent>(OnShield);
EventBus.Unsubscribe<PlayerEXPChangeEvent>(OnLevel);
EventBus.Unsubscribe<PlayerInvincibleEvent>(OnInvincible);
EventBus.Unsubscribe<PlayerDiedEvent>(OnDeath);
EventBus.Unsubscribe<PlayerRevivedEvent>(OnRevive);
EventBus.Unsubscribe<BuffReceivedEvent>(OnBuffReceived);
EventBus.Unsubscribe<BuffRemovedEvent>(OnBuffRemoved);
EventBus.Unsubscribe<DebuffReceivedEvent>(OnDebuffReceived);
EventBus.Unsubscribe<DebuffRemovedEvent>(OnDebuffRemoved);
_hpCTS?.Cancel(); _mpCTS?.Cancel();
}
#endregion
#region Event Callbacks ------------------------------------------------------------------
private void OnHp(PlayerHealthChangedEvent e)
{
if (e.Target != _entity) return;
_hpCTS?.Cancel();
_hpCTS = new();
SetHpImmediate(e.CurrentHP, e.MaxHP);
ShrinkDelayedBarAsync(e.CurrentHP / e.MaxHP, _hpCTS.Token).Forget();
}
private void OnMp(PlayerEnergyChangedEvent e)
{
if (e.Target != _entity) return;
_mpCTS?.Cancel();
_mpCTS = new();
LerpMpAsync(e.CurrentMP / e.MaxMP, _mpCTS.Token).Forget();
}
private void OnShield(PlayerShieldChangedEvent e)
{
if (e.Target != _entity) return;
SetShieldImmediate(e.CurrentShield, e.MaxHP);
_entity.shieldIcon?.gameObject.SetActive(e.CurrentShield > 0f);
}
private void OnLevel(PlayerEXPChangeEvent e)
{
if (e.Target != _entity) return;
if (_entity.level) _entity.level.text = e.NewLevel.ToString();
}
private void OnInvincible(PlayerInvincibleEvent e)
{
if (e.character != _entity) return;
if (_entity.invincibleIcon == null) return;
_entity.invincibleIcon.gameObject.SetActive(true);
CancelInvoke(nameof(HideInv));
Invoke(nameof(HideInv), e.duration);
}
private void OnDeath(PlayerDiedEvent e)
{
if (e.Target != _entity) return;
_hpCTS?.Cancel(); _mpCTS?.Cancel();
SetHpImmediate(0, Mathf.Max(1f, _entity.GetMaxHP()));
SetMpImmediate(0, Mathf.Max(1f, _entity.GetMaxMP()));
SetShieldImmediate(0, Mathf.Max(1f, _entity.GetMaxHP()));
_entity.invincibleIcon?.gameObject.SetActive(false);
_entity.buffIcon?.gameObject.SetActive(false);
_entity.debuffIcon?.gameObject.SetActive(false);
if (GameplayManager.Singleton.ActiveCharactersList.Count <= 0)
{
GameplayManager.Singleton.PauseGame();
UIGameplay.Singleton.DisplayEndGameScreen(false);
}
}
private void OnRevive(PlayerRevivedEvent e)
{
if (e.character != _entity) return;
_hpCTS?.Cancel(); _mpCTS?.Cancel();
SetHpImmediate(_entity.GetCurrentHP(), _entity.GetMaxHP());
SetMpImmediate(_entity.GetCurrentMP(), _entity.GetMaxMP());
SetShieldImmediate(_entity.GetCurrentShield(), _entity.GetMaxHP());
_entity.invincibleIcon?.gameObject.SetActive(false);
_entity.buffIcon?.gameObject.SetActive(false);
_entity.debuffIcon?.gameObject.SetActive(false);
}
private void OnBuffReceived(BuffReceivedEvent e) { if (e.character == _entity && e.activeBuffsAmount > 0) _entity.buffIcon?.gameObject.SetActive(true); }
private void OnBuffRemoved(BuffRemovedEvent e) { if (e.Target == _entity && e.RemainingBuffsCount == 0) _entity.buffIcon?.gameObject.SetActive(false); }
private void OnDebuffReceived(DebuffReceivedEvent e) { if (e.character == _entity && e.activeDebuffsAmount > 0) _entity.debuffIcon?.gameObject.SetActive(true); }
private void OnDebuffRemoved(DebuffRemovedEvent e) { if (e.Target == _entity && e.RemainingDebuffsCount == 0) _entity.debuffIcon?.gameObject.SetActive(false); }
#endregion
#region UI Helpers -----------------------------------------------------------------------
public void SetHpImmediate(float cur, float max)
{
_entity.hpBar.fillAmount = cur / max;
_entity.hpDecreaseBar.fillAmount = cur / max;
if (_entity.HpText) _entity.HpText.text = Mathf.CeilToInt(cur).ToString();
}
public void SetMpImmediate(float cur, float max) =>
_entity.mpBar.fillAmount = max > 0 ? cur / max : 0f;
public void SetShieldImmediate(float shield, float max) =>
_entity.shieldBar.fillAmount = max > 0 ? shield / max : 0f;
private async UniTaskVoid ShrinkDelayedBarAsync(float target, CancellationToken ct)
{
try
{
await UniTask.Delay(TimeSpan.FromSeconds(_entity.decreaseHpBarDelay), cancellationToken: ct);
while (_entity.hpDecreaseBar.fillAmount - target > 0.001f)
{
_entity.hpDecreaseBar.fillAmount -= Time.deltaTime * _entity.decreaseHPBarSpeed;
await UniTask.Yield(ct);
}
_entity.hpDecreaseBar.fillAmount = target;
}
catch (OperationCanceledException) { }
}
private async UniTaskVoid LerpMpAsync(float target, CancellationToken ct)
{
try
{
while (Mathf.Abs(_entity.mpBar.fillAmount - target) > 0.001f)
{
_entity.mpBar.fillAmount =
Mathf.MoveTowards(_entity.mpBar.fillAmount, target, Time.deltaTime * _entity.mpLerpSpeed);
await UniTask.Yield(ct);
}
_entity.mpBar.fillAmount = target;
}
catch (OperationCanceledException) { }
}
private void HideInv() => _entity.invincibleIcon?.gameObject.SetActive(false);
#endregion
}
}