feat: Add prefabs pooling manager.
fix: update spawn NPC.
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f809da92f7a74929fe4911b38ecddcf5bfe0fa9f667ad04f6c927511246975ae
|
||||
size 308267
|
||||
oid sha256:b4fd514f16e0e1d3b0903d7e8ba0ba96ba7a526a14f48d12174c552f9ff0b284
|
||||
size 310807
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace BrewMonster
|
||||
|
||||
public RIDINGPET m_RidingPet; // Riding pet information
|
||||
public GameObject m_pPetModel = null; // Pet model
|
||||
public GameObject m_PetModelVisual = null;
|
||||
public RIDINGPET m_CandPet;// ID of candidate pet
|
||||
A3DVECTOR3 m_vNamePos; // Æï³Ë×´Ì¬Íæ¼ÒÐÕÃûµÄµ÷Õû
|
||||
// ÒÀ¸½ÀàÐÍ
|
||||
@@ -3146,6 +3147,7 @@ namespace BrewMonster
|
||||
{
|
||||
GameObject.Destroy(m_pPetModel);
|
||||
m_pPetModel = null;
|
||||
PoolManager.Instance.Despawn(m_PetModelVisual);
|
||||
}
|
||||
|
||||
if (bResetData)
|
||||
@@ -3301,21 +3303,21 @@ namespace BrewMonster
|
||||
}
|
||||
try
|
||||
{
|
||||
var model = await AddressableManager.Instance.LoadPrefabAsync(AFile.NormalizePath(szPetPath.ToLower(), true));
|
||||
if(model == null)
|
||||
m_PetModelVisual = await PoolManager.Instance.SpawnAsync(AFile.NormalizePath(szPetPath.ToLower(), true), Vector3.zero, Quaternion.identity, 15f);
|
||||
if(m_PetModelVisual == null)
|
||||
{
|
||||
model = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
m_PetModelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
}
|
||||
var obModel = GameObject.Instantiate(model);
|
||||
obModel.transform.SetParent(pPetModel.transform);
|
||||
AddressableManager.Instance.ReleaseAsset(szPetPath);
|
||||
//var obModel = GameObject.Instantiate(model);
|
||||
m_PetModelVisual.transform.SetParent(pPetModel.transform);
|
||||
//AddressableManager.Instance.ReleaseAsset(szPetPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var model = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
var obModel = GameObject.Instantiate(model);
|
||||
obModel.transform.SetParent(pPetModel.transform);
|
||||
AddressableManager.Instance.ReleaseAsset(szPetPath);
|
||||
m_PetModelVisual = GameObject.Instantiate(model);
|
||||
m_PetModelVisual.transform.SetParent(pPetModel.transform);
|
||||
//AddressableManager.Instance.ReleaseAsset(szPetPath);
|
||||
//return null;
|
||||
}
|
||||
return pPetModel;
|
||||
|
||||
@@ -53,6 +53,7 @@ public class CECNPC : CECObject
|
||||
[SerializeField] protected CharacterController _characterController;
|
||||
[SerializeField] protected bool isDebug;
|
||||
[SerializeField] protected NPCVisual npcVisual;
|
||||
GameObject m_modelVisual = null;
|
||||
|
||||
protected static CECStringTab m_ActionNames;
|
||||
/* public string NameNPC => m_strName;
|
||||
@@ -579,7 +580,8 @@ public class CECNPC : CECObject
|
||||
|
||||
public void DestroySelf()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
PrefabPoolManager.Instance.Despawn(gameObject);
|
||||
//Destroy(gameObject);
|
||||
}
|
||||
public float GetTransparentLimit()
|
||||
{
|
||||
@@ -656,7 +658,7 @@ public class CECNPC : CECObject
|
||||
m_aIconStates.clear();*/
|
||||
|
||||
m_pNPCModelPolicy = null;
|
||||
|
||||
PoolManager.Instance.Despawn(m_modelVisual);
|
||||
/*if (m_pPateName)
|
||||
{
|
||||
delete m_pPateName;
|
||||
@@ -996,26 +998,27 @@ public class CECNPC : CECObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
GameObject model = null;
|
||||
|
||||
try
|
||||
{
|
||||
szModelFile = AFile.NormalizePath(szModelFile.ToLower(), true);
|
||||
model = await NPCBuilder.Instance.GetModelByPath(szModelFile);
|
||||
if (model == null)
|
||||
m_modelVisual = await NPCBuilder.Instance.GetModelByPath(szModelFile);
|
||||
if (m_modelVisual == null)
|
||||
{
|
||||
model = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
model.name = szModelFile;
|
||||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
m_modelVisual.name = szModelFile;
|
||||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
model = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
m_modelVisual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
BMLogger.LogWarning($" CECNPC.QueueLoadNPCModel model == null szModelFile= {szModelFile} ");
|
||||
}
|
||||
|
||||
var monsterModel = Instantiate(model, transform);
|
||||
monsterModel.SetActive(true);
|
||||
//var monsterModel = Instantiate(model, transform);
|
||||
m_modelVisual.transform.SetParent(transform, false);
|
||||
m_modelVisual.SetActive(true);
|
||||
var npcVisual = GetComponent<NPCVisual>();
|
||||
npcVisual?.InitNPCEventDoneHandler(m_NPCInfo);
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ public class NPCBuilder : MonoSingleton<NPCBuilder>
|
||||
|
||||
public async Task<GameObject> GetModelByPath(string path)
|
||||
{
|
||||
return await AddressableManager.Instance.LoadPrefabAsync(AFile.NormalizePath(path));
|
||||
//return await AddressableManager.Instance.LoadPrefabAsync(AFile.NormalizePath(path));
|
||||
return await PoolManager.Instance.SpawnAsync(AFile.NormalizePath(path), Vector3.zero, Quaternion.identity, memoryReleaseTTL: 15f, autoDespawnTime: 0f);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -166,6 +166,7 @@ namespace PerfectWorld.Scripts
|
||||
if (matterObject != null)
|
||||
{
|
||||
//var matterObject = Instantiate(matterPrefab);
|
||||
matterObject.transform.SetParent(ObjectSpawner.Instance.transform);
|
||||
matterObject.name = $"Matter {matterObject.name} {matterInfo.tid} {matterInfo.mid}";
|
||||
matterObject.transform.position = new Vector3(Info.pos.x, Info.pos.y, Info.pos.z);
|
||||
matterObject.transform.localScale = new Vector3(1f, 1f, 1f);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a576b0dd1ca5e0c439a1fb60f057d339
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c4e2151efbb02540962f141c9a987cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,208 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized prefab pooling manager for reusable GameObject instances.
|
||||
/// </summary>
|
||||
///
|
||||
namespace BrewMonster.Scripts
|
||||
{
|
||||
public class PrefabPoolManager : MonoBehaviour
|
||||
{
|
||||
private static PrefabPoolManager instance;
|
||||
|
||||
private readonly Dictionary<GameObject, PoolRecord> poolsByPrefab = new();
|
||||
private readonly Dictionary<GameObject, PoolRecord> allInstances = new();
|
||||
private readonly Dictionary<GameObject, PoolRecord> activeInstances = new();
|
||||
|
||||
public static PrefabPoolManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = FindAnyObjectByType<PrefabPoolManager>();
|
||||
if (instance == null)
|
||||
{
|
||||
var managerObject = new GameObject(nameof(PrefabPoolManager));
|
||||
instance = managerObject.AddComponent<PrefabPoolManager>();
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitPool(GameObject prefab, int defaultCapacity = 10, int maxSize = 50)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[PrefabPoolManager] Cannot initialize a pool with a null prefab.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (poolsByPrefab.ContainsKey(prefab))
|
||||
return;
|
||||
|
||||
defaultCapacity = Mathf.Max(0, defaultCapacity);
|
||||
maxSize = Mathf.Max(1, maxSize);
|
||||
if (defaultCapacity > maxSize)
|
||||
defaultCapacity = maxSize;
|
||||
|
||||
var record = new PoolRecord
|
||||
{
|
||||
Prefab = prefab,
|
||||
Container = CreatePoolContainer(prefab)
|
||||
};
|
||||
|
||||
record.Pool = new ObjectPool<GameObject>(
|
||||
() => CreateInstance(record),
|
||||
obj => OnGetFromPool(record, obj),
|
||||
obj => OnReleaseToPool(record, obj),
|
||||
OnDestroyPooledObject,
|
||||
collectionCheck: Application.isEditor,
|
||||
defaultCapacity: defaultCapacity,
|
||||
maxSize: maxSize);
|
||||
|
||||
poolsByPrefab.Add(prefab, record);
|
||||
Prewarm(record, defaultCapacity);
|
||||
}
|
||||
|
||||
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent = null)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[PrefabPoolManager] Cannot spawn a null prefab.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!poolsByPrefab.TryGetValue(prefab, out PoolRecord record))
|
||||
{
|
||||
InitPool(prefab);
|
||||
record = poolsByPrefab[prefab];
|
||||
}
|
||||
|
||||
GameObject instanceObject = record.Pool.Get();
|
||||
instanceObject.transform.SetPositionAndRotation(position, rotation);
|
||||
if (parent != null)
|
||||
{
|
||||
instanceObject.transform.SetParent(parent);
|
||||
}
|
||||
return instanceObject;
|
||||
}
|
||||
|
||||
public void Despawn(GameObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return;
|
||||
|
||||
if (!activeInstances.TryGetValue(obj, out PoolRecord record))
|
||||
{
|
||||
if (allInstances.ContainsKey(obj))
|
||||
{
|
||||
Debug.LogWarning($"[PrefabPoolManager] Object '{obj.name}' has already been returned to the pool.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[PrefabPoolManager] Object '{obj.name}' was not spawned by PrefabPoolManager. Destroying it instead.");
|
||||
Destroy(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
record.Pool.Release(obj);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance != null && instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
instance = this;
|
||||
//DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (instance != this)
|
||||
return;
|
||||
|
||||
foreach (PoolRecord record in poolsByPrefab.Values)
|
||||
{
|
||||
record.Pool.Clear();
|
||||
}
|
||||
|
||||
poolsByPrefab.Clear();
|
||||
allInstances.Clear();
|
||||
activeInstances.Clear();
|
||||
instance = null;
|
||||
}
|
||||
|
||||
private Transform CreatePoolContainer(GameObject prefab)
|
||||
{
|
||||
var container = new GameObject($"{prefab.name}_Pool").transform;
|
||||
container.SetParent(transform);
|
||||
return container;
|
||||
}
|
||||
|
||||
private GameObject CreateInstance(PoolRecord record)
|
||||
{
|
||||
GameObject instanceObject = Instantiate(record.Prefab, record.Container);
|
||||
instanceObject.name = record.Prefab.name;
|
||||
instanceObject.SetActive(false);
|
||||
allInstances[instanceObject] = record;
|
||||
return instanceObject;
|
||||
}
|
||||
|
||||
private void OnGetFromPool(PoolRecord record, GameObject obj)
|
||||
{
|
||||
activeInstances[obj] = record;
|
||||
obj.transform.SetParent(null);
|
||||
obj.SetActive(true);
|
||||
}
|
||||
|
||||
private void OnReleaseToPool(PoolRecord record, GameObject obj)
|
||||
{
|
||||
activeInstances.Remove(obj);
|
||||
obj.SetActive(false);
|
||||
obj.transform.SetParent(record.Container);
|
||||
}
|
||||
|
||||
private void OnDestroyPooledObject(GameObject obj)
|
||||
{
|
||||
activeInstances.Remove(obj);
|
||||
allInstances.Remove(obj);
|
||||
|
||||
if (obj != null)
|
||||
Destroy(obj);
|
||||
}
|
||||
|
||||
private static void Prewarm(PoolRecord record, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
return;
|
||||
|
||||
var warmedObjects = new List<GameObject>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
warmedObjects.Add(record.Pool.Get());
|
||||
}
|
||||
|
||||
for (int i = 0; i < warmedObjects.Count; i++)
|
||||
{
|
||||
record.Pool.Release(warmedObjects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PoolRecord
|
||||
{
|
||||
public GameObject Prefab;
|
||||
public Transform Container;
|
||||
public ObjectPool<GameObject> Pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 868f373ec1cb5324f889aab8b44c8a76
|
||||
@@ -0,0 +1,23 @@
|
||||
# 🤖 AI SYSTEM INSTRUCTION: PrefabPoolManager Usage Rules
|
||||
|
||||
## 1. System Context
|
||||
- **Environment:** Unity 3D (C#)
|
||||
- **Target Platform:** Mobile (Android, iOS) - Requires strict optimization.
|
||||
- **Concept:** The project uses a centralized Object Pooling system called `PrefabPoolManager` (built on `UnityEngine.Pool.ObjectPool`).
|
||||
- **AI Task:** When writing scripts that involve creating or destroying GameObjects, the AI **MUST** use this manager instead of Unity's default methods.
|
||||
|
||||
---
|
||||
|
||||
## 2. Available API (Do NOT implement this, just use it)
|
||||
|
||||
The `PrefabPoolManager` is a Singleton accessible via `PrefabPoolManager.Instance`. It provides the following methods:
|
||||
|
||||
```csharp
|
||||
// Pre-warms a pool (Optional, used during loading)
|
||||
public void InitPool(GameObject prefab, int defaultCapacity = 10, int maxSize = 50);
|
||||
|
||||
// Spawns an object from the pool
|
||||
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation);
|
||||
|
||||
// Returns an object to the pool
|
||||
public void Despawn(GameObject obj);
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a0e8e7ca64846747807f6c55aa465d5
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -146,9 +146,13 @@ public partial class CECGameRun : ITickable
|
||||
BMLogger.LogWarning("CECGameRun::LoadPrefabs, Loading prefabs from Resources. Consider using Addressables for better performance and memory management.");
|
||||
_playerPrefab = Resources.Load<GameObject>(AddressResourceConfig.PlayerPrefab);
|
||||
_monsterPrefab = Resources.Load<GameObject>(AddressResourceConfig.MonsterPrefab);
|
||||
PrefabPoolManager.Instance.InitPool(_monsterPrefab, defaultCapacity: 200, maxSize: 250);
|
||||
_npcServerPrefab = Resources.Load<GameObject>(AddressResourceConfig.NpcServerPrefab);
|
||||
PrefabPoolManager.Instance.InitPool(_npcServerPrefab, defaultCapacity: 25, maxSize: 100);
|
||||
_petServerPrefab = Resources.Load<GameObject>(AddressResourceConfig.PetServerPrefab);
|
||||
PrefabPoolManager.Instance.InitPool(_petServerPrefab);
|
||||
_petMountServerPrefab = Resources.Load<GameObject>(AddressResourceConfig.PetMountServerPrefab);
|
||||
PrefabPoolManager.Instance.InitPool(_petMountServerPrefab);
|
||||
#if UNITY_EDITOR
|
||||
if (_playerPrefab == null)
|
||||
{
|
||||
@@ -394,7 +398,9 @@ public partial class CECGameRun : ITickable
|
||||
}
|
||||
public CECMonster GetMonster()
|
||||
{
|
||||
return ObjectSpawner.Instance.InstantiateObject(_monsterPrefab, setThisAsParent: true)
|
||||
//return ObjectSpawner.Instance.InstantiateObject(_monsterPrefab, setThisAsParent: true)
|
||||
// .GetComponent<CECMonster>();
|
||||
return PrefabPoolManager.Instance.Spawn(_monsterPrefab, Vector3.zero, Quaternion.identity, ObjectSpawner.Instance.transform)
|
||||
.GetComponent<CECMonster>();
|
||||
}
|
||||
public RoleInfo GetSelectedRoleInfo()
|
||||
@@ -742,13 +748,16 @@ public partial class CECGameRun : ITickable
|
||||
|
||||
public CECPet GetPet()
|
||||
{
|
||||
return ObjectSpawner.Instance.InstantiateObject(_petServerPrefab, setThisAsParent: true)
|
||||
.GetComponent<CECPet>();
|
||||
//return ObjectSpawner.Instance.InstantiateObject(_petServerPrefab, setThisAsParent: true)
|
||||
// .GetComponent<CECPet>();
|
||||
return PrefabPoolManager.Instance.Spawn(_petServerPrefab, Vector3.zero, Quaternion.identity, ObjectSpawner.Instance.transform)
|
||||
.GetComponent<CECPet>();
|
||||
}
|
||||
|
||||
public GameObject GetPetMount()
|
||||
{
|
||||
return ObjectSpawner.Instance.InstantiateObject(_petMountServerPrefab, setThisAsParent: true);
|
||||
//return ObjectSpawner.Instance.InstantiateObject(_petMountServerPrefab, setThisAsParent: true);
|
||||
return PrefabPoolManager.Instance.Spawn(_petMountServerPrefab, Vector3.zero, Quaternion.identity, ObjectSpawner.Instance.transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Prefabs Pooling Manager
|
||||
|
||||
## Purpose
|
||||
|
||||
`PrefabPoolManager` centralizes prefab spawning and despawning through `UnityEngine.Pool.ObjectPool`.
|
||||
Any gameplay code that repeatedly creates or removes prefab instances should use this manager instead of direct `Instantiate` and `Destroy` calls.
|
||||
|
||||
## Runtime Flow
|
||||
|
||||
1. A caller accesses `PrefabPoolManager.Instance`.
|
||||
2. If no manager exists in the scene, the singleton creates a `PrefabPoolManager` GameObject and marks it with `DontDestroyOnLoad`.
|
||||
3. The caller may pre-warm a prefab pool by calling:
|
||||
|
||||
```csharp
|
||||
PrefabPoolManager.Instance.InitPool(prefab, defaultCapacity, maxSize);
|
||||
```
|
||||
|
||||
4. `InitPool` creates one `ObjectPool<GameObject>` for the prefab and preloads inactive instances under a prefab-specific container.
|
||||
5. The caller spawns instances by calling:
|
||||
|
||||
```csharp
|
||||
GameObject obj = PrefabPoolManager.Instance.Spawn(prefab, position, rotation);
|
||||
```
|
||||
|
||||
6. `Spawn` initializes a pool automatically if the prefab has not been registered yet, gets an object from the pool, detaches it from the pool container, sets its transform, and activates it.
|
||||
7. The caller returns instances by calling:
|
||||
|
||||
```csharp
|
||||
PrefabPoolManager.Instance.Despawn(obj);
|
||||
```
|
||||
|
||||
8. `Despawn` releases the instance back to its original pool, disables it, and reparents it under the prefab pool container.
|
||||
9. If the pool exceeds `maxSize`, Unity's `ObjectPool` destroys extra released instances.
|
||||
|
||||
## API
|
||||
|
||||
```csharp
|
||||
public void InitPool(GameObject prefab, int defaultCapacity = 10, int maxSize = 50);
|
||||
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation);
|
||||
public void Despawn(GameObject obj);
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```csharp
|
||||
public class ProjectileSpawner : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject projectilePrefab;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
PrefabPoolManager.Instance.InitPool(projectilePrefab, 20, 100);
|
||||
}
|
||||
|
||||
public void Fire(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
GameObject projectile = PrefabPoolManager.Instance.Spawn(projectilePrefab, position, rotation);
|
||||
projectile.SetActive(true);
|
||||
}
|
||||
|
||||
public void Release(GameObject projectile)
|
||||
{
|
||||
PrefabPoolManager.Instance.Despawn(projectile);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Use `InitPool` during loading screens or scene setup for frequently spawned prefabs.
|
||||
- Use `Spawn` instead of `Instantiate`.
|
||||
- Use `Despawn` instead of `Destroy` for objects created through this manager.
|
||||
- Each prefab has its own independent pool and capacity limits.
|
||||
- The system is intended for mobile targets, where avoiding frequent allocations helps reduce frame spikes and garbage collection pressure.
|
||||
Reference in New Issue
Block a user