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

news/2024/12/29 16:40:25/

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.ppmy.cn/news/1226318.html

相关文章

选硬币该用动态规划

选硬币&#xff1a; 现有面值分别为1角1分&#xff0c;5分&#xff0c;1分的硬币&#xff0c;请给出找1角5分钱的最佳方案。 #include <iostream> #include <vector>std::vector<int> findChange(int amount) {std::vector<int> coins {11, 5, 1}; /…

SIMD单指令多数据(并行计算)

单指令多数据&#xff08;SIMD&#xff09;是一种并行计算的技术&#xff0c;允许一个指令同时对多个数据元素进行操作。这对于向量计算和多媒体应用非常有用&#xff0c;因为它们经常涉及到对大块数据执行相同的操作。 在 SIMD 中&#xff0c;数据通常被组织成“向量”&#…

SVG的viewBox、width和height释义, 示例及代码

svg的是没有边界的&#xff0c;svg画布只是用于展示svg世界中某一个范围的内容&#xff0c;而对于超过了svg画布范围的内容&#xff0c;则会被遮挡。默认svg画布默认显示世界坐标下原点坐标的width*height面积的矩形视野。 ​ 我们可以通过viewBox来修改默认的显示配置&#…

【vue实战项目】通用管理系统:首页

前言 本文为博主的vue实战小项目系列中的第三篇&#xff0c;很适合后端或者才入门的小伙伴看&#xff0c;一个前端项目从0到1的保姆级教学。前面的内容&#xff1a; 【vue实战项目】通用管理系统&#xff1a;登录页-CSDN博客 【vue实战项目】通用管理系统&#xff1a;封装to…

【Linux】20、进程状态:不可中断进程、iowait、僵尸进程、dstat strace pstree

文章目录 一、进程状态1.1 iowait 分析1.2 僵尸进程1.3 小结 短时应用的运行时间比较短&#xff0c;很难在 top 或者 ps 这类展示系统概要和进程快照的工具中发现&#xff0c;你需要使用记录事件的工具来配合诊断&#xff0c;比如 execsnoop 或者 perf top。 讲到 CPU 使用率的…

zabbix-proxy分布式监控

Zabbix是一款开源的企业级网络监控软件&#xff0c;可以监测服务器、网络设备、应用程序等各种资源的状态和性能指标。在大型环境中&#xff0c;如果只有一个Zabbix Server来监控所有的节点&#xff0c;可能会遇到性能瓶颈和数据处理难题。 为了解决这个问题&#xff0c;Zabbi…

Jupyter Notebook的下载安装与使用教程_Python数据分析与可视化

Jupyter Notebook的下载安装与使用 Jupyter简介下载与安装启动与创建NotebookJupyter基本操作 在计算机编程领域&#xff0c;有一个很强大的工具叫做Jupyter。它不仅是一个集成的开发环境&#xff0c;还是一个交互式文档平台。对于初学者来说&#xff0c;Jupyter提供了友好的界…

CAS源码工程搭建记录

CAS源码工程搭建 1.下载2.gradle下载源改为阿里云&#xff0c;解决下载慢的问题3.解决保存 1.下载 git clone -b 5.3.x https://gitee.com/mirrors/CAS.git如果下载的是压缩包&#xff0c;导入工程会保存&#xff0c;因为builder.gradle的第20行开始有取git信息&#xff0c;如…