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

news2024/12/23 17:54:42

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; //hashCode
     type: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.coloradmin.cn/o/1223661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

zabbix告警 邮件告警 钉钉告警

邮件告警添加主机组添加模板添加主机在模板中添加监控项在模板中添加触发器添加动作&#xff0c;远程执行命令给用户绑定告警媒介类型 钉钉告警安装python依赖模块python-requests配置钉钉告警配置脚本zabbix_ding.conf在目录/var/log/zabbix中创建钉钉告警日志文件zabbix_ding…

小命令,大世界

Linux是一个大系统&#xff0c;功能丰富&#xff0c;好比是一台巨型机器&#xff0c;而命令&#xff0c;就是这台机器的操作台。要想控制好这台机器&#xff0c;用好这台机器&#xff0c;就得会看仪表&#xff0c;会操作各种按钮。《Linux常用命令自学手册》就是介绍如何操作这…

Dart笔记:glob 文件系统遍历

Dart笔记 文件系统遍历工具&#xff1a;glob 模块 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/13442…

IIs部署发布vue项目测试环境

打开【控制面板 > 程序>启用或关闭Windows功能 】 1、安装IIS: 把这些勾选上&#xff0c;点击确定下载。 2、安装.net: 把这些勾选上&#xff0c;点击确定下载。 3、搜索IIs打开&#xff1a; 4、右击【网站>添加网站 】进行配置&#xff0c;点击确定。 4、右击[项目le…

【一周安全资讯1118】北京高院发布《侵犯公民个人信息犯罪审判白皮书》;工银金融勒索案的事件响应服务商MoxFive是谁?

要闻速览 1、工信部等四部门部署开展智能网联汽车准入和上路通行试点工作 2、北京高院发布《侵犯公民个人信息犯罪审判白皮书》 3、丰田公司确认遭遇美杜莎勒索软件攻击 4、家中设备把数据信息泄露到国外&#xff0c;浙江一男子被罚5000元 5、工银金融勒索案的事件响应服务商M…

信道编码---RS编码与译码原理

本文介绍了RS编码以及译码的原理。 本文的内容基本上都来自刘梦欣的《基于FPGA的RS编译码研究与设计》&#xff0c;大家可以通过知网找到这篇文章&#xff0c;链接在下面。对RS码的原理讲解非常清楚&#xff0c;如果要看的话可以结合第2和第3部分一起看更好懂。我的整理也是比较…

量化交易:开发传统趋势策略之---双均线策略

本文以双均线策略为例&#xff0c;描述如何在BigQuant策略平台上&#xff0c;开发一个传统的趋势跟踪策略&#xff0c;以更好地理解BigQuant回测机制。 双均线策略的策略思想是&#xff1a;当短期均线上穿长期均线时&#xff0c;形成金叉&#xff0c;此时买入股票。当短期均线…

纯CSS自定义滚动条样式

.my-carousel{height: 474px;overflow-y: auto; } /*正常情况下滑块的样式*/ .my-carousel::-webkit-scrollbar {width: 5px; } .my-carousel::-webkit-scrollbar-thumb {border-radius: 8px;background-color: #ccc; } .my-carousel::-webkit-scrollbar-track {border-radius:…

机器学习笔记 - 隐马尔可夫模型的简述

隐马尔可夫模型是一个并不复杂的数学模型,到目前为止,它一直被认为是解决大多数自然语言处理问题最为快速、有效的方法。它成功地解决了复杂的语音识别、机器翻译等问题。看完这些复杂的问题是如何通过简单的模型得到描述和解决,我们会由衷地感叹数学模型之妙。 人类信息交流…

小程序申请,商户号申请,微信支付开通操作流程

总目录 文章目录 总目录前言1 申请商户号&#xff08;如已有商户号跳过&#xff09;1 申请流程与资料2 详细申请步骤 2 申请开通接入微信支付步骤3 申请微信小程序1 申请小程序步骤2 查看小程序AppID 4 微信支付普通商户与AppID账号关联结语 前言 本文主要讲解如何申请微信商户…

为什么选择B+树作为数据库索引结构?

背景 首先&#xff0c;来谈谈B树。为什么要使用B树&#xff1f;我们需要明白以下两个事实&#xff1a; 【事实1】 不同容量的存储器&#xff0c;访问速度差异悬殊。以磁盘和内存为例&#xff0c;访问磁盘的时间大概是ms级的&#xff0c;访问内存的时间大概是ns级的。有个形象…

二叉树中的深搜之二叉树的所有路径

257. 二叉树的所有路径 - 力扣&#xff08;LeetCode&#xff09; 对于二叉树的深度搜索&#xff0c;要学会从以下三个角度来去看待问题&#xff1a; 1. 全局变量&#xff0c;有时候全局变量会减少参数的个数&#xff0c;简化很多流程&#xff1b; 这道题目&#xff0c;要返回根…

Python3语法总结-数据转换②

Python3语法总结-数据转换② Python3语法总结二.Python数据类型转换隐式类型转换显示类型转换 Python3语法总结 二.Python数据类型转换 有时候我们&#xff0c;需要对数据内置的类型进行转换&#xff0c;数据类型的转换。 Python 数据类型转换可以分为两种&#xff1a; 隐式类…

zabbix-proxy分布式监控

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

搜索引擎ElasticSearch分布式搜索和分析引擎学习,SpringBoot整合ES个人心得

ElasticSearch Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java语言开发的&#xff0c;并作为Apache许可条款下的开放源码发布&#xff0c;是一种流行的企业级搜索引擎。Elas…

ROS 学习应用篇(十)ROS中常用可视化工具的使用

ros工具箱有rqt_console、rqt_graph、rqtplot、rqt_image_view rqt plugins下可以打开多个调试窗口&#xff0c;非常好用偶吼吼 Rviz 重点来了 打开方式就是 rocore rivz gazebp roslaunch gazebo_ros **** 可以输入这玩一玩。

【面试经典150 | 数学】Pow(x, n)

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;快速幂-递归方法二&#xff1a;快速幂-迭代 其他语言python3 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主…

前端JS 使用input完成文件上传操作,并对文件进行类型转换

使用input实现文件上传 // 定义一个用于文件上传的按钮<input type"file" name"upload1" />// accept属性用于定义允许上传的文件类型&#xff0c; onchange用于绑定文件上传之后的相应函数<input type"file" name"upload2"…

主办方:上海视频媒体,多样式多渠道跨屏传播

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 一&#xff0c;邀请视频媒体参加活动发布会&#xff0c;好处多多&#xff0c;首先现场气氛会很热烈&#xff0c;主办方会很有面子&#xff0c;视频媒体不管是电视台还是视频网站&#xf…