4.3 刷怪与对象池:让一波波怪"便宜地"来
核心结论
Roguelike 的世界里 5 分钟就要刷几百只怪。每只怪的 Spawn 都是完整初始化(加载资产、建组件、分配 Controller)——直接 Spawn 会带来可见卡顿。解法:波次数据表 + 怪物对象池 + 分级激活。
波次与刷新点:数据化设计
DT_SpawnWave:
| 记录 | 刷怪点 | 怪物 | 数量 | 间隔 | 触发 |
|---|---|---|---|---|---|
| Room1_Wave1 | S_1,S_2 | Gobin_Sword | 4 | 0.5s | 进入房间 |
| Room1_Wave2 | S_1~S_4 | Gobin_Bow,Elite | 6 | 0.8s | Wave1 清完 |
| BossArena | S_Boss | GoblinKing | 1 | 0 | 进门 |
刷新点(TargetPoint 或自定义 Actor)放关卡里,可视化编辑。策划摆点、表里调波次,关卡手感随手调。
对象池(Pool)实现
PoolManager(Actor)
+- 怪物 Class -> [可用实例数组]
+- Spawn 请求:Pop 可用 / 没有则创建(上限内)
+- 死亡/过期:不 Destroy,只 Disable(隐藏+停 AI+移 Collision)-> 回收
+- 关卡切换:全部 Disable 而非 Destroy
回收复用 的关键清理动作: | 动作 | 说明 | |---|---| | ASC 重置 | 清 Tag/Effect、回满血 | | AI 停止 | Stop Movement、清黑板 | | 碰撞与可见 | 关掉(休眠便宜) | | Tick | 关或延长间隔 |
性能对比:池化后波次切换的帧时间峰值从 ~80ms 掉到 ~8ms。这是"看不见的优化"里回报最高的一项。
分级激活:远处少动
| 玩家距离 | 怪物状态 |
|---|---|
| < 20m | 完整 AI + 完整 Tick |
| 20~50m | AI 开、Tick 减半 |
| > 50m | 休眠(Dormant 或彻底 Disable) |
用 Actor 的 Dormancy 或直接 Tick 间隔控制(引擎支持 SetActorTickInterval)。50 个怪的房间,活跃的往往只有 10 个。
常见坑与解法
坑 1:池化后怪物属性串档(上个死者的 buff 还在)
原因:回收时没重置 ASC。
解法:池化回收的标准动作清单里必须有"清 ASC"(RemoveAllGameplayEffects + 清 Tag)。这是最隐蔽的 bug。
坑 2:池里怪物没真正"消失"(还在挡路)
原因:只隐藏了可见性,碰撞还在。
解法:Disable 时关 Collision(SetActorEnableCollision false)+ 移出当前 Level 的 collision hash。
坑 3:关卡传送时池里的怪混进新关卡
原因:池是持久的,没跟关卡走。
解法:池按关卡(sublevel)分组;跨关卡时整池清空或转移,玩家回旧房间按表重生波次。
坑 4:联机时池不同步
原因:池是客户端各自的。
解法:权威在服务器——服务器决定波次与生成,客户端池里的是模拟代理(普通复制或"池 Actor 带复制"需要自定义序列化,中小项目建议直接复制普通的 Spawn Actor + 池只在本地做表现层用途,生成由服务器复制)。
相关节点速查
- actor 休眠:
AActor::Dormancy - Tick 间隔:
SetActorTickInterval(或 Actor 的 TickInterval 属性) - 波次表:
DT_SpawnWave自定义结构
心得:Roguelike 的"刷" 就是心跳,对象池是让心跳不疼的支架。我们项目做完池化后,切房间那种"咯噔"的卡顿彻底消失——玩家感受不到的技术,就是最好的技术。
