using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using SepCore.DataTable; using SepCore.Definition; using SepCore.Entity; using GameFramework.Event; using SepCore.AsyncTask; using SepCore.CustomUtility; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.EnemyManager { public class EnemyManagerComponent : GameFrameworkComponent { private const string EnemyGroupName = "Enemy"; private EntityComponent _entity; private EnemyRegistry _enemyRegistry; private EnemySpawnScheduler _spawnScheduler; public IReadOnlyCollection Enemies => _enemyRegistry?.Enemies; [SerializeField] private int _spawnEnemyMaxCount = 5000; [SerializeField] private int _spawnDistanceFromPlayer = 20; private int _currentSpawnEnemyId; private int _currentLevel; private float _duration; private float _baseDuration; private Transform _player; private ISpawnPositionStrategy _spawnPositionStrategy; private CancellationTokenSource _spawnCts; public float SpawnRateScale => _spawnScheduler?.SpawnRateScale ?? 1f; public float BattleDuration => _duration; public float ElapsedBattleTime => _spawnScheduler?.ElapsedTime ?? 0f; public int CurrentEnemyCount => _enemyRegistry?.Count ?? 0; #region Lifecycle private void Start() { _entity = GameEntry.Entity; _enemyRegistry = new EnemyRegistry(); _spawnScheduler = new EnemySpawnScheduler(); _spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); } private void OnDestroy() { GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); _spawnCts?.Dispose(); _spawnCts = null; _enemyRegistry = null; _spawnScheduler?.Reset(); _spawnScheduler = null; _entity = null; } public void OnInit(DRLevel level, Player player) { _player = player != null ? player.CachedTransform : null; _spawnPositionStrategy = new RandomCircleSpawnStrategy(_spawnDistanceFromPlayer); _baseDuration = level.Duration; _duration = _baseDuration; _currentLevel = level.Id; _spawnScheduler.Init(level, OnWaveSpawn); _enemyRegistry.Clear(); _currentSpawnEnemyId = 0; } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { _enemyRegistry.PruneInvalidEntries(); _spawnScheduler.Tick(elapseSeconds); } private void OnWaveSpawn(EnemyType enemyType, int count) { for (int j = 0; j < count; j++) { SpawnEnemyAsync(enemyType).Forget(); } } public void OnReset() { _spawnCts.Cancel(); _spawnCts.Dispose(); _spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); _spawnScheduler.Reset(); ClearEnemies(); _currentSpawnEnemyId = 0; _currentLevel = 0; _baseDuration = 0; _duration = 0; } #endregion private async UniTaskVoid SpawnEnemyAsync(EnemyType enemyType) { if (_player == null) return; if (_enemyRegistry.Count >= _spawnEnemyMaxCount) return; int entityId = _currentSpawnEnemyId++; var enemyData = EntityDataFactory.Create(entityId, enemyType, _currentLevel); enemyData.Position = _spawnPositionStrategy.GetSpawnPosition(_player); 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; } if (_player != null) { enemy.SetTarget(_player); } _enemyRegistry.Register(enemy); } private void ClearEnemies() { var enemies = new List(_enemyRegistry.Enemies); foreach (var enemy in enemies) { if (enemy == null || !enemy.Available) continue; _entity.HideEntity(enemy); } _enemyRegistry.Clear(); } public bool TryGetEnemy(int entityId, out EntityBase enemy) { return _enemyRegistry.TryGet(entityId, out enemy); } public void SetSpawnRateScale(float scale) { _spawnScheduler.SetSpawnRateScale(scale); } #region Event Handler private void OnHideEntityComplete(object sender, GameEventArgs e) { if (e is not HideEntityCompleteEventArgs ne) return; string entityGroupName = ne.EntityGroup.Name; if (entityGroupName == EnemyGroupName) { _enemyRegistry.Remove(ne.EntityId); } } #endregion } }