218 lines
7.3 KiB
C#
218 lines
7.3 KiB
C#
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<EntityBase> 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;
|
||
// 停战标志。OnReset 置 true,OnInit 置 false。
|
||
// ShowEntity 请求一旦发出,取消 await 并不能阻止框架真正创建实体,
|
||
// 这些在途敌人会在 OnReset 之后才出生(fire ShowEntitySuccess)。
|
||
// 此时 _isStopped 为真,由 OnShowEntitySuccess 在出生瞬间兜底 Hide 掉。
|
||
private bool _isStopped;
|
||
|
||
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);
|
||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||
|
||
_spawnCts?.Dispose();
|
||
_spawnCts = null;
|
||
_enemyRegistry = null;
|
||
_spawnScheduler?.Reset();
|
||
_spawnScheduler = null;
|
||
_entity = null;
|
||
}
|
||
|
||
public void OnInit(DRLevel level, Player player)
|
||
{
|
||
_isStopped = false;
|
||
|
||
_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()
|
||
{
|
||
// 先置停战标志。Cancel() 会同步恢复在途的 SpawnEnemyAsync(enemy 拿不到引用,
|
||
// 无法自行 hide),这些实体会延后出生,由 OnShowEntitySuccess 兜底。
|
||
_isStopped = true;
|
||
|
||
_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)
|
||
{
|
||
// 取消通常发生在 OnReset 期间,此时实体往往尚未出生,enemy 为 null 无法 hide。
|
||
// 真正的兜底在 OnShowEntitySuccess:停战后出生的敌人会被立即 hide。
|
||
if (enemy != null && enemy.Available)
|
||
{
|
||
_entity.HideEntity(enemy);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
// await 恢复时若已停战,说明这是漏网的在途敌人,立即 hide,不注册。
|
||
if (_isStopped)
|
||
{
|
||
_entity.HideEntity(enemy);
|
||
return;
|
||
}
|
||
|
||
if (_player != null)
|
||
{
|
||
enemy.SetTarget(_player);
|
||
}
|
||
|
||
_enemyRegistry.Register(enemy);
|
||
}
|
||
|
||
private void ClearEnemies()
|
||
{
|
||
var enemies = new List<EntityBase>(_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);
|
||
}
|
||
}
|
||
|
||
private void OnShowEntitySuccess(object sender, GameEventArgs e)
|
||
{
|
||
if (e is not ShowEntitySuccessEventArgs ne) return;
|
||
if (ne.Entity.EntityGroup?.Name != EnemyGroupName)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 停战窗口(Shop/LevelUp)期间不应存在任何敌人。OnReset 之前发出的在途
|
||
// ShowEntity 请求会在此刻才真正出生,立即 hide 兜底。
|
||
if (_isStopped)
|
||
{
|
||
_entity.HideEntity(ne.Entity.Id);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|