diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyManagerComponent.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyManagerComponent.cs index dd7e285..a454ba7 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyManagerComponent.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyManagerComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using Cysharp.Threading.Tasks; using SepCore.DataTable; using SepCore.Definition; @@ -19,7 +20,7 @@ namespace SepCore.EnemyManager private EnemyRegistry _enemyRegistry; private EnemySpawnScheduler _spawnScheduler; - public List Enemies => _enemyRegistry?.Enemies; + public IReadOnlyCollection Enemies => _enemyRegistry?.Enemies; [SerializeField] private int _spawnEnemyMaxCount = 5000; [SerializeField] private int _spawnDistanceFromPlayer = 20; @@ -31,6 +32,7 @@ namespace SepCore.EnemyManager private Transform _player; private ISpawnPositionStrategy _spawnPositionStrategy; + private CancellationTokenSource _spawnCts; public float SpawnRateScale => _spawnScheduler?.SpawnRateScale ?? 1f; public float BattleDuration => _duration; @@ -44,6 +46,7 @@ namespace SepCore.EnemyManager _entity = GameEntry.Entity; _enemyRegistry = new EnemyRegistry(); _spawnScheduler = new EnemySpawnScheduler(); + _spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); } @@ -52,6 +55,8 @@ namespace SepCore.EnemyManager { GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); + _spawnCts?.Dispose(); + _spawnCts = null; _enemyRegistry = null; _spawnScheduler = null; _entity = null; @@ -73,6 +78,7 @@ namespace SepCore.EnemyManager public void OnUpdate(float elapseSeconds, float realElapseSeconds) { + _enemyRegistry.PruneInvalidEntries(); var spawnRequests = _spawnScheduler.Tick(elapseSeconds); foreach (var request in spawnRequests) { @@ -85,6 +91,10 @@ namespace SepCore.EnemyManager public void OnReset() { + _spawnCts.Cancel(); + _spawnCts.Dispose(); + _spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); + _spawnScheduler.Reset(); ClearEnemies(); _currentSpawnEnemyId = 0; @@ -100,14 +110,20 @@ namespace SepCore.EnemyManager if (_player == null) return; if (_enemyRegistry.Count >= _spawnEnemyMaxCount) return; - int entityPoolId = _currentSpawnEnemyId % _spawnEnemyMaxCount; - var enemyData = EntityDataFactory.Create(entityPoolId, enemyType, _currentLevel); + int entityId = _currentSpawnEnemyId++; + var enemyData = EntityDataFactory.Create(entityId, enemyType, _currentLevel); enemyData.Position = _spawnPositionStrategy.GetSpawnPosition(_player); - _currentSpawnEnemyId++; - EnemyBase enemy = await _entity.ShowEnemyAsync(enemyData); - if (enemy == null) + var ct = _spawnCts.Token; + var (isCanceled, enemy) = + await _entity.ShowEnemyAsync(enemyData, cancellationToken: ct).SuppressCancellationThrow(); + if (isCanceled || ct.IsCancellationRequested || enemy == null || !enemy.Available) { + if (enemy != null && enemy.Available) + { + _entity.HideEntity(enemy); + } + return; } @@ -115,12 +131,14 @@ namespace SepCore.EnemyManager { enemy.SetTarget(_player); } + _enemyRegistry.Register(enemy); } - public void ClearEnemies() + private void ClearEnemies() { - foreach (var enemy in _enemyRegistry.Enemies) + var enemies = new List(_enemyRegistry.Enemies); + foreach (var enemy in enemies) { if (enemy == null || !enemy.Available) continue; _entity.HideEntity(enemy); diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyRegistry.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyRegistry.cs index 93474f6..df23265 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyRegistry.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyRegistry.cs @@ -6,80 +6,58 @@ namespace SepCore.EnemyManager { public class EnemyRegistry { - private readonly List _enemies; private readonly Dictionary _enemyById; - public int Count { get; private set; } - public List Enemies => _enemies; + public int Count => _enemyById.Count; + public IReadOnlyCollection Enemies => _enemyById.Values; public EnemyRegistry() { - _enemies = new List(); _enemyById = new Dictionary(); } public void Register(EnemyBase enemy) { if (enemy == null) return; - - Count++; - RemoveFromCache(enemy.Id); - _enemies.Add(enemy); _enemyById[enemy.Id] = enemy; } public void Remove(int entityId) { - if (Count > 0) + if (!_enemyById.ContainsKey(entityId)) { - Count--; + Debug.LogWarning($"EnemyRegistry: Attempt to remove non-existent entity id={entityId}"); + return; } - RemoveFromCache(entityId); + _enemyById.Remove(entityId); } public bool TryGet(int entityId, out EntityBase enemy) { - enemy = null; - if (!_enemyById.TryGetValue(entityId, out EntityBase cachedEnemy)) + return _enemyById.TryGetValue(entityId, out enemy); + } + + public void PruneInvalidEntries() + { + var invalidIds = new List(); + foreach (var kvp in _enemyById) { - return false; + if (kvp.Value == null || !kvp.Value.Available) + { + invalidIds.Add(kvp.Key); + } } - if (cachedEnemy == null || !cachedEnemy.Available) + foreach (int id in invalidIds) { - _enemyById.Remove(entityId); - return false; + _enemyById.Remove(id); } - - enemy = cachedEnemy; - return true; } public void Clear() { - _enemies.Clear(); _enemyById.Clear(); - Count = 0; - } - - private void RemoveFromCache(int entityId) - { - _enemyById.Remove(entityId); - - for (int i = _enemies.Count - 1; i >= 0; i--) - { - EntityBase cachedEnemy = _enemies[i]; - if (cachedEnemy == null || cachedEnemy.Id == entityId) - { - if (cachedEnemy != null) - { - _enemyById.Remove(cachedEnemy.Id); - } - - _enemies.RemoveAt(i); - } - } } } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/ObjectBase.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/ObjectBase.meta new file mode 100644 index 0000000..4d88228 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/ObjectBase.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74801656cc6bf8548bc7f31f1c927157 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Launcher.unity b/Assets/Launcher.unity index 1ffacdb..c85d94d 100644 --- a/Assets/Launcher.unity +++ b/Assets/Launcher.unity @@ -625,7 +625,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[3].m_InstanceCapacity - value: 500 + value: 10000 objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[4].m_InstanceCapacity @@ -661,7 +661,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[3].m_InstanceExpireTime - value: 60 + value: 120 objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[4].m_InstanceExpireTime @@ -697,7 +697,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[3].m_InstanceAutoReleaseInterval - value: 0 + value: 30 objectReference: {fileID: 0} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EntityGroups.Array.data[4].m_InstanceAutoReleaseInterval