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 的"刷" 就是心跳,对象池是让心跳不疼的支架。我们项目做完池化后,切房间那种"咯噔"的卡顿彻底消失——玩家感受不到的技术,就是最好的技术。