推进计时器逻辑的集中管理

- 将敌人的攻击计时任务放到基类中进行创建
- 将 EnemySpawnScheduler 敌人生成调度器的刷怪计时任务迁移到 TimerComponent
This commit is contained in:
SepComet 2026-06-23 20:42:24 +08:00
parent 846e4d6f44
commit cc8982b131
6 changed files with 69 additions and 93 deletions

View File

@ -58,6 +58,7 @@ namespace SepCore.EnemyManager
_spawnCts?.Dispose(); _spawnCts?.Dispose();
_spawnCts = null; _spawnCts = null;
_enemyRegistry = null; _enemyRegistry = null;
_spawnScheduler?.Reset();
_spawnScheduler = null; _spawnScheduler = null;
_entity = null; _entity = null;
} }
@ -71,7 +72,7 @@ namespace SepCore.EnemyManager
_duration = _baseDuration; _duration = _baseDuration;
_currentLevel = level.Id; _currentLevel = level.Id;
_spawnScheduler.Init(level); _spawnScheduler.Init(level, OnWaveSpawn);
_enemyRegistry.Clear(); _enemyRegistry.Clear();
_currentSpawnEnemyId = 0; _currentSpawnEnemyId = 0;
} }
@ -79,13 +80,14 @@ namespace SepCore.EnemyManager
public void OnUpdate(float elapseSeconds, float realElapseSeconds) public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
_enemyRegistry.PruneInvalidEntries(); _enemyRegistry.PruneInvalidEntries();
var spawnRequests = _spawnScheduler.Tick(elapseSeconds); _spawnScheduler.Tick(elapseSeconds);
foreach (var request in spawnRequests) }
private void OnWaveSpawn(EnemyType enemyType, int count)
{
for (int j = 0; j < count; j++)
{ {
for (int j = 0; j < request.Count; j++) SpawnEnemyAsync(enemyType).Forget();
{
SpawnEnemyAsync(request.EnemyType).Forget();
}
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using SepCore.DataTable; using SepCore.DataTable;
using SepCore.Definition; using SepCore.Definition;
using SepCore.Timer;
using UnityEngine; using UnityEngine;
namespace SepCore.EnemyManager namespace SepCore.EnemyManager
@ -12,98 +12,67 @@ namespace SepCore.EnemyManager
private float _elapsedTime; private float _elapsedTime;
private float[] _baseIntervals; private float[] _baseIntervals;
private float[] _currentIntervals;
private EnemyType[] _enemyTypes; private EnemyType[] _enemyTypes;
private int[] _spawnCounts; private int[] _spawnCounts;
private float[] _nextSpawnTimes; private TimerHandle[] _waveHandles;
private float _spawnRateScale = 1f; private float _spawnRateScale = 1f;
private Action<EnemyType, int> _onSpawn;
public float SpawnRateScale => _spawnRateScale; public float SpawnRateScale => _spawnRateScale;
public float ElapsedTime => _elapsedTime; public float ElapsedTime => _elapsedTime;
public int WaveCount => _enemyTypes?.Length ?? 0; public int WaveCount => _enemyTypes?.Length ?? 0;
public void Init(DRLevel level) public void Init(DRLevel level, Action<EnemyType, int> onSpawn)
{ {
GameEntry.Timer.CancelByOwner(this);
_onSpawn = onSpawn;
_baseIntervals = (float[])level.Intervals.Clone(); _baseIntervals = (float[])level.Intervals.Clone();
_currentIntervals = (float[])_baseIntervals.Clone();
_enemyTypes = level.EntityTypes; _enemyTypes = level.EntityTypes;
_spawnCounts = level.EntityCounts; _spawnCounts = level.EntityCounts;
_elapsedTime = 0f;
SetSpawnRateScale(_spawnRateScale); _waveHandles = new TimerHandle[_baseIntervals.Length];
for (int i = 0; i < _baseIntervals.Length; i++)
{
int waveIndex = i;
float interval = GetScaledInterval(_baseIntervals[i], _spawnRateScale);
_waveHandles[i] = GameEntry.Timer.ScheduleRepeat(interval, () => OnWaveTick(waveIndex), -1, this);
}
} }
public List<SpawnRequest> Tick(float deltaTime) public void Tick(float deltaTime)
{ {
_elapsedTime += deltaTime; _elapsedTime += deltaTime;
var requests = new List<SpawnRequest>();
if (_nextSpawnTimes == null) return requests;
for (int i = 0; i < _nextSpawnTimes.Length; i++)
{
if (_elapsedTime < _nextSpawnTimes[i]) continue;
requests.Add(new SpawnRequest
{
EnemyType = _enemyTypes[i],
Count = _spawnCounts[i],
WaveIndex = i
});
_nextSpawnTimes[i] += _currentIntervals[i];
}
return requests;
} }
public void SetSpawnRateScale(float scale) public void SetSpawnRateScale(float scale)
{ {
float newScale = Mathf.Max(MinSpawnRateScale, scale); _spawnRateScale = Mathf.Max(MinSpawnRateScale, scale);
if (_baseIntervals == null || _baseIntervals.Length == 0) if (_waveHandles == null) return;
for (int i = 0; i < _waveHandles.Length; i++)
{ {
_spawnRateScale = newScale;
return;
}
bool hasRuntimeState = _nextSpawnTimes != null && _currentIntervals != null &&
_nextSpawnTimes.Length == _baseIntervals.Length &&
_currentIntervals.Length == _baseIntervals.Length;
float oldScale = _spawnRateScale;
_spawnRateScale = newScale;
if (!hasRuntimeState)
{
for (int i = 0; i < _baseIntervals.Length; i++)
{
_currentIntervals[i] = GetScaledInterval(_baseIntervals[i], _spawnRateScale);
}
_nextSpawnTimes = (float[])_currentIntervals.Clone();
return;
}
for (int i = 0; i < _baseIntervals.Length; i++)
{
float oldInterval = GetScaledInterval(_baseIntervals[i], oldScale);
float newInterval = GetScaledInterval(_baseIntervals[i], _spawnRateScale); float newInterval = GetScaledInterval(_baseIntervals[i], _spawnRateScale);
GameEntry.Timer.SetInterval(_waveHandles[i], newInterval, adjustRemainingTime: true);
float remainTime = Mathf.Max(0f, _nextSpawnTimes[i] - _elapsedTime);
float remainRatio = oldInterval > Mathf.Epsilon ? Mathf.Clamp01(remainTime / oldInterval) : 0f;
_currentIntervals[i] = newInterval;
_nextSpawnTimes[i] = _elapsedTime + newInterval * remainRatio;
} }
} }
public void Reset() public void Reset()
{ {
GameEntry.Timer.CancelByOwner(this);
_elapsedTime = 0; _elapsedTime = 0;
_baseIntervals = null; _baseIntervals = null;
_currentIntervals = null;
_enemyTypes = null; _enemyTypes = null;
_spawnCounts = null; _spawnCounts = null;
_nextSpawnTimes = null; _waveHandles = null;
_spawnRateScale = 1f; _spawnRateScale = 1f;
_onSpawn = null;
}
private void OnWaveTick(int waveIndex)
{
_onSpawn?.Invoke(_enemyTypes[waveIndex], _spawnCounts[waveIndex]);
} }
private static float GetScaledInterval(float baseInterval, float scale) private static float GetScaledInterval(float baseInterval, float scale)
@ -112,11 +81,4 @@ namespace SepCore.EnemyManager
return baseInterval / safeScale; return baseInterval / safeScale;
} }
} }
public struct SpawnRequest
{
public EnemyType EnemyType;
public int Count;
public int WaveIndex;
}
} }

