139 lines
4.8 KiB
C#
139 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using GameFramework;
|
|
|
|
namespace IndianOceanAssets.Engine2_5D
|
|
{
|
|
/// <summary>
|
|
/// 灵灯系统 —— 挂在玩家物体上。
|
|
/// 按 U 键在玩家当前位置放置灵灯,灵灯照亮周围区域,
|
|
/// 怪物碰触灵灯会被扣 1 点血,灵灯消失并被回收。
|
|
/// 灵灯有存在时间,超时后自动回收。
|
|
/// </summary>
|
|
public class SpiritLanternSystem : MonoBehaviour
|
|
{
|
|
[Header("灵灯数量")]
|
|
[Tooltip("最大可持有灵灯数量")]
|
|
[SerializeField] private int maxLanterns = 5;
|
|
|
|
[Header("按键")]
|
|
[SerializeField] private KeyCode lanternKey = KeyCode.U;
|
|
|
|
[Header("冷却")]
|
|
[Tooltip("放置灵灯的冷却时间(秒)")]
|
|
[SerializeField] private float cooldown = 5f;
|
|
|
|
[Header("灵灯预制体")]
|
|
[SerializeField] private GameObject lanternPrefab;
|
|
|
|
private int _remainingLanterns;
|
|
private float _lastPlaceTime = -999f;
|
|
|
|
/// <summary>跟踪所有已放置的灵灯,用于回收时取消事件订阅,防止内存泄漏</summary>
|
|
private readonly List<SpiritLantern> _activeLanterns = new List<SpiritLantern>();
|
|
|
|
/// <summary>当前可释放的灵灯数量(供 UI 使用)</summary>
|
|
public int RemainingLanterns => _remainingLanterns;
|
|
|
|
/// <summary>冷却是否就绪</summary>
|
|
public bool IsCooldownReady => Time.time > _lastPlaceTime + cooldown;
|
|
|
|
/// <summary>冷却剩余时间(供 UI 使用)</summary>
|
|
public float CooldownRemaining => Mathf.Max(0f, (_lastPlaceTime + cooldown) - Time.time);
|
|
|
|
private void Start()
|
|
{
|
|
_remainingLanterns = maxLanterns;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// 按 U 且 CD 就绪 且 还有灵灯 → 放置
|
|
if (Input.GetKeyDown(lanternKey)
|
|
&& _remainingLanterns > 0
|
|
&& Time.time > _lastPlaceTime + cooldown
|
|
&& lanternPrefab != null)
|
|
{
|
|
PlaceLantern();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在玩家当前位置放置一个灵灯。
|
|
/// </summary>
|
|
private void PlaceLantern()
|
|
{
|
|
_remainingLanterns--;
|
|
_lastPlaceTime = Time.time;
|
|
|
|
// 播放灵灯放置音效
|
|
if (AudioManager.Instance != null)
|
|
AudioManager.Instance.PlaySFXFromResources("SFX/Lantern", 0.8f);
|
|
|
|
// 在玩家位置实例化灵灯
|
|
GameObject lanternObj = Instantiate(lanternPrefab, transform.position, Quaternion.identity);
|
|
SpiritLantern lantern = lanternObj.GetComponent<SpiritLantern>();
|
|
|
|
if (lantern != null)
|
|
{
|
|
// 设置所有者引用,用于回收时取消事件订阅
|
|
lantern.SetOwner(this);
|
|
// 订阅灵灯回收事件,并跟踪该灵灯
|
|
lantern.OnLanternRecalled += OnLanternRecalled;
|
|
_activeLanterns.Add(lantern);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 灵灯被回收时调用(怪物碰触或超时)。
|
|
/// 清理已销毁的灵灯引用,防止内存泄漏。
|
|
/// </summary>
|
|
private void OnLanternRecalled()
|
|
{
|
|
// 清理列表中已销毁的灵灯引用
|
|
_activeLanterns.RemoveAll(l => l == null);
|
|
_remainingLanterns = Mathf.Min(_remainingLanterns + 1, maxLanterns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显式取消订阅指定灵灯的事件并从跟踪列表移除。
|
|
/// 由 SpiritLantern 在销毁前调用,确保事件订阅被正确释放。
|
|
/// </summary>
|
|
public void UnregisterLantern(SpiritLantern lantern)
|
|
{
|
|
if (lantern != null)
|
|
{
|
|
lantern.OnLanternRecalled -= OnLanternRecalled;
|
|
_activeLanterns.Remove(lantern);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 安全网:玩家销毁或场景卸载时,清理所有灵灯的事件订阅。
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
foreach (var lantern in _activeLanterns)
|
|
{
|
|
if (lantern != null)
|
|
lantern.OnLanternRecalled -= OnLanternRecalled;
|
|
}
|
|
_activeLanterns.Clear();
|
|
}
|
|
|
|
// ===== 编辑器可视化 =====
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
// 绘制冷却指示
|
|
if (Time.time < _lastPlaceTime + cooldown)
|
|
{
|
|
float remaining = (_lastPlaceTime + cooldown) - Time.time;
|
|
float ratio = remaining / cooldown;
|
|
Gizmos.color = new Color(1f, 0.6f, 0f, 0.3f);
|
|
Gizmos.DrawWireSphere(transform.position, 1f * ratio);
|
|
}
|
|
}
|
|
}
|
|
}
|