【Unity学习笔记】对象池

文章目录

  • 设计思路
    • 总体设计
    • 从生命周期考虑
  • 一些代码


对象池这个东西老生常谈了,使用它的好处在于:当我们需要重复创建或者销毁一些物体,例如限制子弹数量上限为10发,当射出第11发就需要使第10发消失,第11出现。销毁10号子弹和创建11号子弹虽然能实现,但是由于创建和销毁比较消耗性能,因此不应该这样实现。如果使用对象池技术就可以避免创建销毁,改为隐藏和显示。也就是显示11号,隐藏10号子弹实现一样的功能。通过对象池,如果要发射1000发子弹,我们不必实现1000次创建和销毁,只需11次创建和销毁(产生11个子弹并依次显示即可了。

设计思路

在设计对象池的时候,我们会考虑到使用设计模式的问题。像吃鸡游戏里有口径不同的子弹,那么不同枪也有不同的弹容,不同的射速也会造成同一时间内不同枪支的对象池中可以同时存在的子弹数量不同,也就意味着不同的枪有不同的对象池,且不同的对象池使用的子弹,子弹的最大容量都有不同。在之前的笔记中学习到了Monobehavior的生命周期,每个生命周期都是要在每帧消耗性能的。因此我们要仔细考虑整个对象池的设计模式:

应当怎样实现对象池?首先从设计需求来看,一个对象池中存放N发子弹(N应当是可变的),且对象池中子弹也不能固定。其次,如果在游戏中切换枪支,由于不同枪支各有自己的对象池,因此使用的对象池也应当改变。最后,射击游戏中枪支的使用一般是贯穿全程的,所以无论场景变换,对象池始终应该是DDOL的。


总体设计

基于上述的想法,我们可以确定一个设计模式:首先用一个对象池管理器,这个对象池管理器是DDOL的,并且它可以管理对象池的切换,相当于切枪时也要切换对应的对象池。对于这个功能,我们可以用字典来实现,通过键值对以不同的键值来对应不同的对象池。对象池管理器可以方便的进行对象池(枪支)管理,例如有的枪支子弹用完了就要丢弃,我们就卸载对应的对象池,捡到新枪就加载对应的对象池。由于它是DDOL的,我们可以把上个场景的枪也保留到下一个场景,而不是销毁后重新加载。而基于这样的性质,对象池在游戏中也应当是全局唯一的,这就要求我们为它继承一个单例模式。

其次,对象池的设计,一般而言,我们应当在Awake时就为对象池生成最大数量N的子弹,而不是需要时生成,因为生成子弹的卡顿可能会摧毁玩家的游戏体验。我们宁可把1s的生成的时间放在加载界面,也不能让玩家在游玩时有0.1秒的卡顿。对于子弹在对象池中的存取,先发射的肯定先消失,那么队列就是最适合的数据结构,我们将其出队,当射程结束后再入队。这样就能保证持续射击时能不断生成子弹。

最后是子弹,由于同一个对象池类应当能发射不同的子弹,我们可以把子弹作为预制件来保存。有些属性是可以挂载在子弹物体本身上的。例如设计游戏时枪支属性和子弹属性分别计算的时候。枪支可以设计基类,那我们也可以设计一个子弹的基类来让其余子弹继承。


从生命周期考虑

决定这些系统的设计模式之后,那么应当考虑哪些脚本需要继承Monobehavior的生命周期。首先子弹是绝对不能继承的,如果对象池最多有100发子弹,并且同时拥有10个对象池。如果子弹有生命周期就会有1000个生命周期,反之则只有10个。所以子弹不能有生命周期,因此对于子弹的运动,我们应当把子弹的功能设计统筹到它的对象池中去,在对象池中用Update或者协程为这个GameObject进行一些需要更新的处理。

其次就是对象池,这个对象应该继承Mono behavior,这使得不同的对象池能够独立的处理它们自身不同的情况,更好的设计是写一个对象池的父类,并让子类来继承并重写一些可自定义的方法。

最后就是对象池管理器,如果我们想要它DDOL,那就需要继承Mono;如果它不用和生命周期交互,只是简单的管理对象池,就不需要,可继承也可不继承。而通常我们都是在单例模式设计时设计该单例是否继承Mono的。


一些代码

单例模式

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
            }
            if (_instance == null)
            {
                GameObject obj = new GameObject();
                obj.name = typeof(T).Name;
                obj.AddComponent<T>();
            }
            return _instance;
        }
    }
}

