Files
5.24BuYu/Assets/MyGame/Framework/Pool/BulletPool.cs
T
2026-10-15 03:57:20 +08:00

206 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 子弹专用对象池 — 按 prefab.GetInstanceID() 分桶,与鱼的 Pool 完全隔离
/// 用法:
/// var go = BulletPool.Instance.Get(prefab, pos, rot, parent);
/// BulletPool.Instance.Return(go); // 代替 Destroy(单个回收)
/// BulletPool.Instance.ClearAll(); // 换炮台/切场景调用:销毁所有活跃子弹 + 清池
/// prefabID 直接用 prefab.GetInstanceID()(Unity 内部 hash,全局唯一)
/// 活跃子弹→prefabID 的映射用 Dictionary<instanceID, prefabID> 存储,不挂组件不改 name
/// </summary>
public class BulletPool : Singleton<BulletPool>
{
/// <summary>子弹回池前的状态重置委托:由热更侧 BullitMove 注入,框架层不反向依赖热更类型</summary>
public static Action<GameObject> ResetBulletState;
// prefab.GetInstanceID() → 该 prefab 的池
private Dictionary<int, BulletPoolData> dic = new Dictionary<int, BulletPoolData>();
// 活跃子弹映射:子弹GameObject → prefab.GetInstanceID()
// key 用 GameObject 本身(Unity 重载了 GetHashCode/Equals,按实例区分)
// value 是 prefabID,回收时凭此找到对应池
private Dictionary<GameObject, int> activeMap = new Dictionary<GameObject, int>();
// 每个池子的最大缓存数(超出直接 Destroy,防止内存膨胀)
private const int MaxCachePerPrefab = 200;
// 池子根节点(隐藏在场景里)
private GameObject poolRoot;
/// <summary>从池中取一个子弹(池空则 Instantiate)</summary>
public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent)
{
if (prefab == null) return null;
int prefabID = prefab.GetInstanceID();
GameObject obj = null;
if (dic.TryGetValue(prefabID, out var data))
{
// ⭐ 循环跳过已销毁的坏对象(ClearAll 用 Destroy 延迟销毁,时序竞争中可能残留)
while (data.poolList.Count > 0)
{
obj = data.GetObj();
if (obj != null) break; // Unity 重载 ==,能识别已 Destroy 的假 null
obj = null; // 坏对象丢弃,继续取下一个
}
}
if (obj != null)
{
// ⭐ 必须先 SetParent 再设 position:模仿 Instantiate(prefab, pos, rot, parent)
obj.transform.SetParent(parent, false);
obj.transform.position = position;
obj.transform.rotation = rotation;
obj.SetActive(true);
}
else
{
obj = GameObject.Instantiate(prefab, position, rotation, parent);
obj.SetActive(true);
}
// 记录映射:子弹实例 → prefabID(回收时凭此找到对应池)
activeMap[obj] = prefabID;
return obj;
}
/// <summary>无 parent 重载</summary>
public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation)
{
return Get(prefab, position, rotation, null);
}
/// <summary>回收子弹到池(超容量则 Destroy)</summary>
public void Return(GameObject obj)
{
if (obj == null) return;
// 取不到映射 → 非 pool 对象 → 兜底 Destroy
if (!activeMap.TryGetValue(obj, out int prefabID) || prefabID == 0)
{
GameObject.Destroy(obj);
return;
}
activeMap.Remove(obj);
if (!dic.TryGetValue(prefabID, out var data))
dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID);
if (data.poolList.Count >= MaxCachePerPrefab)
{
GameObject.Destroy(obj);
return;
}
// 重置子弹状态(由热更侧注入:通常是 BullitMove.ResetState)
ResetBulletState?.Invoke(obj);
data.PushObj(obj);
}
/// <summary>预加载 N 个某 prefab 到池</summary>
public void Preload(GameObject prefab, int count)
{
if (prefab == null || count <= 0) return;
int prefabID = prefab.GetInstanceID();
if (!dic.TryGetValue(prefabID, out var data))
dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID);
for (int i = 0; i < count; i++)
{
var obj = GameObject.Instantiate(prefab, EnsurePoolRoot().transform);
// 预加载的对象直接入池,不进 activeMap(它们没被"借出")
data.PushObj(obj);
}
}
/// <summary>销毁所有活跃子弹 + 清空所有池(换炮台/切场景调用)</summary>
public void ClearAll()
{
// 销毁所有借出未归还的子弹
foreach (var kv in activeMap.Keys)
{
if (kv != null) GameObject.Destroy(kv);
}
activeMap.Clear();
// 清空所有池
foreach (var kv in dic.Values)
kv.Clear();
dic.Clear();
if (poolRoot != null)
{
GameObject.Destroy(poolRoot);
poolRoot = null;
}
}
/// <summary>只销毁活跃子弹,保留池缓存(换炮台时调用:清屏但池能复用)</summary>
public void ClearActive()
{
foreach (var kv in activeMap.Keys)
{
if (kv != null) GameObject.Destroy(kv);
}
activeMap.Clear();
// ⭐ 不清 dic,池里的缓存保留,同 prefab 下次能复用
}
private GameObject EnsurePoolRoot()
{
if (poolRoot == null)
{
poolRoot = new GameObject("[BulletPool]");
poolRoot.SetActive(false); // 池根节点隐藏,池内对象跟随隐藏
}
return poolRoot;
}
}
/// <summary>单个 prefab 的子弹池数据</summary>
public class BulletPoolData
{
public Transform fatherObj;
public List<GameObject> poolList;
public BulletPoolData(Transform root, int id)
{
fatherObj = new GameObject("bulletPool_" + id).transform;
fatherObj.SetParent(root, false);
poolList = new List<GameObject>();
}
public void PushObj(GameObject obj)
{
if (obj == null) return;
poolList.Add(obj);
obj.transform.SetParent(fatherObj, false);
obj.SetActive(false);
}
public GameObject GetObj()
{
GameObject obj = poolList[poolList.Count - 1];
poolList.RemoveAt(poolList.Count - 1);
return obj;
}
public void Clear()
{
foreach (var obj in poolList)
{
if (obj != null) GameObject.Destroy(obj);
}
poolList.Clear();
if (fatherObj != null)
GameObject.Destroy(fatherObj.gameObject);
}
}