2026-07-11 17:07:32 +08:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-07-13 04:48:18 +08:00
|
|
|
/// 子弹专用对象池 — 按 prefab.GetInstanceID() 分桶,与鱼的 Pool 完全隔离
|
2026-07-11 17:07:32 +08:00
|
|
|
/// 用法:
|
|
|
|
|
/// var go = BulletPool.Instance.Get(prefab, pos, rot, parent);
|
2026-07-13 04:48:18 +08:00
|
|
|
/// BulletPool.Instance.Return(go); // 代替 Destroy(单个回收)
|
|
|
|
|
/// BulletPool.Instance.ClearAll(); // 换炮台/切场景调用:销毁所有活跃子弹 + 清池
|
|
|
|
|
/// prefabID 直接用 prefab.GetInstanceID()(Unity 内部 hash,全局唯一)
|
|
|
|
|
/// 活跃子弹→prefabID 的映射用 Dictionary<instanceID, prefabID> 存储,不挂组件不改 name
|
2026-07-11 17:07:32 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public class BulletPool : Singleton<BulletPool>
|
|
|
|
|
{
|
|
|
|
|
// prefab.GetInstanceID() → 该 prefab 的池
|
|
|
|
|
private Dictionary<int, BulletPoolData> dic = new Dictionary<int, BulletPoolData>();
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
// 活跃子弹映射:子弹GameObject → prefab.GetInstanceID()
|
|
|
|
|
// key 用 GameObject 本身(Unity 重载了 GetHashCode/Equals,按实例区分)
|
|
|
|
|
// value 是 prefabID,回收时凭此找到对应池
|
|
|
|
|
private Dictionary<GameObject, int> activeMap = new Dictionary<GameObject, int>();
|
|
|
|
|
|
2026-07-11 17:07:32 +08:00
|
|
|
// 每个池子的最大缓存数(超出直接 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;
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
int prefabID = prefab.GetInstanceID();
|
|
|
|
|
GameObject obj = null;
|
2026-07-11 17:07:32 +08:00
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
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)
|
2026-07-11 17:07:32 +08:00
|
|
|
{
|
2026-07-11 19:05:27 +08:00
|
|
|
// ⭐ 必须先 SetParent 再设 position:模仿 Instantiate(prefab, pos, rot, parent)
|
|
|
|
|
obj.transform.SetParent(parent, false);
|
2026-07-11 17:07:32 +08:00
|
|
|
obj.transform.position = position;
|
|
|
|
|
obj.transform.rotation = rotation;
|
|
|
|
|
obj.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
obj = GameObject.Instantiate(prefab, position, rotation, parent);
|
|
|
|
|
obj.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
// 记录映射:子弹实例 → prefabID(回收时凭此找到对应池)
|
|
|
|
|
activeMap[obj] = prefabID;
|
2026-07-11 17:07:32 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
// 取不到映射 → 非 pool 对象 → 兜底 Destroy
|
|
|
|
|
if (!activeMap.TryGetValue(obj, out int prefabID) || prefabID == 0)
|
2026-07-11 17:07:32 +08:00
|
|
|
{
|
|
|
|
|
GameObject.Destroy(obj);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-07-13 04:48:18 +08:00
|
|
|
activeMap.Remove(obj);
|
2026-07-11 17:07:32 +08:00
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
if (!dic.TryGetValue(prefabID, out var data))
|
|
|
|
|
dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID);
|
2026-07-11 17:07:32 +08:00
|
|
|
|
|
|
|
|
if (data.poolList.Count >= MaxCachePerPrefab)
|
|
|
|
|
{
|
|
|
|
|
GameObject.Destroy(obj);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置子弹状态(若有 BullitMove 组件)
|
|
|
|
|
var bm = obj.GetComponent<BullitMove>();
|
|
|
|
|
if (bm != null) bm.ResetState();
|
|
|
|
|
|
|
|
|
|
data.PushObj(obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>预加载 N 个某 prefab 到池</summary>
|
|
|
|
|
public void Preload(GameObject prefab, int count)
|
|
|
|
|
{
|
|
|
|
|
if (prefab == null || count <= 0) return;
|
2026-07-13 04:48:18 +08:00
|
|
|
int prefabID = prefab.GetInstanceID();
|
2026-07-11 17:07:32 +08:00
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
if (!dic.TryGetValue(prefabID, out var data))
|
|
|
|
|
dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID);
|
2026-07-11 17:07:32 +08:00
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
var obj = GameObject.Instantiate(prefab, EnsurePoolRoot().transform);
|
2026-07-13 04:48:18 +08:00
|
|
|
// 预加载的对象直接入池,不进 activeMap(它们没被"借出")
|
2026-07-11 17:07:32 +08:00
|
|
|
data.PushObj(obj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
/// <summary>销毁所有活跃子弹 + 清空所有池(换炮台/切场景调用)</summary>
|
|
|
|
|
public void ClearAll()
|
2026-07-11 17:07:32 +08:00
|
|
|
{
|
2026-07-13 04:48:18 +08:00
|
|
|
// 销毁所有借出未归还的子弹
|
|
|
|
|
foreach (var kv in activeMap.Keys)
|
|
|
|
|
{
|
|
|
|
|
if (kv != null) GameObject.Destroy(kv);
|
|
|
|
|
}
|
|
|
|
|
activeMap.Clear();
|
|
|
|
|
|
|
|
|
|
// 清空所有池
|
2026-07-11 17:07:32 +08:00
|
|
|
foreach (var kv in dic.Values)
|
|
|
|
|
kv.Clear();
|
|
|
|
|
dic.Clear();
|
|
|
|
|
|
|
|
|
|
if (poolRoot != null)
|
|
|
|
|
{
|
|
|
|
|
GameObject.Destroy(poolRoot);
|
|
|
|
|
poolRoot = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 04:48:18 +08:00
|
|
|
/// <summary>只销毁活跃子弹,保留池缓存(换炮台时调用:清屏但池能复用)</summary>
|
|
|
|
|
public void ClearActive()
|
|
|
|
|
{
|
|
|
|
|
foreach (var kv in activeMap.Keys)
|
|
|
|
|
{
|
|
|
|
|
if (kv != null) GameObject.Destroy(kv);
|
|
|
|
|
}
|
|
|
|
|
activeMap.Clear();
|
|
|
|
|
// ⭐ 不清 dic,池里的缓存保留,同 prefab 下次能复用
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 17:07:32 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|