当前位置: 首页 > news >正文

【bigo前端】egret中的对象池浅谈

file

本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

egret是一款小游戏开发引擎,支持跨平台开发,之前使用这款引擎开发了一款捕鱼游戏,在这里简单聊下再egret中关于对象池的使用,虽然该引擎已经停止维护了,但是对象池的概念适合通用的游戏场景,不限框架。

起因

关于对象池的使用主要是有两个场景,一个是捕鱼游戏里面鱼的生成,还有一个是射击过程中子弹的生成。当用户处于游戏时,这两个场景会频繁的生成对象并展示对应的动画,会造成大量的内存碎片以及频繁的分配内存空间。而通过对象池能够比较好的解决这个问题。
捕鱼场景

对象池模式

首先定义一个池对象,用来存储可复用的对象。池对象包含两组列表,一组为可复用hash对象,一组为正在使用的hash对象。在游戏加载游戏资源的时候,对池对象进行初始化,创建了一个鱼对象集合,并且同时初始化可复用hash对象作为备用池。

当游戏资源加载完成的同时,向对象池尝试获取一个可复用的鱼对象,对象池查询可复用hash对象是否存在可以复用的鱼对象,存在可复用对象的时候将对象进行初始化并将对象的索引从可复用hash对象中移除加入正在使用的hash对象。当鱼对象在游戏舞台中被移除的时候,将其进行回收到可复用hash对象等待下一轮渲染。通过这种方式避免了大量的重复对象的创建跟销毁,减少不必要的内存分配。

实现方式

首先设计一个工厂方法,实现一个FishGenerator类,负责各种类型Fish的生成,Distributor类负责各类鱼的回收与存储。

流程图

鱼的实现如下,前期开发过程中,不同种类的鱼没有单独分开类实现,都是通过传入骨骼动画与类型内部实现的,所以这里通过 type 来识别不同的鱼进行获取与复用。 hashc egret 本身的 hashCode ,通过 hashc 允许外部访问,并且由于其具有唯一性,所以将其作为存储管理的下标。

interface IFish {hashc:number; //hashCodetype:string; //类型标识isIdle:boolean; //标记是否空闲lastUsed: number // 上次使用时间dispose():void; //释放对象内部引用del():void; //彻底释放对象reset():void;   //重置setProtocol( val:IDistributor ):void; //设置协议
}

Distributor 的实现如下, distribution 负责将生成或者复用的鱼分配到对应的hash对象,并删除其再原先hash对象上的引用;
clearRegularly 则是定时清除过久未复用的鱼对象,减少复用hash对象所占的内存

interface IDistributor {distribution(val:IFish):void; //分配addFish(val:IFish):void; //添加元素getFish(type:string):IFish; //获取元素clear():void; //清除所有未使用的对象clearRegularly(): void; // 定期清除过久未使用的对象
}

BashFish 源代码如下, reset 方法重置鱼的空闲状态,重新在空闲池跟使用池之间进行分配,记录鱼的使用时间作为定期清除的标识。 dispose 方法则是释放鱼,将鱼的状态重置为空闲状态,并将鱼重新分配。

class FishBase extends egret.Sprite implements IFish {private _isIdle: boolean = false;private _dis:IDistributor = null;public type:string = '';public lastUsed: number = 0;...//more code...constructor(factory: dragonBones.EgretFactory, config: fishTypeConfig) {super();this.type = config.type;...// more code...}public reset():void {// 重置当前鱼的对象空闲状态为false,并且更新使用时间,将其进行对象的交互this._isIdle = false;this.lastUsed = Date.now();this._dis.distribution(this);}public get hashc():number {return this.hashCode;}public get isIdle():boolean {return this._isIdle;}public dispose():void {this._isIdle = true;// 重新进行分配this._dis.distribution(this);}public del():void {this.dispose();this._dis = null;}public setProtocol( val:IDistributor ):void {// 建立Distributor链接this._dis = val;}// 离开舞台后移除鱼remove() {this.dispose();}...// more code...
}
...

FishGenerator 源代码如下,FishGenerator 对外暴露expansiongetFish方法,expansion方法是当空闲池不足时,一次性创建大量的空闲的鱼备用,getFish方法是通过Distributor获取空闲池中的鱼,当空闲池中的鱼不足的时候创建新的鱼分配到空闲池中并将鱼进行返回。

