从 EnemyManager 里拆分出敌人的生成

This commit is contained in:
SepComet 2026-06-18 15:40:00 +08:00
parent 921193f469
commit 38849f5019
3 changed files with 147 additions and 82 deletions

View File

@ -13,48 +13,37 @@ namespace SepCore.EnemyManager
{ {
public class EnemyManagerComponent : GameFrameworkComponent public class EnemyManagerComponent : GameFrameworkComponent
{ {
private const float MinSpawnRateScale = 0.1f;
private const string EnemyGroupName = "Enemy"; private const string EnemyGroupName = "Enemy";
private EntityComponent _entity; private EntityComponent _entity;
private EnemyRegistry _enemyRegistry; private EnemyRegistry _enemyRegistry;
private EnemySpawnScheduler _spawnScheduler;
public List<EntityBase> Enemies => _enemyRegistry?.Enemies; public List<EntityBase> Enemies => _enemyRegistry?.Enemies;
private float _spawnEnemyTimer;
[SerializeField] private int _spawnEnemyMaxCount = 5000; [SerializeField] private int _spawnEnemyMaxCount = 5000;
[SerializeField] private int _spawnDistanceFromPlayer = 20; [SerializeField] private int _spawnDistanceFromPlayer = 20;
private int _currentSpawnEnemyId; private int _currentSpawnEnemyId;
private int _currentLevel; private int _currentLevel;
private float[] _baseSpawnEnemyIntervals;
private float[] _spawnEnemyIntervals;
private EnemyType[] _spawnEnemyTypes;
private int[] _spawnEnemyCounts;
private float _duration; private float _duration;
private float _baseDuration; private float _baseDuration;
private float[] _nextSpawnTimes;
private float _spawnRateScale = 1f;
private Transform _player; private Transform _player;
private ISpawnPositionStrategy _spawnPositionStrategy; private ISpawnPositionStrategy _spawnPositionStrategy;
public float SpawnRateScale => _spawnRateScale; public float SpawnRateScale => _spawnScheduler?.SpawnRateScale ?? 1f;
public float BattleDuration => _duration; public float BattleDuration => _duration;
public float ElapsedBattleTime => _spawnEnemyTimer; public float ElapsedBattleTime => _spawnScheduler?.ElapsedTime ?? 0f;
public int CurrentEnemyCount => _enemyRegistry?.Count ?? 0; public int CurrentEnemyCount => _enemyRegistry?.Count ?? 0;
#region FSM #region Lifecycle
private void Start() private void Start()
{ {
_entity = GameEntry.Entity; _entity = GameEntry.Entity;
_enemyRegistry = new EnemyRegistry(); _enemyRegistry = new EnemyRegistry();
_spawnScheduler = new EnemySpawnScheduler();
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
} }
@ -64,6 +53,7 @@ namespace SepCore.EnemyManager
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_enemyRegistry = null; _enemyRegistry = null;
_spawnScheduler = null;
_entity = null; _entity = null;
} }
@ -72,52 +62,34 @@ namespace SepCore.EnemyManager
_player = player != null ? player.CachedTransform : null; _player = player != null ? player.CachedTransform : null;
_spawnPositionStrategy = new RandomCircleSpawnStrategy(_spawnDistanceFromPlayer); _spawnPositionStrategy = new RandomCircleSpawnStrategy(_spawnDistanceFromPlayer);
_baseSpawnEnemyIntervals = (float[])level.Intervals.Clone();
_spawnEnemyIntervals = (float[])_baseSpawnEnemyIntervals.Clone();
_spawnEnemyTypes = level.EntityTypes;
_spawnEnemyCounts = level.EntityCounts;
_baseDuration = level.Duration; _baseDuration = level.Duration;
_duration = _baseDuration; _duration = _baseDuration;
SetSpawnRateScale(_spawnRateScale); _spawnScheduler.Init(level);
_enemyRegistry.Clear(); _enemyRegistry.Clear();
_currentSpawnEnemyId = 0; _currentSpawnEnemyId = 0;
} }
public void OnUpdate(float elapseSeconds, float realElapseSeconds) public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
_spawnEnemyTimer += elapseSeconds; var spawnRequests = _spawnScheduler.Tick(elapseSeconds);
foreach (var request in spawnRequests)
for (int i = 0; i < _nextSpawnTimes.Length; i++)
{ {
float nextSpawnTime = _nextSpawnTimes[i]; for (int j = 0; j < request.Count; j++)
if (_spawnEnemyTimer < nextSpawnTime) continue;
for (int j = 0; j < _spawnEnemyCounts[i]; j++)
{ {
SpawnEnemyAsync(_spawnEnemyTypes[i]).Forget(); SpawnEnemyAsync(request.EnemyType).Forget();
} }
_nextSpawnTimes[i] += _spawnEnemyIntervals[i];
} }
} }
public void OnReset() public void OnReset()
{ {
_spawnEnemyTimer = 0; _spawnScheduler.Reset();
_enemyRegistry.Clear();
_currentSpawnEnemyId = 0; _currentSpawnEnemyId = 0;
_currentLevel = 0; _currentLevel = 0;
_baseSpawnEnemyIntervals = null;
_spawnEnemyIntervals = null;
_spawnEnemyTypes = null;
_spawnEnemyCounts = null;
_baseDuration = 0; _baseDuration = 0;
_duration = 0; _duration = 0;
_nextSpawnTimes = null;
ClearEnemies();
} }
#endregion #endregion
@ -160,47 +132,7 @@ namespace SepCore.EnemyManager
public void SetSpawnRateScale(float scale) public void SetSpawnRateScale(float scale)
{ {
float newScale = Mathf.Max(MinSpawnRateScale, scale); _spawnScheduler.SetSpawnRateScale(scale);
if (_baseSpawnEnemyIntervals == null || _baseSpawnEnemyIntervals.Length == 0)
{
_spawnRateScale = newScale;
return;
}
bool hasRuntimeState = _nextSpawnTimes != null && _spawnEnemyIntervals != null &&
_nextSpawnTimes.Length == _baseSpawnEnemyIntervals.Length &&
_spawnEnemyIntervals.Length == _baseSpawnEnemyIntervals.Length;
float oldScale = _spawnRateScale;
_spawnRateScale = newScale;
if (!hasRuntimeState)
{
for (int i = 0; i < _baseSpawnEnemyIntervals.Length; i++)
{
_spawnEnemyIntervals[i] = GetScaledInterval(_baseSpawnEnemyIntervals[i], _spawnRateScale);
}
_nextSpawnTimes = (float[])_spawnEnemyIntervals.Clone();
return;
}
for (int i = 0; i < _baseSpawnEnemyIntervals.Length; i++)
{
float oldInterval = GetScaledInterval(_baseSpawnEnemyIntervals[i], oldScale);
float newInterval = GetScaledInterval(_baseSpawnEnemyIntervals[i], _spawnRateScale);
float remainTime = Mathf.Max(0f, _nextSpawnTimes[i] - _spawnEnemyTimer);
float remainRatio = oldInterval > Mathf.Epsilon ? Mathf.Clamp01(remainTime / oldInterval) : 0f;
_spawnEnemyIntervals[i] = newInterval;
_nextSpawnTimes[i] = _spawnEnemyTimer + newInterval * remainRatio;
}
}
private static float GetScaledInterval(float baseInterval, float scale)
{
float safeScale = Mathf.Max(MinSpawnRateScale, scale);
return baseInterval / safeScale;
} }
#region Event Handler #region Event Handler