继承单例的对象池管理器

public class ObjPoolManager : Singleton<ObjPoolManager>
{
    [SerializeField]
    public Dictionary<string, ObjPool> m_poolDic;
    /// <summary>
    /// 读取挂载的子物体中的对象池并加入字典
    /// </summary>
    /// <param name="obj">用于统一管理对象池的父物体</param>
    public void initPool(GameObject obj)
    {
        m_poolDic = new Dictionary<string, ObjPool>();
        for (int i = 0; i < obj.transform.childCount; i++)
        {
            var thisChild = obj.transform.GetChild(i);
            m_poolDic.Add(thisChild.name, thisChild.GetComponent<ObjPool>());
        }
    }
    ///其他管理对象池的代码
}

对象池代码

public class ObjPool : MonoBehaviour
{
    [SerializeField]
    GameObject obj;
    [SerializeField]
    GameObject QiangKou;
    [SerializeField]
    int Maxnum = 0;


    void Start()
    {
        Initiate();
    }
    protected Queue m_queue;
    void Initiate()
    {
        m_queue = new Queue(Maxnum);
        for (int i = 0; i < Maxnum; i++)
        {
            var newobj = Instantiate(obj);
            newobj.transform.position = Vector3.zero;
            newobj.name = "bullet" + i.ToString();
            newobj.transform.SetParent(this.transform);
            m_queue.Enqueue(newobj);
            newobj.SetActive(false);
        }
    }

    void Shot(GameObject bullet)
    {
        Debug.Log("发射子弹:" + bullet.name);
        StartCoroutine(Fire(bullet));
    }

    IEnumerator Fire(GameObject bullet)
    {
        Debug.Log("发射:" + bullet.name);
        while (bullet.transform.position.x < 1500)
        {
            bullet.transform.position = new Vector3(bullet.transform.position.x + 10f, bullet.transform.position.y, bullet.transform.position.z);
            yield return new WaitForSeconds(0.02f);
        }
        InPool(bullet);
    }
    void OutPool()
    {
        GameObject bullet = (GameObject)m_queue.Dequeue();
        bullet.transform.position = QiangKou.transform.position;
        bullet.SetActive(true);
        Shot(bullet);
    }
    void InPool(GameObject bullet)
    {
        bullet.SetActive(false);
        m_queue.Enqueue(bullet);
    }
    public void OnShotClick()
    {
        Debug.Log("准备发射");
        OutPool();
    }
}

调用对象池管理器单例的其他类

public class GunsManager : MonoBehaviour
{
    void Start()
    {
        ObjPoolManager.Instance.initPool(this.gameObject);
    }
}

在这里插入图片描述
单对象池效果展示,其中白色块代表枪口,黑色块代表子弹
在这里插入图片描述
多对象池效果展示,三色按钮代表了三种不同的枪,点击按钮发射子弹

想要完善系统,则需要拓展对象池管理器类,然后通过其他类进行调用对象池管理器类。

在上述例子中,我只是将对象池类挂载到了每个按钮上,然后根据按钮触发事件来发射子弹。那么同样发射的代码就需要重复三次。更好的方式应当是统一使用一个按钮来执行发射对象池事件,可以直接定义在对象池管理器上,然后根据索引值来选择操作的对象池。