class FishGenerator {private _dis:IDistributor = null;...//more code...public constructor( val:IDistributor ) {this.init(val);}private init(val:IDistributor):void {this._dis = val;}public expansion(factoryList: any, num) {const len = factoryList.length;for (let i = 0; i < len; i += 1) {const item = factoryList[i];const fish = this.createFish(item.factory, item.config);this._dis.addFish(fish);fish.reset();}}public getFish(factory: any, config:any):IFish {let fish:IFish = this._dis.getFish(config.type);// 无法进行复用,则创建一个新的鱼对象if(fish === null) {fish = this.createFish(factory, config);this._dis.addFish(fish);fish.reset();}return fish;}private createFish(factory: any, config:any):IFish {return new FishBase(factory, config);}...//more code...
}

分配器源代码如下,对外提供distributionaddFishgetFishclear以及clearRegularly方法

distribution:根据鱼的状态进行重新分配

addFish:建立鱼跟Distributor 的链接,并将鱼分配到空闲池

getFish:获取可复用的鱼

clear:清除空闲鱼群

clearRegularly: 获取鱼次数达到500的倍数的时候,清除过久未使用的空闲鱼群

class Distributor implements IDistributor {private _UsedPool:Object = null; //使用中的鱼private _IdlePool:Object = null; //空闲的鱼private count:number= 0;...//more code...public constructor() {this._IdlePool = {};this._UsedPool = {};}// 将鱼进行重新分配,空闲状态分配到空闲hash对象,使用中分配到使用hash对象// 移除原先的引用public distribution( val:IFish ):void {if(val.isIdle) {this._IdlePool[val.hashc] = val;delete this._UsedPool[val.hashc];} else {this._UsedPool[val.hashc] = val;delete this._IdlePool[val.hashc];}}public addFish(val:IFish):void {val.setProtocol(this);if(val.isIdle) {this._IdlePool[val.hashc] = val;} else {this._UsedPool[val.hashc] = val;}}public getFish(type:string):IFish  {this.count += 1;let obj:IFish  = null;const keys = Object.keys(this._IdlePool);for (let i = 0,len = keys.length; i < len; i += 1) {const key = keys[i];obj = this._IdlePool[key] as IFish;// 如果类型一致,则为可复用对象,返回引用并重置状态if (obj.type === type) {obj.reset();if (this.count % 500 === 0) this.clearRegularly();return obj;}}if (this.count % 500 === 0) this.clearRegularly();return null;}public clear():void {let obj:IFish = null;for (let key in this._IdlePool) {obj = this._IdlePool[key] as IFish;// 释放与Distributor的链接obj.del();}this._IdlePool = null;this._IdlePool = {};}private clearRegularly() {const cur = Date.now();const keys = Object.keys(this._IdlePool);for (let i = 0,len = keys.length; i < len; i += 1) {const key = keys[i];const obj = this._IdlePool[key] as IFish;// 复用hash中存在10分钟内未复用过的对象进行销毁动作if (cur - obj.lastUsed > 600 * 1000) { obj.del();delete this._IdlePool[key];}}}...//more code...
}
最终效果

游戏运行期间,鱼的总数量维持在80-114之间,内存稳定在27-31mb之间,游戏效果总体流畅度得到相应的提升,用户的游戏体验得到改善。目前游戏整体效果得到一定的提升,但仍存在性能优化的空间,后续会继续提升游戏的性能,给用户更好的体验。
效果1
效果2
效果3
效果4

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

http://www.lryc.cn/news/236684.html

相关文章:

  • 用公式告诉你 现货黄金投资者要不要换策略?
  • 系列六、多线程集合不安全
  • MidJourney笔记(1)-入门
  • CRM系统定制开发价格
  • Kubernetes实战(五)-pod之间网络请求实战
  • 7年经验之谈 —— 如何高效的开展app的性能测试?
  • 小程序action-sheet结合自定义tabbar显示
  • 机器学习笔记 - 隐马尔可夫模型的简述
  • iOS学习 --- Xcode 15 下载iOS_17.0.1_Simulator失败解决方法
  • 多视图聚类论文阅读(二)
  • Docker在Centos7下的安装
  • LLM大模型4位量化实战【GPTQ】
  • 安装keras、tensorflow
  • ffmpeg知识点整理
  • Git 笔记之gitignore
  • 【配置】Redis常用配置详解
  • Linux(Ubuntu)安装JDK环境
  • OpenCV C++ 张正友相机标定【相机标定原理、相机标定流程、图像畸变矫正】
  • SDL2 播放音频(MP4)
  • WMS仓库管理系统库位功能
  • vue2组件通信中的一些拓展(props,emit,ref父子双向传参)
  • Flink1.17 DataStream API
  • 数据结构中树、森林 与 二叉树的转换
  • 力扣labuladong——一刷day43
  • MapApp 地图应用
  • Java之反射获取和赋值字段
  • ckplayer自己定义风格播放器的开发记录
  • 全网最全Django面试题整理(一)
  • vue统一登录
  • MVSNet论文笔记