灵灯系统v0.1
This commit is contained in:
@@ -4,7 +4,7 @@ using System.Collections;
|
|||||||
namespace IndianOceanAssets.Engine2_5D
|
namespace IndianOceanAssets.Engine2_5D
|
||||||
{
|
{
|
||||||
// Ensures the GameObject has these essential components
|
// Ensures the GameObject has these essential components
|
||||||
[RequireComponent(typeof(HealthSystem), typeof(SwordAttack), typeof(ProjectileShooter))]
|
[RequireComponent(typeof(HealthSystem), typeof(SwordAttack), typeof(ProjectileShooter), typeof(SpiritLanternSystem))]
|
||||||
public class PlayerController : MonoBehaviour
|
public class PlayerController : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Movement")]
|
[Header("Movement")]
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEditor;
|
||||||
|
using IndianOceanAssets.Engine2_5D;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编辑器工具:一键创建灵灯 Prefab。
|
||||||
|
/// 菜单路径:Assets > 创建灵灯 Prefab
|
||||||
|
/// </summary>
|
||||||
|
public static class SpiritLanternPrefabCreator
|
||||||
|
{
|
||||||
|
[MenuItem("Assets/创建灵灯 Prefab")]
|
||||||
|
public static void Create()
|
||||||
|
{
|
||||||
|
// 创建根物体
|
||||||
|
GameObject root = new GameObject("SpiritLantern");
|
||||||
|
|
||||||
|
// SpriteRenderer — 灵灯外观(使用默认 Sprite,后续可替换为美术资源)
|
||||||
|
SpriteRenderer sr = root.AddComponent<SpriteRenderer>();
|
||||||
|
sr.color = new Color(1f, 0.85f, 0.3f, 0.9f); // 暖黄色灯光
|
||||||
|
sr.sortingOrder = 100; // 在遮罩之上渲染
|
||||||
|
|
||||||
|
// LightSource — 光照组件(复用现有光照系统,与主角遮罩效果一致)
|
||||||
|
LightSource lightSource = root.AddComponent<LightSource>();
|
||||||
|
SerializedObject so = new SerializedObject(lightSource);
|
||||||
|
so.FindProperty("radius").floatValue = 4f;
|
||||||
|
so.FindProperty("intensity").floatValue = 1f;
|
||||||
|
so.FindProperty("registerOnStart").boolValue = true;
|
||||||
|
so.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
// SpiritLantern — 灵灯行为脚本
|
||||||
|
root.AddComponent<SpiritLantern>();
|
||||||
|
|
||||||
|
// CircleCollider2D — 触发器,检测怪物接触
|
||||||
|
// 【碰撞检测说明】
|
||||||
|
// 灵灯是主动检测方:灵灯的 Trigger 检测进入的怪物 Collider2D
|
||||||
|
// 需确保 Edit > Project Settings > Physics 2D 碰撞矩阵中,
|
||||||
|
// 灵灯所在 Layer 与怪物 Layer 的交叉项已勾选
|
||||||
|
CircleCollider2D collider = root.AddComponent<CircleCollider2D>();
|
||||||
|
collider.isTrigger = true;
|
||||||
|
collider.radius = 0.5f;
|
||||||
|
|
||||||
|
// Rigidbody2D — 物理碰撞需要(Trigger 需要至少一方有 Rigidbody)
|
||||||
|
Rigidbody2D rb = root.AddComponent<Rigidbody2D>();
|
||||||
|
rb.isKinematic = true;
|
||||||
|
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||||
|
|
||||||
|
// 设置 Layer 为 Default
|
||||||
|
root.layer = LayerMask.NameToLayer("Default");
|
||||||
|
|
||||||
|
// 保存为 Prefab
|
||||||
|
string prefabPath = "Assets/eco/Prefabs/SpiritLantern.prefab";
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
||||||
|
|
||||||
|
// 清理场景中的临时物体
|
||||||
|
Object.DestroyImmediate(root);
|
||||||
|
|
||||||
|
Debug.Log($"[SpiritLantern] Prefab 已创建: {prefabPath}");
|
||||||
|
EditorUtility.PingAsset(AssetDatabase.LoadMainAssetAtPath(prefabPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace IndianOceanAssets.Engine2_5D
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 单个灵灯的行为组件 —— 挂在灵灯 Prefab 上。
|
||||||
|
/// 放置后照亮周围区域(通过 LightSource),
|
||||||
|
/// 怪物碰触时扣 1 点血并自毁回收,
|
||||||
|
/// 存在时间到期后自动回收。
|
||||||
|
///
|
||||||
|
/// 【碰撞检测说明】
|
||||||
|
/// 灵灯自身持有 CircleCollider2D (isTrigger=true),是主动检测方。
|
||||||
|
/// 当怪物的 Collider2D 进入灵灯的触发区域时,OnTriggerEnter2D 被调用。
|
||||||
|
/// 需确保 Physics2D 碰撞矩阵中,灵灯所在 Layer 与怪物 Layer 可互相触发。
|
||||||
|
/// 灵灯不依赖 whatIsEnemy LayerMask,而是直接用 Tag/Component 判定目标。
|
||||||
|
/// </summary>
|
||||||
|
public class SpiritLantern : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("存在时间")]
|
||||||
|
[Tooltip("灵灯放置后的存在时间(秒),到期自动回收")]
|
||||||
|
[SerializeField] private float lifetime = 10f;
|
||||||
|
|
||||||
|
[Header("伤害")]
|
||||||
|
[Tooltip("怪物碰触灵灯时受到的伤害")]
|
||||||
|
[SerializeField] private int damageOnContact = 1;
|
||||||
|
|
||||||
|
/// <summary>灵灯被回收时触发(被 SpiritLanternSystem 订阅)</summary>
|
||||||
|
public event Action OnLanternRecalled;
|
||||||
|
|
||||||
|
private bool _recalled;
|
||||||
|
private SpiritLanternSystem _ownerSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置灵灯的所有者系统引用,用于回收时显式取消事件订阅。
|
||||||
|
/// 由 SpiritLanternSystem.PlaceLantern() 调用。
|
||||||
|
/// </summary>
|
||||||
|
public void SetOwner(SpiritLanternSystem system)
|
||||||
|
{
|
||||||
|
_ownerSystem = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// 存在时间倒计时
|
||||||
|
lifetime -= Time.deltaTime;
|
||||||
|
if (lifetime <= 0f)
|
||||||
|
{
|
||||||
|
Recall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 怪物碰触灵灯时触发:扣血 + 回收灵灯。
|
||||||
|
/// 灵灯的 Trigger 主动检测进入的碰撞体,排除玩家后对怪物造成伤害。
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggerEnter2D(Collider2D other)
|
||||||
|
{
|
||||||
|
// 排除玩家(玩家 Tag 为 "Player"),避免放置时与玩家碰撞体误触发
|
||||||
|
if (other.CompareTag("Player"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 尝试获取怪物的 HealthSystem
|
||||||
|
HealthSystem health = other.GetComponent<HealthSystem>();
|
||||||
|
if (health != null)
|
||||||
|
{
|
||||||
|
health.Damage(damageOnContact);
|
||||||
|
Recall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 回收灵灯:显式取消事件订阅 → 触发通知 → 销毁 GameObject。
|
||||||
|
/// 使用 _recalled 标志防止重复触发。
|
||||||
|
/// </summary>
|
||||||
|
private void Recall()
|
||||||
|
{
|
||||||
|
if (_recalled) return;
|
||||||
|
_recalled = true;
|
||||||
|
|
||||||
|
// 1. 先通过 System 显式取消事件订阅,防止内存泄漏
|
||||||
|
if (_ownerSystem != null)
|
||||||
|
{
|
||||||
|
_ownerSystem.UnregisterLantern(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 通知其他可能的订阅者(如 UI 等)
|
||||||
|
OnLanternRecalled?.Invoke();
|
||||||
|
OnLanternRecalled = null;
|
||||||
|
|
||||||
|
// 3. 销毁灵灯 GameObject
|
||||||
|
Destroy(gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 安全网:如果灵灯被非正常销毁(场景卸载、外部 Destroy 等),
|
||||||
|
/// 确保事件仍然触发,避免 SpiritLanternSystem 的灵灯计数永久丢失。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (!_recalled)
|
||||||
|
{
|
||||||
|
_recalled = true;
|
||||||
|
|
||||||
|
// 先取消订阅,再触发通知
|
||||||
|
if (_ownerSystem != null)
|
||||||
|
{
|
||||||
|
_ownerSystem.UnregisterLantern(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnLanternRecalled?.Invoke();
|
||||||
|
OnLanternRecalled = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
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 = 3f;
|
||||||
|
|
||||||
|
[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;
|
||||||
|
|
||||||
|
// 在玩家位置实例化灵灯
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user