)
示例工程【免费下载链接】Unity3DTraining【Unity杂货铺】unity大杂烩~项目地址https://gitcode.com/gh_mirrors/un/Unity3DTraining点击查看免费下载本篇文章聚焦 Unity3DTraining 仓库中MemoryPool_ObjectPool/Object Pool2示例的完整实现它不依赖任何第三方插件仅用Dictionarystring, ListGameObject两个基础容器就写出了一套可复用的 GameObject 对象池。读完本文你将掌握对象池的借取Get/归还Store核心模型、预制体命名与 Key 匹配的底层细节并能在自己的射击、刷怪、弹幕等高频实例化场景中直接落地这套方案。文章配套源码与场景位于 MemoryPool_ObjectPool/Object Pool2同仓库还收录了一篇泛型对象池理论译文MemoryPool_ObjectPool/Object Pool两篇可对照阅读。图 1Game 运行视图白色小方块即是从对象池中重复借出/归还的子弹实例图 2Hierarchy 面板中Gun节点下挂载的多个bullet(Clone)实例正是对象池缓存未激活子弹的直接证据一、为什么需要对象池频繁实例化的性能账本在 Unity 中大量重复出现的物体——例如发射的子弹、爆炸特效、飘字——如果每次都走Instantiate/Destroy会造成两笔开销实例化/销毁的引擎开销创建与销毁 GameObject、挂载组件、分配本地内存均需时间成本托管堆压力每次实例化都会产生托管堆分配频繁分配会推高 GC 频率引发帧率抖动。对象池的基本思路在源码注释中写得很直白GameObjectPool.cs将用过的对象保存起来等下一次需要这种对象的时候再拿出来重复使用。恰当地使用对象池可以在一定程度上减少频繁创建对象所造成的开销。同时源码注释也给出了重要警示并非所有对象都适合池化。维护对象池本身也有开销如果对象生成成本很低池化反而可能使维护池的开销大于新建对象的开销导致性能不升反降。这一点是使用对象池前必须做的取舍判断。二、核心设计借Get与还Store两个静态方法整套池的对外接口只有两个静态方法职责单一方法职责对应时机GameObjectPool.Get(prefabName, position, rotation)从池中借出一个实例池空则新建发射子弹、生成敌人时GameObjectPool.Store(go)把用完的实例归还进池隐藏而非销毁子弹飞完、敌人死亡时内部存储结构GameObjectPool.csprivate static Dictionarystring, ListGameObject pool new Dictionarystring, ListGameObject();Key预制体的名字实际是实例化后的(Clone)名见下文ValueListGameObject存放该类型下所有已回收、未激活的实例。2.1 借取Get 的三条分支逻辑Get 方法 的完整实现public static GameObject Get(string prefabName, Vector3 position, Quaternion rotation) { // 拼接制作dic的key名因为instantiate出的gameobject都会自动命名为gameobject(Clone) // 这里是为了通下面store方法里给key的命名匹配 string key prefabName (Clone); GameObject go; // 如果字典里有这个key 并且key对应的数组不为空有该种类子弹且该种类子弹中有已经创建过的未激活的子弹 if (pool.ContainsKey(key) pool[key].Count 0) { // 从gameobjectname这个key位置取出数组 ListGameObject list pool[key]; // 取出一号位的子弹 go list[0]; // 从列表中去除这个子弹拿出来用 list.RemoveAt(0); // 初始化状态 go.SetActive(true); go.transform.position position; go.transform.rotation rotation; } // 如果对象池中没有对象 else { go Instantiate(Resources.Load(prefabName), position, rotation) as GameObject; } return go; }三个关键细节值得注意Key 拼接规则key prefabName (Clone)。因为 UnityInstantiate出来的对象会被自动命名为xxx(Clone)Store时直接取go.name作为 Key二者天然对齐。这是整套池能正确分类的前提复用时的状态重置从List[0]取出后要RemoveAt(0)随后重新设置SetActive(true)、position、rotation保证复用对象状态与新建对象一致池空的兜底路径直接Instantiate(Resources.Load(prefabName), position, rotation)。注意这里用Resources.Load所以预制体必须放在Resources目录下本示例位于 Assets/Resources/bullet.prefab。2.2 归还Store 的隐藏而非销毁策略Store 方法public static GameObject Store(GameObject go) { // 获取gameobject的名字会是一个在上面get方法里创建的预设体的gameobject名字会是gameobject(Clone) string key go.name; if (pool.ContainsKey(key)) { // 就在这个key所对应的数组中加入这个g // 这个g就是已经用完的子弹放到这个数组里的gameobject都是不销毁只是取消激活等待再次利用的gameobject pool[key].Add(go); } else { // 建立一个这个key的arraylist 并把g加进去 pool[key] new ListGameObject() { go }; } go.SetActive(false); return go; }归还的核心动作是SetActive(false)而非Destroy。隐藏后对象仍驻留内存等待下一次Get时被重新激活。Dictionary的ContainsKey分支保证了每种类型首次归还时自动创建对应的List无需预初始化。三、演示场景PoolTest 发射 AutoDestroy 自动回收3.1 触发端按空格借出子弹并施加冲量PoolTest.csvoid Update () { if (Input.GetKeyDown(KeyCode.Space)) { GameObject bullet GameObjectPool.Get(bullet, trans.position, Quaternion.identity); bullet.GetComponentRigidbody().AddForce(trans.forward*30, ForceMode.Impulse); } }trans是一个Transform字段指向枪口/发射点在 Inspector 中拖拽赋值Get(bullet, ...)传入的bullet正是Resources下预制体的名字不含扩展名借出的子弹立即被AddForce(trans.forward * 30, ForceMode.Impulse)以冲量方式赋予前向速度——这也解释了为什么子弹预制体需要挂Rigidbody。3.2 回收端3 秒后自动归还AutoDestroy.cs 挂在子弹预制体上实现用完自动回池void Update () { StartCoroutine(DestroySelf()); } private IEnumerator DestroySelf() { yield return new WaitForSeconds(3f); GameObjectPool.Store(this.gameObject); }逻辑一目了然子弹飞出 3 秒后调用GameObjectPool.Store(this.gameObject)将自己归还。配合图 2 的 Hierarchy 截图可以看到Gun节点下累积了 8 个bullet(Clone)正是这些归还后处于未激活缓存状态的实例。小提醒StartCoroutine放在Update里会每帧重复启动协程生产代码通常应改为在实例激活时启动一次本示例旨在演示回收思路读者接入自己项目时按需调整即可。四、对象池设计要点与进阶方向4.1 本方案的设计取舍可据此扩展从 GameObjectPool.cs 源码结构可以归纳出该方案的特征按类型分类缓存不同预制体通过 Key 自然隔离互不干扰静态访问pool与Get/Store均为static全局任意脚本可直接调用Awake中的_instance仅为演示预留的单例赋值实际逻辑并未依赖它无容量上限、无预热首次借取时新建后续回收复用实现极简线程模型依赖 Unity 主线程的Update/协程调用未做线程安全处理——这也是 Unity 对象池的常见形态与 MemoryPool_ObjectPool/Object Pool 中Unity 只有主线程可调用 API池无需支持多线程的结论一致。4.2 与泛型对象池的对照仓库的 Object Pool/README.md 提供了一份 C# 泛型对象池译文展示了面向普通 C# 类的池化思路StackTNew()/Store()、支持重置闭包ResetAction、按需初始化OnetimeInitAction、IResetable自重置、帧末集体重置ResetAll等进阶设计。两者互补泛型池管类实例本示例管场景 GameObject。前者适合ListVector3、自定义数据结构等内存敏感场景后者直接接管 GameObject 的生命周期与激活状态更适合射击、刷怪这类场景级高频复用。4.3 落地到真实项目时建议补充的能力预热与上限场景加载时为高频类型预先GetStore一批实例并设定单类型池容量上限避免峰值时大量新建重置钩子复用对象若有复杂状态血条、Buff、动画建议在Get时统一重置保证借出即干净场景切换清理Store进池的对象若在切换场景时被自动销毁需同步清空对应 Key 的缓存避免残留引用。五、总结Unity3DTraining 的Object Pool2示例用约 70 行代码完成了对象池的完整闭环Get借出池空则Resources.LoadInstantiate兜底→Store归还SetActive(false)隐藏缓存→ 以预制体名(Clone)作为字典 Key 分类管理。它证明了对象池的核心并不复杂关键在于复用的对象必须重置到可用状态以及缓存要按类型隔离。结合图 1、图 2 的运行效果你可以快速验证反复按空格发射子弹Hierarchy 中的bullet(Clone)数量不会无限增长因为飞出的子弹 3 秒后都会回到池中等待再次借出。若需要更理论化的对象池设计讨论重置策略、集体重置、容量收缩等可继续阅读 MemoryPool_ObjectPool/Object Pool/README.md并把它与本篇的 GameObject 版实现互为参照搭建出适合自己项目的完整内存优化方案。赞分享示例工程【免费下载链接】Unity3DTraining【Unity杂货铺】unity大杂烩~项目地址https://gitcode.com/gh_mirrors/un/Unity3DTraining点击查看免费下载相关推荐BMAD-METHOD 官方模块体系解析bmb / cis / gds / tea 四大模块、安装器分发机制与版本通道BMAD METHOD 官方模块体系解析bmb / cis / gds / tea 四大模块、安装器分发机制与版本通道 BMAD METHOD 的核心功能由内AI 技能人工智能开发工具Gonum内存池实现sync.Pool在矩阵对象复用中的应用Gonum内存池实现sync.Pool在矩阵对象复用中的应用 在数值计算领域频繁创建和销毁矩阵对象会导致严重的性能损耗和内存碎片化。Gonum作为Go语言的科学计算在 Garnet 中开发自定义对象Custom Object从继承 CustomObjectBase 到注册自定义命令的完整实战在 Garnet 中开发自定义对象Custom Object从继承 CustomObjectBase 到注册自定义命令的完整实战 导读 Garnet 是缓存KV存储后端上一篇mikro-orm 虚拟实体Virtual Entities实战用动态 SQL 与 MongoDB 聚合映射只读实体下一篇探索TelegraphiOS、tvOS和macOS的安全Web服务器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考