例如切枪操作,也可以通过修改对象池管理器的字典,通过对象池管理器,可以更灵活地管理对象池。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/55962.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NullPointerException导致手机重启案例分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、 Framework 层对象 空指针导致手机重启。二、解决方案&#xff0c;规避空指针三、Telecom APK 控制导致的重启举例 一、 Framework 层对象 空指针导…

XGBoost的基础思想与实现

目录 1. XGBoost VS 梯度提升树 1.1 XGBoost实现精确性与复杂度之间的平衡 1.2 XGBoost极大程度地降低模型复杂度、提升模型运行效率 1.3 保留了部分与梯度提升树类似的属性 2. XGBoost的sklearnAPI实现 2.1 sklearn API 实现回归 2.2 sklearn API 实现分类 3. XGBoost回…

MySQL 极速安装使用与卸载

目录 mysql-5.6.51 极速安装使用与卸载 sqlyog工具 mysql简化 mysql-8.1.0下载配置 再完善 mysql-5.6.51 极速安装使用与卸载 mysql-8.1.0下载安装在后 mysql中国官网 MySQLhttps://www.mysql.com/cn/ 点击MySQL社区服务器 点击历史档案 下载完 解压 用管理员运行cmd&a…

ODIN_1靶机详解

ODIN_1靶机复盘 下载地址&#xff1a;https: //download.vulnhub.com/odin/odin.ova 靶场很简单&#xff0c;一会儿就打完了。 靶场说明里提醒说加一个dns解析。 我们在/etc/hosts加一条解析 就能正常打开网站了&#xff0c;要么网站打开css是乱的。 这里看到结尾就猜测肯定…

uniapp自定义消息语音

需求是后端推送的消息APP要响自定义语音&#xff0c;利用官方插件&#xff0c;总结下整体流程 uniapp后台配置 因为2.0只支持uniapp自己的后台发送消息&#xff0c;所以要自己的后台发送消息只能用1.0 插件地址和代码 插件地址: link let isIos (plus.os.name "iOS&qu…

<C++> 三、内存管理

1.C/C内存分布 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";const char *pChar3 "abcd";int *ptr1…

小乌龟(TortoiseGit)连接GitLab

目录 &#x1f35f;写在前面 &#x1f35f;实验目标 &#x1f35f;安装gitlab &#x1f37f;1、安装依赖 &#x1f37f;2、下载清华gitlab包 &#x1f37f;3、安装gitlab &#x1f37f;4、修改配置文件 &#x1f37f;5、管理命令 &#x1f35f;访问gitlab &#x1f35f;界面设置…

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(16)-Fiddler如何充当第三者再识AutoResponder标签-上

1.简介 Fiddler充当第三者&#xff0c;主要是通过AutoResponder标签在客户端和服务端之间&#xff0c;Fiddler抓包&#xff0c;然后改包&#xff0c;最后发送。AutoResponder这个功能可以算的上是Fiddler最实用的功能&#xff0c;可以让我们修改服务器端返回的数据&#xff0c…

html学习3(表格table、列表list)

1、html表格由<table>标签来定义。 <thead>用来定义表格的标题部分&#xff0c;其内部用 <th > 元素定义列的标题&#xff0c;可以使其在表格中以粗体显示&#xff0c;与普通单元格区分开来。<tbody>用来定义表格的主体部分&#xff0c;其内部用<t…

力扣 63. 不同路径 II

题目来源&#xff1a;https://leetcode.cn/problems/unique-paths-ii/description/ C题解&#xff1a;动态规划五部曲。 确定dp数组&#xff08;dp table&#xff09;以及下标的含义。dp[i][j] &#xff1a;表示从(0, 0)出发&#xff0c;到(i, j) 有dp[i][j]条不同的路径。确定…

Vue 2.x 项目升级到 Vue 3详细指南【总结版】

