Implement Object Pooling for platform items and procedural enemies to optimize memory allocations
Подробный список изменений: - В компонент PlatformSpawnedContent добавлен указатель SourcePrefab на оригинальный префаб. - Переписаны методы создания и очистки в PlatformContentSpawner: при движении экрана вниз монеты, пружины и другое оборудование не уничтожаются, а деактивируются и переносятся в скрытый пул Platform Content Pool. При движении вверх объекты активируются из пула по их префабам. - Реализован аналогичный пулинг в ProceduralEnemySpawner для процедурных врагов с очисткой пула в OnDestroy при выгрузке сцены.
This commit is contained in:
parent
839a68aef1
commit
01a6f631ef
@ -22,12 +22,15 @@ namespace DanroJump.Gameplay
|
||||
[SerializeField] private Vector2 platformClearance = new(0.15f, 0.15f);
|
||||
[SerializeField] private LayerMask blockingMask = ~0;
|
||||
|
||||
private const string PoolRootName = "Procedural Enemy Pool";
|
||||
private readonly Collider2D[] overlapBuffer = new Collider2D[16];
|
||||
private readonly System.Collections.Generic.List<GameObject> aliveEnemies = new();
|
||||
private readonly System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.List<GameObject>> inactivePool = new();
|
||||
private DiContainer container;
|
||||
private Camera gameplayCamera;
|
||||
private IGameplayDifficultyProgression difficultyProgression;
|
||||
private System.Random random;
|
||||
private Transform poolRoot;
|
||||
private float nextSpawnY;
|
||||
private bool isInitialized;
|
||||
|
||||
@ -73,8 +76,52 @@ namespace DanroJump.Gameplay
|
||||
}
|
||||
}
|
||||
|
||||
private Transform GetPoolRoot()
|
||||
{
|
||||
if (poolRoot != null)
|
||||
{
|
||||
return poolRoot;
|
||||
}
|
||||
|
||||
var rootObject = GameObject.Find(PoolRootName);
|
||||
if (rootObject == null)
|
||||
{
|
||||
rootObject = new GameObject(PoolRootName);
|
||||
rootObject.SetActive(false);
|
||||
}
|
||||
|
||||
poolRoot = rootObject.transform;
|
||||
return poolRoot;
|
||||
}
|
||||
|
||||
private void Recycle(GameObject enemy)
|
||||
{
|
||||
if (enemy == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (enemy.TryGetComponent(out PlatformSpawnedContent marker) && marker.SourcePrefab != null)
|
||||
{
|
||||
enemy.SetActive(false);
|
||||
enemy.transform.SetParent(GetPoolRoot(), false);
|
||||
|
||||
var sourcePrefab = marker.SourcePrefab;
|
||||
if (!inactivePool.TryGetValue(sourcePrefab, out var list))
|
||||
{
|
||||
list = new System.Collections.Generic.List<GameObject>();
|
||||
inactivePool.Add(sourcePrefab, list);
|
||||
}
|
||||
list.Add(enemy);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет процедурных врагов ниже линии рецикла платформ.
|
||||
/// Удаляет процедурных врагов ниже линии рецикла платформ с утилизацией в пул.
|
||||
/// </summary>
|
||||
public void CleanupBelow(float worldY)
|
||||
{
|
||||
@ -89,7 +136,7 @@ namespace DanroJump.Gameplay
|
||||
|
||||
if (enemy.transform.position.y < worldY)
|
||||
{
|
||||
Destroy(enemy);
|
||||
Recycle(enemy);
|
||||
aliveEnemies.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
@ -142,8 +189,34 @@ namespace DanroJump.Gameplay
|
||||
|
||||
private void Spawn(GameObject prefab, Vector3 position)
|
||||
{
|
||||
var instance = container.InstantiatePrefab(prefab, position, Quaternion.identity, transform);
|
||||
instance.SetActive(true);
|
||||
GameObject instance = null;
|
||||
if (inactivePool.TryGetValue(prefab, out var list) && list.Count > 0)
|
||||
{
|
||||
var lastIndex = list.Count - 1;
|
||||
instance = list[lastIndex];
|
||||
list.RemoveAt(lastIndex);
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
instance.transform.SetParent(transform, false);
|
||||
instance.transform.position = position;
|
||||
instance.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = container.InstantiatePrefab(prefab, position, Quaternion.identity, transform);
|
||||
}
|
||||
|
||||
if (!instance.TryGetComponent(out PlatformSpawnedContent marker))
|
||||
{
|
||||
marker = instance.AddComponent<PlatformSpawnedContent>();
|
||||
}
|
||||
|
||||
marker.SourcePrefab = prefab;
|
||||
marker.ResetForPlatformSpawn();
|
||||
|
||||
aliveEnemies.Add(instance);
|
||||
}
|
||||
|
||||
@ -186,9 +259,17 @@ namespace DanroJump.Gameplay
|
||||
{
|
||||
for (var index = aliveEnemies.Count - 1; index >= 0; index--)
|
||||
{
|
||||
if (aliveEnemies[index] == null || !aliveEnemies[index].activeInHierarchy)
|
||||
var enemy = aliveEnemies[index];
|
||||
if (enemy == null)
|
||||
{
|
||||
aliveEnemies.RemoveAt(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!enemy.activeSelf || !enemy.activeInHierarchy)
|
||||
{
|
||||
Recycle(enemy);
|
||||
aliveEnemies.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,5 +283,30 @@ namespace DanroJump.Gameplay
|
||||
|
||||
return Mathf.Lerp(min, max, (float)(random?.NextDouble() ?? 0d));
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (var pair in inactivePool)
|
||||
{
|
||||
if (pair.Value != null)
|
||||
{
|
||||
foreach (var obj in pair.Value)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
Destroy(obj);
|
||||
}
|
||||
}
|
||||
pair.Value.Clear();
|
||||
}
|
||||
}
|
||||
inactivePool.Clear();
|
||||
|
||||
if (poolRoot != null)
|
||||
{
|
||||
Destroy(poolRoot.gameObject);
|
||||
poolRoot = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,15 @@ namespace DanroJump.Gameplay
|
||||
public sealed class PlatformContentSpawner
|
||||
{
|
||||
private const string EnemyRootName = "Enemy Pool";
|
||||
private const string PoolRootName = "Platform Content Pool";
|
||||
|
||||
private readonly DiContainer container;
|
||||
private readonly Dictionary<PlatformController, List<GameObject>> spawnedContentByPlatform = new();
|
||||
private readonly List<PlatformSpawnedContent> orphanMarkerBuffer = new();
|
||||
private readonly List<PlatformSpawnPoint> spawnPointCandidates = new();
|
||||
private readonly Dictionary<GameObject, List<GameObject>> inactivePool = new();
|
||||
private Transform enemyRoot;
|
||||
private Transform poolRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Получает DI-контейнер для создания prefab с injection.
|
||||
@ -25,6 +28,50 @@ namespace DanroJump.Gameplay
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
private Transform GetPoolRoot()
|
||||
{
|
||||
if (poolRoot != null)
|
||||
{
|
||||
return poolRoot;
|
||||
}
|
||||
|
||||
var rootObject = GameObject.Find(PoolRootName);
|
||||
if (rootObject == null)
|
||||
{
|
||||
rootObject = new GameObject(PoolRootName);
|
||||
rootObject.SetActive(false);
|
||||
}
|
||||
|
||||
poolRoot = rootObject.transform;
|
||||
return poolRoot;
|
||||
}
|
||||
|
||||
private void RecycleOrDestroy(GameObject content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.TryGetComponent(out PlatformSpawnedContent marker) && marker.SourcePrefab != null)
|
||||
{
|
||||
content.SetActive(false);
|
||||
content.transform.SetParent(GetPoolRoot(), false);
|
||||
|
||||
var sourcePrefab = marker.SourcePrefab;
|
||||
if (!inactivePool.TryGetValue(sourcePrefab, out var list))
|
||||
{
|
||||
list = new List<GameObject>();
|
||||
inactivePool.Add(sourcePrefab, list);
|
||||
}
|
||||
list.Add(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.Destroy(content);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает старый контент платформы и спавнит новый по правилам источника.
|
||||
/// </summary>
|
||||
@ -62,7 +109,7 @@ namespace DanroJump.Gameplay
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очищает контент всех отслеживаемых платформ.
|
||||
/// Очищает контент всех отслеживаемых платформ и полностью освобождает пул.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
@ -72,6 +119,28 @@ namespace DanroJump.Gameplay
|
||||
}
|
||||
|
||||
spawnedContentByPlatform.Clear();
|
||||
|
||||
foreach (var pair in inactivePool)
|
||||
{
|
||||
if (pair.Value != null)
|
||||
{
|
||||
foreach (var obj in pair.Value)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
}
|
||||
pair.Value.Clear();
|
||||
}
|
||||
}
|
||||
inactivePool.Clear();
|
||||
|
||||
if (poolRoot != null)
|
||||
{
|
||||
Object.Destroy(poolRoot.gameObject);
|
||||
poolRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -97,14 +166,33 @@ namespace DanroJump.Gameplay
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает конкретный prefab в точке спавна и привязывает оборудование к платформе.
|
||||
/// Создает конкретный prefab в точке спавна с поддержкой пулинга.
|
||||
/// </summary>
|
||||
private void Spawn(GameObject prefab, Transform parent, PlatformController platform)
|
||||
{
|
||||
var targetParent = parent != null ? parent : platform.transform;
|
||||
var isEnemy = EnemyPrefabRules.TryGetEnemy(prefab, out _);
|
||||
var instanceParent = isEnemy ? GetEnemyRoot(platform) : targetParent;
|
||||
var instance = container.InstantiatePrefab(prefab, targetParent.position, targetParent.rotation, instanceParent);
|
||||
|
||||
GameObject instance = null;
|
||||
if (inactivePool.TryGetValue(prefab, out var list) && list.Count > 0)
|
||||
{
|
||||
var lastIndex = list.Count - 1;
|
||||
instance = list[lastIndex];
|
||||
list.RemoveAt(lastIndex);
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
instance.transform.SetParent(instanceParent, false);
|
||||
instance.transform.SetPositionAndRotation(targetParent.position, targetParent.rotation);
|
||||
instance.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = container.InstantiatePrefab(prefab, targetParent.position, targetParent.rotation, instanceParent);
|
||||
}
|
||||
|
||||
if (isEnemy)
|
||||
{
|
||||
@ -117,6 +205,7 @@ namespace DanroJump.Gameplay
|
||||
marker = instance.AddComponent<PlatformSpawnedContent>();
|
||||
}
|
||||
|
||||
marker.SourcePrefab = prefab;
|
||||
marker.ResetForPlatformSpawn();
|
||||
|
||||
if (instance.TryGetComponent(out IPlatformEquipment equipment))
|
||||
@ -175,9 +264,9 @@ namespace DanroJump.Gameplay
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Уничтожает объекты из уже выделенного списка без освобождения самого списка.
|
||||
/// Утилизирует или уничтожает объекты из уже выделенного списка без освобождения самого списка.
|
||||
/// </summary>
|
||||
private static void ClearContentList(List<GameObject> spawnedContent)
|
||||
private void ClearContentList(List<GameObject> spawnedContent)
|
||||
{
|
||||
for (var index = spawnedContent.Count - 1; index >= 0; index--)
|
||||
{
|
||||
@ -194,13 +283,13 @@ namespace DanroJump.Gameplay
|
||||
continue;
|
||||
}
|
||||
|
||||
Object.Destroy(content);
|
||||
RecycleOrDestroy(content);
|
||||
spawnedContent.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fallback-очистка дочерних объектов, если словарь не содержит платформу.
|
||||
/// Fallback-очистка дочерних объектов с утилизацией в пул.
|
||||
/// </summary>
|
||||
private void ClearOrphanSpawnedContent(PlatformController platform)
|
||||
{
|
||||
@ -216,7 +305,7 @@ namespace DanroJump.Gameplay
|
||||
continue;
|
||||
}
|
||||
|
||||
Object.Destroy(marker.gameObject);
|
||||
RecycleOrDestroy(marker.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,12 @@ namespace DanroJump.Gameplay
|
||||
/// </summary>
|
||||
public bool IsDetachedFromPlatform { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на оригинальный префаб, из которого был инстанцирован данный объект.
|
||||
/// Используется для возврата объекта в нужную ветку пула.
|
||||
/// </summary>
|
||||
public GameObject SourcePrefab { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Помечает объект как больше не принадлежащий платформе.
|
||||
/// </summary>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user