View File

@ -2,6 +2,7 @@
using SepCore.AsyncTask; using SepCore.AsyncTask;
using SepCore.Definition; using SepCore.Definition;
using SepCore.Entity; using SepCore.Entity;
using SepCore.Timer;
using UnityEngine; using UnityEngine;
public abstract class EnemyBase : TargetableObject public abstract class EnemyBase : TargetableObject
@ -15,6 +16,29 @@ public abstract class EnemyBase : TargetableObject
protected EnemyData _enemyData; protected EnemyData _enemyData;
protected bool _canAttack;
protected TimerHandle _attackTimerHandle;
protected void StartAttackCooldown(float cooldown)
{
_canAttack = false;
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(cooldown, () => _canAttack = true, this);
}
protected void ResetAttackCooldown(float cooldown)
{
GameEntry.Timer.Cancel(_attackTimerHandle);
_canAttack = false;
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(cooldown, () => _canAttack = true, this);
}
protected void CancelAttackCooldown()
{
GameEntry.Timer.Cancel(_attackTimerHandle);
_attackTimerHandle = TimerHandle.Invalid;
_canAttack = false;
}
protected override void OnShow(object userData) protected override void OnShow(object userData)
{ {
base.OnShow(userData); base.OnShow(userData);

View File

@ -1,7 +1,6 @@
using SepCore.Components; using SepCore.Components;
using SepCore.CustomUtility; using SepCore.CustomUtility;
using SepCore.Definition; using SepCore.Definition;
using SepCore.Timer;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -24,8 +23,6 @@ namespace SepCore.Entity
private int _attackDamage = 1; private int _attackDamage = 1;
private float _sqrAttackRange; private float _sqrAttackRange;
private TimerHandle _attackTimerHandle;
private bool _canAttack;
private AttackStateType _attackState = AttackStateType.Idle; private AttackStateType _attackState = AttackStateType.Idle;
@ -71,8 +68,7 @@ namespace SepCore.Entity
_attackDamage = Mathf.Max(1, _meleeEnemyData.AttackDamage); _attackDamage = Mathf.Max(1, _meleeEnemyData.AttackDamage);
_sqrAttackRange = _attackRange * _attackRange; _sqrAttackRange = _attackRange * _attackRange;
_canAttack = false; StartAttackCooldown(_attackCooldown);
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(_attackCooldown, () => _canAttack = true, this);
_attackState = AttackStateType.Idle; _attackState = AttackStateType.Idle;
_targetableTarget = null; _targetableTarget = null;
@ -96,8 +92,7 @@ namespace SepCore.Entity
_movementComponent.OnReset(); _movementComponent.OnReset();
_healthComponent.OnReset(); _healthComponent.OnReset();
_targetableTarget = null; _targetableTarget = null;
GameEntry.Timer.Cancel(_attackTimerHandle); CancelAttackCooldown();
_attackTimerHandle = TimerHandle.Invalid;
_attackState = AttackStateType.Idle; _attackState = AttackStateType.Idle;
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
@ -155,8 +150,7 @@ namespace SepCore.Entity
if (_canAttack) if (_canAttack)
{ {
_canAttack = false; ResetAttackCooldown(_attackCooldown);
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(_attackCooldown, () => _canAttack = true, this);
TransitionTo(AttackStateType.Attack); TransitionTo(AttackStateType.Attack);
} }

View File

@ -3,7 +3,6 @@ using SepCore.AsyncTask;
using SepCore.CustomUtility; using SepCore.CustomUtility;
using SepCore.Definition; using SepCore.Definition;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using SepCore.Timer;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -28,8 +27,6 @@ namespace SepCore.Entity
private float _attackRangeSquared; private float _attackRangeSquared;
private float _attackCooldown = 1f; private float _attackCooldown = 1f;
private int _attackDamage = 1; private int _attackDamage = 1;
private TimerHandle _attackTimerHandle;
private bool _canAttack;
[SerializeField] private float _projectileSpeed = 12f; [SerializeField] private float _projectileSpeed = 12f;
[SerializeField] private float _projectileLifeTime = 3f; [SerializeField] private float _projectileLifeTime = 3f;
@ -83,8 +80,7 @@ namespace SepCore.Entity
_projectileSpawnHeightOffset); _projectileSpawnHeightOffset);
_projectileAssetName = ReadStringParam(ProjectileAssetNameParamKey, _projectileAssetName); _projectileAssetName = ReadStringParam(ProjectileAssetNameParamKey, _projectileAssetName);
_canAttack = false; StartAttackCooldown(_attackCooldown);
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(_attackCooldown, () => _canAttack = true, this);
this.CachedTransform.position = enemyData.Position; this.CachedTransform.position = enemyData.Position;
} }
else else
@ -112,8 +108,7 @@ namespace SepCore.Entity
if (_canAttack) if (_canAttack)
{ {
TryFireProjectile(); TryFireProjectile();
_canAttack = false; ResetAttackCooldown(_attackCooldown);
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(_attackCooldown, () => _canAttack = true, this);
} }
} }
else else
@ -127,8 +122,7 @@ namespace SepCore.Entity
{ {
_movementComponent.OnReset(); _movementComponent.OnReset();
_healthComponent.OnReset(); _healthComponent.OnReset();
GameEntry.Timer.Cancel(_attackTimerHandle); CancelAttackCooldown();
_attackTimerHandle = TimerHandle.Invalid;
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
} }

View File

@ -492,7 +492,7 @@ namespace SepCore.Timer
return; return;
} }
taskInfo.RemainingTime = taskInfo.Interval; taskInfo.RemainingTime += taskInfo.Interval;
} }
private void InvokeCallback(TimerTaskInfo taskInfo) private void InvokeCallback(TimerTaskInfo taskInfo)