View File

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using SepCore.DataTable;
using SepCore.Definition;
using UnityEngine;
namespace SepCore.EnemyManager
{
public class EnemySpawnScheduler
{
private const float MinSpawnRateScale = 0.1f;
private float _elapsedTime;
private float[] _baseIntervals;
private float[] _currentIntervals;
private EnemyType[] _enemyTypes;
private int[] _spawnCounts;
private float[] _nextSpawnTimes;
private float _spawnRateScale = 1f;
public float SpawnRateScale => _spawnRateScale;
public float ElapsedTime => _elapsedTime;
public int WaveCount => _enemyTypes?.Length ?? 0;
public void Init(DRLevel level)
{
_baseIntervals = (float[])level.Intervals.Clone();
_currentIntervals = (float[])_baseIntervals.Clone();
_enemyTypes = level.EntityTypes;
_spawnCounts = level.EntityCounts;
SetSpawnRateScale(_spawnRateScale);
}
public List<SpawnRequest> Tick(float 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)
{
float newScale = Mathf.Max(MinSpawnRateScale, scale);
if (_baseIntervals == null || _baseIntervals.Length == 0)
{
_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 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()
{
_elapsedTime = 0;
_baseIntervals = null;
_currentIntervals = null;
_enemyTypes = null;
_spawnCounts = null;
_nextSpawnTimes = null;
_spawnRateScale = 1f;
}
private static float GetScaledInterval(float baseInterval, float scale)
{
float safeScale = Mathf.Max(MinSpawnRateScale, scale);
return baseInterval / safeScale;
}
}
public struct SpawnRequest
{
public EnemyType EnemyType;
public int Count;
public int WaveIndex;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8380f7f752e62b41a102a7f5a739309
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: