diff --git a/Assets/Framework/Pool/BulletPool.cs b/Assets/Framework/Pool/BulletPool.cs
index a03eea56d..54cae1167 100644
--- a/Assets/Framework/Pool/BulletPool.cs
+++ b/Assets/Framework/Pool/BulletPool.cs
@@ -2,16 +2,24 @@ using System.Collections.Generic;
using UnityEngine;
///
-/// 子弹专用对象池 — 按 prefab 实例ID 分桶,与鱼的 Pool 完全隔离
+/// 子弹专用对象池 — 按 prefab.GetInstanceID() 分桶,与鱼的 Pool 完全隔离
/// 用法:
/// var go = BulletPool.Instance.Get(prefab, pos, rot, parent);
-/// BulletPool.Instance.Return(go); // 代替 Destroy
+/// BulletPool.Instance.Return(go); // 代替 Destroy(单个回收)
+/// BulletPool.Instance.ClearAll(); // 换炮台/切场景调用:销毁所有活跃子弹 + 清池
+/// prefabID 直接用 prefab.GetInstanceID()(Unity 内部 hash,全局唯一)
+/// 活跃子弹→prefabID 的映射用 Dictionary 存储,不挂组件不改 name
///
public class BulletPool : Singleton
{
// prefab.GetInstanceID() → 该 prefab 的池
private Dictionary dic = new Dictionary();
+ // 活跃子弹映射:子弹GameObject → prefab.GetInstanceID()
+ // key 用 GameObject 本身(Unity 重载了 GetHashCode/Equals,按实例区分)
+ // value 是 prefabID,回收时凭此找到对应池
+ private Dictionary activeMap = new Dictionary();
+
// 每个池子的最大缓存数(超出直接 Destroy,防止内存膨胀)
private const int MaxCachePerPrefab = 200;
@@ -23,15 +31,23 @@ public class BulletPool : Singleton
{
if (prefab == null) return null;
- int id = prefab.GetInstanceID();
- GameObject obj;
+ int prefabID = prefab.GetInstanceID();
+ GameObject obj = null;
- if (dic.TryGetValue(id, out var data) && data.poolList.Count > 0)
+ 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)
{
- obj = data.GetObj();
// ⭐ 必须先 SetParent 再设 position:模仿 Instantiate(prefab, pos, rot, parent)
- // 若先设 position 再 SetParent(parent, false),localPosition 会保持,
- // 换 parent 后世界位置偏移 → 子弹从错误位置飞出
obj.transform.SetParent(parent, false);
obj.transform.position = position;
obj.transform.rotation = rotation;
@@ -43,12 +59,8 @@ public class BulletPool : Singleton
obj.SetActive(true);
}
- // 确保挂上 tracker 记录 prefabID(回收时凭此找到对应池)
- var tracker = obj.GetComponent();
- if (tracker == null)
- tracker = obj.AddComponent();
- tracker.prefabID = id;
-
+ // 记录映射:子弹实例 → prefabID(回收时凭此找到对应池)
+ activeMap[obj] = prefabID;
return obj;
}
@@ -63,18 +75,16 @@ public class BulletPool : Singleton
{
if (obj == null) return;
- var tracker = obj.GetComponent();
- int id = tracker != null ? tracker.prefabID : 0;
-
- // 取不到 id → 兜底 Destroy(非池内对象)
- if (id == 0)
+ // 取不到映射 → 非 pool 对象 → 兜底 Destroy
+ if (!activeMap.TryGetValue(obj, out int prefabID) || prefabID == 0)
{
GameObject.Destroy(obj);
return;
}
+ activeMap.Remove(obj);
- if (!dic.TryGetValue(id, out var data))
- dic[id] = data = new BulletPoolData(EnsurePoolRoot().transform, id);
+ if (!dic.TryGetValue(prefabID, out var data))
+ dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID);
if (data.poolList.Count >= MaxCachePerPrefab)
{
@@ -93,25 +103,30 @@ public class BulletPool : Singleton
public void Preload(GameObject prefab, int count)
{
if (prefab == null || count <= 0) return;
- int id = prefab.GetInstanceID();
+ int prefabID = prefab.GetInstanceID();
- if (!dic.TryGetValue(id, out var data))
- dic[id] = data = new BulletPoolData(EnsurePoolRoot().transform, id);
+ 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);
- var tracker = obj.GetComponent();
- if (tracker == null)
- tracker = obj.AddComponent();
- tracker.prefabID = id;
+ // 预加载的对象直接入池,不进 activeMap(它们没被"借出")
data.PushObj(obj);
}
}
- /// 清理所有子弹池(切场景时调用)
- public void Clear()
+ /// 销毁所有活跃子弹 + 清空所有池(换炮台/切场景调用)
+ 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();
@@ -123,6 +138,17 @@ public class BulletPool : Singleton
}
}
+ /// 只销毁活跃子弹,保留池缓存(换炮台时调用:清屏但池能复用)
+ 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)
@@ -134,12 +160,6 @@ public class BulletPool : Singleton
}
}
-/// 挂在池内对象上,记录所属 prefabID(回收时凭此找到对应池)
-public class BulletPoolTracker : MonoBehaviour
-{
- public int prefabID;
-}
-
/// 单个 prefab 的子弹池数据
public class BulletPoolData
{
diff --git a/Assets/Framework/Pool/EffectPool.cs b/Assets/Framework/Pool/EffectPool.cs
new file mode 100644
index 000000000..870e37111
--- /dev/null
+++ b/Assets/Framework/Pool/EffectPool.cs
@@ -0,0 +1,171 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+///
+/// 特效专用对象池 — 按 prefab.GetInstanceID() 分桶
+/// 粒子播放完自动回池(通过 EffectAutoReturn 组件检测)
+/// 用法:
+/// var go = EffectPool.Instance.Get(prefab, pos, rot, parent);
+/// // 无需手动回收,EffectAutoReturn 会在粒子播完后自动回池
+/// EffectPool.Instance.ClearAll(); // 切场景时彻底清理
+///
+public class EffectPool : Singleton
+{
+ // prefab.GetInstanceID() → 该 prefab 的池
+ private Dictionary dic = new Dictionary();
+
+ // 活跃特效映射:特效GameObject → prefab.GetInstanceID()
+ private Dictionary activeMap = new Dictionary();
+
+ private const int MaxCachePerPrefab = 100;
+
+ private GameObject poolRoot;
+
+ /// 从池中取一个特效(池空则 Instantiate),取出后自动播放并在播完时回池
+ 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))
+ {
+ // 循环跳过已销毁的坏对象
+ while (data.poolList.Count > 0)
+ {
+ obj = data.GetObj();
+ if (obj != null) break;
+ obj = null;
+ }
+ }
+
+ if (obj != null)
+ {
+ 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);
+ }
+
+ activeMap[obj] = prefabID;
+
+ // 挂上自动回收组件(已挂则复用)
+ var autoReturn = obj.GetComponent();
+ if (autoReturn == null)
+ autoReturn = obj.AddComponent();
+ autoReturn.prefabID = prefabID;
+ autoReturn.Play();
+
+ return obj;
+ }
+
+ /// 回收特效到池(通常由 EffectAutoReturn 自动调用)
+ public void Return(GameObject obj)
+ {
+ if (obj == null) return;
+
+ 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;
+ }
+
+ data.PushObj(obj);
+ }
+
+ /// 销毁所有活跃特效 + 清空所有池(切场景调用)
+ 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;
+ }
+ }
+
+ private GameObject EnsurePoolRoot()
+ {
+ if (poolRoot == null)
+ {
+ poolRoot = new GameObject("[EffectPool]");
+ poolRoot.SetActive(false);
+ }
+ return poolRoot;
+ }
+}
+
+/// 挂在池内特效上,粒子播放完自动回池
+public class EffectAutoReturn : MonoBehaviour
+{
+ [HideInInspector] public int prefabID;
+
+ private ParticleSystem[] particles;
+ private float checkTimer;
+ private const float MinPlayTime = 0.3f; // 至少播 0.3s 再检测,避免第一帧 IsAlive=false 误判
+
+ /// 重新播放所有粒子
+ public void Play()
+ {
+ if (particles == null || particles.Length == 0)
+ particles = GetComponentsInChildren(true);
+
+ checkTimer = 0f;
+ foreach (var p in particles)
+ {
+ if (p != null)
+ {
+ p.Clear(true);
+ p.Play(true);
+ }
+ }
+ }
+
+ void Update()
+ {
+ checkTimer += Time.deltaTime;
+ if (checkTimer < MinPlayTime) return;
+
+ // 所有粒子都播完 → 回池
+ if (particles == null || particles.Length == 0)
+ {
+ EffectPool.Instance.Return(gameObject);
+ return;
+ }
+
+ bool anyAlive = false;
+ foreach (var p in particles)
+ {
+ if (p != null && p.IsAlive(true)) { anyAlive = true; break; }
+ }
+ if (!anyAlive)
+ {
+ EffectPool.Instance.Return(gameObject);
+ }
+ }
+}
diff --git a/Assets/Framework/Pool/EffectPool.cs.meta b/Assets/Framework/Pool/EffectPool.cs.meta
new file mode 100644
index 000000000..eecda9e23
--- /dev/null
+++ b/Assets/Framework/Pool/EffectPool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e095098a1433eb4408b49fa20449bcb3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Framework/Pool/Pool.cs b/Assets/Framework/Pool/Pool.cs
index a199af11b..ef89533b1 100644
--- a/Assets/Framework/Pool/Pool.cs
+++ b/Assets/Framework/Pool/Pool.cs
@@ -98,7 +98,13 @@ public class Pool : Singleton
//{
// rotation = temp.transform.rotation;
//}
- obj = Object.Instantiate(FishResourcesData.GetFishPrefab(id), position, rotation,parent) as GameObject;//没有就读取一个,不用存入字典
+ var prefab = FishResourcesData.GetFishPrefab(id);
+ if (prefab == null)
+ {
+ Debug.LogError($"[Pool] 找不到鱼 prefab, fishNumber={id}");
+ return null;
+ }
+ obj = Object.Instantiate(prefab, position, rotation,parent) as GameObject;//没有就读取一个,不用存入字典
obj.SetActive(true);
obj.name = id.ToString();
diff --git a/Assets/MyGame/Scenes/UIScene.unity b/Assets/MyGame/Scenes/UIScene.unity
index 263213e84..228a21b2e 100644
--- a/Assets/MyGame/Scenes/UIScene.unity
+++ b/Assets/MyGame/Scenes/UIScene.unity
@@ -10153,7 +10153,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: afce420be78fe7e45bf80ca49529e276, type: 3}
m_Name:
m_EditorClassIdentifier:
- enableBot: 0
+ enableBot: 1
gameConfig:
id:
isDelete: 0
diff --git a/Assets/MyGame/Scripts/Items/Bullits/BullitMove.cs b/Assets/MyGame/Scripts/Items/Bullits/BullitMove.cs
index 3c2e8e069..8e0956168 100644
--- a/Assets/MyGame/Scripts/Items/Bullits/BullitMove.cs
+++ b/Assets/MyGame/Scripts/Items/Bullits/BullitMove.cs
@@ -149,7 +149,7 @@ public class BullitMove : MonoBehaviour
isTriggerFirst = false;
if (effectPrefab != null)
{
- Instantiate(effectPrefab, transform.position, Quaternion.identity, transform.parent);
+ EffectPool.Instance.Get(effectPrefab, transform.position, Quaternion.identity, transform.parent);
}
if (userid == GameManager.Instance.user.id)
@@ -215,7 +215,7 @@ public class BullitMove : MonoBehaviour
{
if (effectPrefab != null)
{
- Instantiate(effectPrefab, transform.position, Quaternion.identity, transform.parent);
+ EffectPool.Instance.Get(effectPrefab, transform.position, Quaternion.identity, transform.parent);
}
if (userid == GameManager.Instance.user.id)
diff --git a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs
index 7c6a7daa1..2fe55d8db 100644
--- a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs
+++ b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs
@@ -1108,6 +1108,11 @@ public class CreateFishManager : MonoSingleton
try
{
var fish = Pool.Instance.Get(int.Parse(createFishData.fishNumber), new Vector3(9999, 9999, 0), Quaternion.identity, fishRoot);
+ if (fish == null)
+ {
+ Debug.LogError($"[CreateFishManager] Pool.Get 返回 null,跳过此鱼: {createFishData.fishNumber}");
+ return false;
+ }
var mc = fish.GetComponent() ?? fish.AddComponent();
mc.monsterData.fishTypeConfig = FishDatas[createFishData.fishNumber];
mc.guid = createFishData.guid;
diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs
index 4012c825d..743145842 100644
--- a/Assets/MyGame/Scripts/Turret/SitPosData.cs
+++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs
@@ -172,6 +172,10 @@ public class SitPosData : MonoBehaviour
///
public void LoadTurret(int turretNumber)
{
+ // ⭐ 换炮台时只清活跃子弹(清屏),保留池缓存(同 prefab 能复用)
+ // 用 ClearActive 而非 ClearAll:ClearAll 会清池导致永远不复用
+ BulletPool.Instance.ClearActive();
+
if (currentTurret != null)
{
Destroy(currentTurret.gameObject);
diff --git a/Assets/MyGame/aa/Game/DefaultBossControl.cs b/Assets/MyGame/aa/Game/DefaultBossControl.cs
index 7c589bf93..0652ebc10 100644
--- a/Assets/MyGame/aa/Game/DefaultBossControl.cs
+++ b/Assets/MyGame/aa/Game/DefaultBossControl.cs
@@ -50,7 +50,8 @@ public class DefaultBossControl : MonsterControl_BossBase
{
//GetComponent().enabled = false;
- currEffect_DieAward.SetActive(true);
+ if (currEffect_DieAward != null)
+ currEffect_DieAward.SetActive(true);
}
public override void OnMoveUpdata()
{
diff --git a/Assets/MyGame/aa/Game/FLZY/BOSS/JL/FLZY_JL_Boss.cs b/Assets/MyGame/aa/Game/FLZY/BOSS/JL/FLZY_JL_Boss.cs
index 3d44111a4..6b43a54af 100644
--- a/Assets/MyGame/aa/Game/FLZY/BOSS/JL/FLZY_JL_Boss.cs
+++ b/Assets/MyGame/aa/Game/FLZY/BOSS/JL/FLZY_JL_Boss.cs
@@ -37,7 +37,8 @@ public class FLZY_JL_Boss : MonsterControl_BossBase
{
GetComponent().enabled = false;
- currEffect_DieAward.SetActive(true);
+ if (currEffect_DieAward != null)
+ currEffect_DieAward.SetActive(true);
}
public override void DieFish(RoomKillNotice obj = null)
diff --git a/Assets/MyGame/aa/Game/FLZY/BOSS/ZDX/FLZY_ZDX_BOSS.cs b/Assets/MyGame/aa/Game/FLZY/BOSS/ZDX/FLZY_ZDX_BOSS.cs
index 21e68e5f3..24affb06a 100644
--- a/Assets/MyGame/aa/Game/FLZY/BOSS/ZDX/FLZY_ZDX_BOSS.cs
+++ b/Assets/MyGame/aa/Game/FLZY/BOSS/ZDX/FLZY_ZDX_BOSS.cs
@@ -47,6 +47,7 @@ public class FLZY_ZDX_BOSS : MonsterControl_BossBase
{
//GetComponent().enabled = false;
- currEffect_DieAward.SetActive(true);
+ if (currEffect_DieAward != null)
+ currEffect_DieAward.SetActive(true);
}
}
diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
index 8af3497a1..54892646d 100644
--- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
+++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
@@ -433,7 +433,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_FreezeFish, SyncGameData_FreezeFish);
EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_BotFireAngle, SyncGameData_BotFireAngle);
-
+ // ⭐ 离场清理:子弹池 + 特效池彻底清空,防止内存泄漏
+ BulletPool.Instance.ClearAll();
+ EffectPool.Instance.ClearAll();
}
public virtual void Start()
@@ -1408,7 +1410,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
else
{
- var lp = monster.lockPoint ?? monster.transform;
+ // 注意:Unity 序列化字段未赋值时是"假 null",?? 运算符无法识别,必须用 == null
+ var lp = (monster.lockPoint != null) ? monster.lockPoint : monster.transform;
pos = lp.position;
}
if (!hitPos.gameObject.activeInHierarchy)
@@ -1821,7 +1824,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (monster.colliderList != null && monster.colliderList.Count > 0 && data.colliderIndex != -1)
hitPos = monster.colliderList[data.colliderIndex];
else
- hitPos = monster.lockPoint ?? monster.transform;
+ hitPos = (monster.lockPoint != null) ? monster.lockPoint : monster.transform;
list_Sits[sync.sitPos].SetLockTarget(monster, hitPos);
}