下面为你设计一套商业化级FPS第一人称游戏PVE刷怪流程框架(PVE Enemy Spawning System Framework),适用于Unity(C#),也可迁移到其他引擎。该框架强调高性能、高配置化、高可用、易扩展、热更友好、支持复杂关卡与波次、支持AI与多样怪物类型,适合商业项目。
1. 框架目标与核心原则
- 高性能:对象池、分帧生成、异步加载、内存优化
- 高配置化:怪物、波次、关卡、出生点、AI等全部配置表驱动,支持热更
- 高可用:流程解耦、易维护、易扩展、支持多种刷怪模式
- 商业级特性:多样怪物、精英/首领、AI行为、动态难度、事件驱动、统计与掉落、网络同步等
2. 框架模块结构
2.1 主要模块
- SpawnManager:刷怪总控,管理关卡、波次、怪物生成、流程控制
- WaveConfig:波次配置表,支持热更
- MonsterConfig:怪物配置表,支持热更
- SpawnPoint:刷怪点管理
- MonsterPool:怪物对象池
- MonsterBase:怪物基类,支持多态
- AIController:AI行为控制
- SpawnEventBus:事件系统
- DropManager:掉落与奖励
- DifficultyManager:动态难度调整
- SpawnStatistics:刷怪统计与分析
3. 代码结构与核心实现
3.1 WaveConfig(波次配置)
[Serializable]
public class WaveConfigItem
{
public int WaveId;
public float StartDelay; // 波次开始延迟
public List<SpawnInfo> Spawns; // 本波次刷怪信息
}
[Serializable]
public class SpawnInfo
{
public string MonsterId;
public int Count;
public float Interval; // 间隔
public List<int> SpawnPointIds; // 可用刷怪点
}
public class WaveConfig
{
public List<WaveConfigItem> Waves;
public static WaveConfig Load()
{
var json = Resources.Load<TextAsset>("WaveConfig").text;
return JsonUtility.FromJson<WaveConfig>(json);
}
}
3.2 MonsterConfig(怪物配置)
[Serializable]
public class MonsterConfigItem
{
public string MonsterId;
public string Name;
public string PrefabPath;
public float HP;
public float MoveSpeed;
public float Attack;
public float AttackInterval;
public string AIType;
public string DropTableId;
// ...更多属性
}
public class MonsterConfig
{
public List<MonsterConfigItem> Monsters;
public static MonsterConfig Load()
{
var json = Resources.Load<TextAsset>("MonsterConfig").text;
return JsonUtility.FromJson<MonsterConfig>(json);
}
public MonsterConfigItem GetConfig(string monsterId)
{
return Monsters.Find(m => m.MonsterId == monsterId);
}
}
3.3 SpawnPoint(刷怪点)
public class SpawnPoint : MonoBehaviour
{
public int PointId;
public bool IsOccupied;
}
3.4 MonsterPool(对象池)
public class MonsterPool
{
public static MonsterPool Instance { get; } = new MonsterPool();
private Dictionary<string, Queue<MonsterBase>> pool = new Dictionary<string, Queue<MonsterBase>>();
public MonsterBase GetMonster(string monsterId)
{
if (!pool.ContainsKey(monsterId)) pool[monsterId] = new Queue<MonsterBase>();
if (pool[monsterId].Count > 0)
return pool[monsterId].Dequeue();
var config = MonsterConfig.Load().GetConfig(monsterId);
var prefab = Resources.Load<GameObject>(config.PrefabPath);
var go = GameObject.Instantiate(prefab);
var monster = go.GetComponent<MonsterBase>();
monster.Init(config);
return monster;
}
public void RecycleMonster(MonsterBase monster)
{
monster.gameObject.SetActive(false);
pool[monster.Config.MonsterId].Enqueue(monster);
}
}
3.5 MonsterBase(怪物基类)
public abstract class MonsterBase : MonoBehaviour
{
public MonsterConfigItem Config { get; private set; }
public AIController AI { get; private set; }
public int CurrentHP { get; private set; }
public virtual void Init(MonsterConfigItem config)
{
Config = config;
CurrentHP = (int)config.HP;
AI = AIControllerFactory.Create(config.AIType, this);
}
public virtual void OnSpawn(Vector3 pos)
{
transform.position = pos;
gameObject.SetActive(true);
AI?.OnSpawn();
}
public virtual void OnDead()
{
AI?.OnDead();
DropManager.Instance.HandleDrop(Config.DropTableId, transform.position);
MonsterPool.Instance.RecycleMonster(this);
SpawnEventBus.Send("OnMonsterDead", this);
}
public virtual void TakeDamage(int damage)
{
CurrentHP -= damage;
if (CurrentHP <= 0) OnDead();
}
}
3.6 AIController(AI行为)
public abstract class AIController
{
protected MonsterBase monster;
public AIController(MonsterBase monster) { this.monster = monster; }
public virtual void OnSpawn() { }
public virtual void OnDead() { }
public abstract void Update();
}
public class SimpleChaseAI : AIController
{
public SimpleChaseAI(MonsterBase monster) : base(monster) { }
public override void Update()
{
// 追击玩家逻辑
}
}
public static class AIControllerFactory
{
public static AIController Create(string aiType, MonsterBase monster)
{
switch (aiType)
{
case "SimpleChase": return new SimpleChaseAI(monster);
// ...更多AI类型
default: return null;
}
}
}
3.7 SpawnManager(刷怪总控)
public class SpawnManager : MonoBehaviour
{
public static SpawnManager Instance { get; private set; }
public List<SpawnPoint> spawnPoints;
private WaveConfig waveConfig;
private int currentWave = 0;
private int aliveMonsters = 0;
private bool isSpawning = false;
void Awake() { Instance = this; }
public void Init()
{
waveConfig = WaveConfig.Load();
currentWave = 0;
StartCoroutine(SpawnWaveRoutine());
}
private IEnumerator SpawnWaveRoutine()
{
while (currentWave < waveConfig.Waves.Count)
{
var wave = waveConfig.Waves[currentWave];
yield return new WaitForSeconds(wave.StartDelay);
foreach (var spawn in wave.Spawns)
{
StartCoroutine(SpawnMonsters(spawn));
}
isSpawning = true;
while (aliveMonsters > 0 || isSpawning)
yield return null;
currentWave++;
SpawnEventBus.Send("OnWaveEnd", currentWave);
}
SpawnEventBus.Send("OnAllWavesEnd", null);
}
private IEnumerator SpawnMonsters(SpawnInfo spawn)
{
isSpawning = true;
for (int i = 0; i < spawn.Count; i++)
{
var point = GetAvailableSpawnPoint(spawn.SpawnPointIds);
if (point == null) yield break;
var monster = MonsterPool.Instance.GetMonster(spawn.MonsterId);
monster.OnSpawn(point.transform.position);
aliveMonsters++;
point.IsOccupied = true;
monster.AI = AIControllerFactory.Create(monster.Config.AIType, monster);
monster.gameObject.SetActive(true);
monster.AI?.OnSpawn();
monster.OnDead += () =>
{
aliveMonsters--;
point.IsOccupied = false;
};
yield return new WaitForSeconds(spawn.Interval);
}
isSpawning = false;
}
private SpawnPoint GetAvailableSpawnPoint(List<int> ids)
{
foreach (var id in ids)
{
var point = spawnPoints.Find(p => p.PointId == id && !p.IsOccupied);
if (point != null) return point;
}
return null;
}
}
3.8 SpawnEventBus(事件系统)
public static class SpawnEventBus
{
private static Dictionary<string, Action<object>> eventTable = new Dictionary<string, Action<object>>();
public static void Register(string eventName, Action<object> callback)
{
if (!eventTable.ContainsKey(eventName))
eventTable[eventName] = delegate { };
eventTable[eventName] += callback;
}
public static void Unregister(string eventName, Action<object> callback)
{
if (eventTable.ContainsKey(eventName))
eventTable[eventName] -= callback;
}
}
继续为你完善商业化级FPS第一人称游戏PVE刷怪流程框架,补充掉落、动态难度、统计、热更、性能优化、流程控制等高级特性,并给出完整的流程示例。
3.9 DropManager(掉落与奖励)
public class DropManager : MonoBehaviour
{
public static DropManager Instance { get; private set; }
private Dictionary<string, DropTable> dropTables;
void Awake()
{
Instance = this;
LoadDropTables();
}
void LoadDropTables()
{
// 假设本地或服务器热更加载
var json = Resources.Load<TextAsset>("DropTableConfig").text;
dropTables = JsonUtility.FromJson<DropTableList>(json).ToDictionary();
}
public void HandleDrop(string dropTableId, Vector3 pos)
{
if (!dropTables.ContainsKey(dropTableId)) return;
var table = dropTables[dropTableId];
var dropItem = table.GetRandomDrop();
if (dropItem != null)
{
// 生成掉落物
var prefab = Resources.Load<GameObject>(dropItem.PrefabPath);
GameObject.Instantiate(prefab, pos, Quaternion.identity);
}
}
}
[Serializable]
public class DropTable
{
public string DropTableId;
public List<DropItem> Items;
public DropItem GetRandomDrop()
{
float totalWeight = Items.Sum(i => i.Weight);
float rand = UnityEngine.Random.Range(0, totalWeight);
float acc = 0;
foreach (var item in Items)
{
acc += item.Weight;
if (rand <= acc) return item;
}
return null;
}
}
[Serializable]
public class DropItem
{
public string ItemId;
public string PrefabPath;
public float Weight;
}
[Serializable]
public class DropTableList
{
public List<DropTable> Tables;
public Dictionary<string, DropTable> ToDictionary()
{
return Tables.ToDictionary(t => t.DropTableId, t => t);
}
}
3.10 DifficultyManager(动态难度调整)
public class DifficultyManager : MonoBehaviour
{
public static DifficultyManager Instance { get; private set; }
public float DifficultyMultiplier { get; private set; } = 1f;
void Awake() { Instance = this; }
public void AdjustDifficulty(int wave, float playerPerformance)
{
// 例如:根据波次和玩家表现动态调整
DifficultyMultiplier = 1f + wave * 0.1f + playerPerformance * 0.2f;
}
public float ApplyDifficulty(float baseValue)
{
return baseValue * DifficultyMultiplier;
}
}
在MonsterBase.Init
等处可用DifficultyManager.Instance.ApplyDifficulty(config.HP)
等方式动态调整怪物属性。
3.11 SpawnStatistics(刷怪统计与分析)
public class SpawnStatistics
{
public int TotalSpawned { get; private set; }
public int TotalKilled { get; private set; }
public Dictionary<string, int> MonsterKillCount = new Dictionary<string, int>();
public void OnMonsterSpawn(string monsterId)
{
TotalSpawned++;
}
public void OnMonsterDead(string monsterId)
{
TotalKilled++;
if (!MonsterKillCount.ContainsKey(monsterId))
MonsterKillCount[monsterId] = 0;
MonsterKillCount[monsterId]++;
}
}
在SpawnManager
和MonsterBase.OnDead
中调用统计接口。
3.12 热更与配置化
- 配置表(波次、怪物、掉落、AI等)全部支持本地/服务器热更,支持JSON、ScriptableObject等格式。
- AI行为、掉落表、刷怪点等均可通过配置表扩展,无需改动代码。
- 关卡流程可通过配置表描述(如关卡脚本、事件触发等)。
3.13 性能优化建议
- 对象池:怪物、掉落物、特效等全部池化,避免频繁销毁/创建。
- 分帧生成:大波次怪物分帧生成,避免卡顿。
- 异步加载:怪物、掉落物等资源异步加载,支持预加载。
- 刷怪点复用:刷怪点可动态分配,避免同一位置堆叠。
- AI分帧/分组更新:大量怪物AI可分帧或分组更新,降低CPU压力。
3.14 流程控制与事件驱动
- 事件总线(SpawnEventBus)用于解耦流程,如波次开始/结束、怪物死亡、关卡胜利/失败等。
- 流程控制:支持暂停、加速、跳波、重开等功能,便于调试和运营活动。
3.15 典型刷怪流程示例
// 1. 关卡开始
SpawnManager.Instance.Init();
// 2. 进入波次循环
// - 延迟后开始波次
// - 读取波次配置,分帧生成怪物
// - 怪物生成后,AI启动,统计+难度调整