文章目录 0.前言1.升级教程1.1. 升级 Vue CLI&#xff1a;1.2. 安装 Vue 3&#xff1a;1.3. 更新 Vue 组件&#xff1a;1.4. 迁移全局 API&#xff1a;1.5. 迁移路由和状态管理器&#xff1a;1.6. 迁移 TypeScript&#xff1a;1.7. 迁移测试代码&#xff1a; 2.迁移总结2.0. 这…

ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级

文章目录 1.什么是 OTA2. ESP32cam HTTP_OTA 本地准备2.1 HTTP OTA 升级原理2.2 开发板本地基准程序&#xff08;程序版本&#xff1a;1_0_0&#xff09;2.3 开发板升级程序&#xff08;程序版本&#xff1a;1_0_1&#xff09;2.4 本地 HTTP_OTA 升级测试2.4.1 本地运行一个 HT…

使用Linux部署Jpress博客系统

环境要求 linux系统&#xff1a;我使用的操作系统是CentOS7 数据库&#xff1a;mysql&#xff0c;也可以使用mariadb jdk&#xff1a;与你的Linux操作系统能兼容的版本 tomcat&#xff1a;我使用的是tomcat8版本 如果没有数据库&#xff0c;请先自行下载 如果没有安装jdk…

Agile manifesto principle (敏捷宣言的原则)

Agile在管理中越来越受推崇&#xff0c;最初是由于传统的软件开发管理方式&#xff08;瀑布模型&#xff09;面对日益复杂的需求&#xff0c;无法Delivery令人满意的结果&#xff0c;经过总结探索&#xff0c;2001年&#xff0c;由行业代表在一次聚会中提出Agile敏捷mainfesto&…

RK3588开发板 (armsom-w3) 之 USB摄像头图像预览

硬件准备 RK3588开发板&#xff08;armsom-w3&#xff09;、USB摄像头&#xff08;罗技高清网络摄像机 C93&#xff09;、1000M光纤 、 串口调试工具 v4l2采集画面 v4l2-ctl是一个用于Linux系统的命令行实用程序&#xff0c;用于控制视频4 Linux 2&#xff08;V4L2&#xff0…

P1257 平面上的最接近点对

题目 思路 详见加强加强版 代码 #include<bits/stdc.h> using namespace std; #define int long long const int maxn4e510; pair<int,int> a[maxn]; int n; double d1e16; pair<int,int> vl[maxn],vr[maxn]; void read() { cin>>n;for(int i1;i<…

(一)基于Spring Reactor框架响应式异步编程|道法术器

Spring WebFlux 响应式异步编程|道法术器(一) Spring WeFlux响应式编程整合另一种方案|道法术器(二) R2DBC简介 Spring data R2DBC是更大的Spring data 系列的一部分&#xff0c;它使得实现基于R2DBC的存储库变得容易。R2DBC代表反应式关系数据库连接&#xff0c;这是一种使用…

SpringBoot统一功能处理

我们要实现以下3个目标&#xff1a; 统一用户登录权限统一数据格式返回统一异常处理 1.用户的登录权限校验 1.1Spring AOP用户统一登录验证问题 Aspect Component public class UserAspect {// 定义切点controller包下、子孙包下所有类的所有方法Pointcut("execution(…

浅析大数据时代下的视频技术发展趋势以及AI加持下视频场景应用

视频技术的发展可以追溯到19世纪初期的早期实验。到20世纪初期&#xff0c;电视技术的发明和普及促进了视频技术的进一步发展。 1&#xff09;数字化&#xff1a;数字化技术的发明和发展使得视频技术更加先进。数字电视信号具有更高的清晰度和更大的带宽&#xff0c;可以更快地…

【李宏毅机器学习·学习笔记】Deep Learning General Guidance

本节课可视为机器学习系列课程的一个前期攻略&#xff0c;这节课主要对Machine Learning 的框架进行了简单的介绍&#xff1b;并以training data上的loss大小为切入点&#xff0c;介绍了几种常见的在模型训练的过程中容易出现的情况。 课程视频&#xff1a; Youtube&#xff1…