vampire-like/Assets/GameMain/Scripts/Runtime/CustomComponent/EnemyManager/EnemyManagerComponent.cs

218 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 置 trueOnInit 置 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() 会同步恢复在途的 SpawnEnemyAsyncenemy 拿不到引用,
// 无法自行 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
}
}