【Unity设计模式】使用对象池

news2024/12/24 9:08:50

在这里插入图片描述


前言

最近在学习Unity游戏设计模式,看到两本比较适合入门的书,一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》

这两本书介绍了大部分会使用到的设计模式,因此很值得学习

本专栏暂时先记录一些教程部分,深入阅读后续再更新


文章目录

  • 前言
  • 什么是对象池
  • 关于生命周期管理


什么是对象池

对象池技术是老生常谈的一个概念了,很简单,无论是软件开发还是编程语言的学习中,我们都经常接触到池化技术。池化技术指的是将一些常用的变量或对象存储到一个公共池中,当我们需要调用这些变量或对象的时候,直接引用公共池中的对象而无需new一个新对象。例如管理socket的连接池,管理string的Intern字符串池,还有管理GameObject的对象池等等等等。

在Unity中,如果你想要实现发射子弹的功能,那么就需要在发射时实例化子弹预制件。但是如果使用创建Instantiate()和销毁Destory()方法来控制子弹在场景中的生命周期,这无疑是一个很浪费性能的策略,会导致游戏运行卡顿。

所以我们引入了对象池,我们在场景初始化的时候提前生成好指定数量的子弹实例,当我们发射子弹时,从对象池中取出物体,而销毁子弹时则将他们放回对象池。这样对性能的消耗就小多了。

优点是能够更好的管理物体,减少Cpu压力,减少对象生成占用堆空间导致的GC

对象池代码挺简单的,主要由三个部分组成:
1.对象池初始化代码
2.从对象池取出物体的代码
3.将物体放回对象池的代码

public class ObjPool<T> where T : new()
{
	private Stack<T> ObjStack;

	public ObjPool (int maxCount)
	{
		ObjStack = new Stack<T>(maxCount);
		for (int i = 0; i < maxCount; i++)
		{
			var obj = new T();
			ObjStack.Push(obj);
		}
	}

	public void InPool (T obj)
	{
		ObjStack.Push(obj);
	}

	public T OutPool()
	{
		return ObjStack.Pop();
	}
}

很简单就能手搓一个对象池,当然上述代码只是随手写的,也可也根据自己需要来写一个对象池

为什么我不介绍unity的对象池呢,因为unity太幽默了,大部分版本不支持UnityEngine.Pool这个命名空间。不过可以贴出它的代码,相比于我们的代码,就是在对象池函数更完善了,且在执行时添加了一些委托方法以供触发。

using System;
using System.Collections.Generic;

namespace UnityEngine.Pool
{
  /// <summary>
  ///   <para>A stack based Pool.IObjectPool_1.</para>
  /// </summary>
  public class ObjectPool<T> : IDisposable, IObjectPool<T> where T : class
  {
    internal readonly List<T> m_List;
    private readonly Func<T> m_CreateFunc;
    private readonly Action<T> m_ActionOnGet;
    private readonly Action<T> m_ActionOnRelease;
    private readonly Action<T> m_ActionOnDestroy;
    private readonly int m_MaxSize;
    internal bool m_CollectionCheck;

    public int CountAll { get; private set; }

    public int CountActive => this.CountAll - this.CountInactive;

    public int CountInactive => this.m_List.Count;

    public ObjectPool(
      Func<T> createFunc,
      Action<T> actionOnGet = null,
      Action<T> actionOnRelease = null,
      Action<T> actionOnDestroy = null,
      bool collectionCheck = true,
      int defaultCapacity = 10,
      int maxSize = 10000)
    {
      if (createFunc == null)
        throw new ArgumentNullException(nameof (createFunc));
      if (maxSize <= 0)
        throw new ArgumentException("Max Size must be greater than 0", nameof (maxSize));
      this.m_List = new List<T>(defaultCapacity);
      this.m_CreateFunc = createFunc;
      this.m_MaxSize = maxSize;
      this.m_ActionOnGet = actionOnGet;
      this.m_ActionOnRelease = actionOnRelease;
      this.m_ActionOnDestroy = actionOnDestroy;
      this.m_CollectionCheck = collectionCheck;
    }

    public T Get()
    {
      T obj;
      if (this.m_List.Count == 0)
      {
        obj = this.m_CreateFunc();
        ++this.CountAll;
      }
      else
      {
        int index = this.m_List.Count - 1;
        obj = this.m_List[index];
        this.m_List.RemoveAt(index);
      }
      Action<T> actionOnGet = this.m_ActionOnGet;
      if (actionOnGet != null)
        actionOnGet(obj);
      return obj;
    }

    public PooledObject<T> Get(out T v) => new PooledObject<T>(v = this.Get(), (IObjectPool<T>) this);

    public void Release(T element)
    {
      if (this.m_CollectionCheck && this.m_List.Count > 0)
      {
        for (int index = 0; index < this.m_List.Count; ++index)
        {
          if ((object) element == (object) this.m_List[index])
            throw new InvalidOperationException("Trying to release an object that has already been released to the pool.");
        }
      }
      Action<T> actionOnRelease = this.m_ActionOnRelease;
      if (actionOnRelease != null)
        actionOnRelease(element);
      if (this.CountInactive < this.m_MaxSize)
      {
        this.m_List.Add(element);
      }
      else
      {
        Action<T> actionOnDestroy = this.m_ActionOnDestroy;
        if (actionOnDestroy != null)
          actionOnDestroy(element);
      }
    }

    public void Clear()
    {
      if (this.m_ActionOnDestroy != null)
      {
        foreach (T obj in this.m_List)
          this.m_ActionOnDestroy(obj);
      }
      this.m_List.Clear();
      this.CountAll = 0;
    }

    public void Dispose() => this.Clear();
  }
}


关于生命周期管理

还是射击游戏的例子,现在我们要枪射出子弹,代码设计如下:

1.创建一个枪类
2.在枪类中创建一个接受子弹类的对象池
3.创建n个子弹类的预制体

那么我们该如何管理它们的生命周期?或者说上述三个类哪些需要执行MonoBehaviour的Update方法?

首先,对象池类肯定不执行Update方法,它就不该继承MonoBehaviour,我们只需要让它实现管理物体进出池的方法,对物体本身生命周期的执行不该由他执行。

子弹类可以实现Update方法吗?由于子弹类是GameObject,所以通常需要继承MonoBehaviour,但不代表生命周期函数一定由子弹类自身执行。很简单的道理,因为每个Update的存在都是对Unity 的性能消耗,假设场景里有十把枪,每把发射100发子弹,那么总计1000发子弹就得执行1000个Update方法!显然这是不合适的。

所以正常的方式是让枪类实现Update方法,并在枪类中对子弹应做的Update事件进行处理。这样10把枪就只需要10个update就能管理1000发子弹。

using UnityEngine.Pool;

public class RevisedGun : MonoBehaviour
{// stack-based ObjectPool available with Unity 2021 and above
    private IObjectPool<RevisedProjectile> objectPool;

    // throw an exception if we try to return an existing item, already in the pool
    [SerializeField] private bool collectionCheck = true;

    // extra options to control the pool capacity and maximum size
    [SerializeField] private int defaultCapacity = 20;
    [SerializeField] private int maxSize = 100;

    private void Awake()
    {
        objectPool = new ObjectPool<RevisedProjectile>(CreateProjectile,
            OnGetFromPool, OnReleaseToPool, OnDestroyPooledObject,
            collectionCheck, defaultCapacity, maxSize);
    }

    // invoked when creating an item to populate the object pool
    private RevisedProjectile CreateProjectile()
    {
        RevisedProjectile projectileInstance = Instantiate(projectilePrefab);
        projectileInstance.ObjectPool = objectPool;
        return projectileInstance;
    }

    // invoked when returning an item to the object pool
    private void OnReleaseToPool(RevisedProjectile pooledObject)
    {
        pooledObject.gameObject.SetActive(false);
    }

    // invoked when retrieving the next item from the object pool
    private void OnGetFromPool(RevisedProjectile pooledObject)
    {
        pooledObject.gameObject.SetActive(true);
    }

    // invoked when we exceed the maximum number of pooled items (i.e. destroy the pooled object)
    private void OnDestroyPooledObject(RevisedProjectile pooledObject)
    {
        Destroy(pooledObject.gameObject);
    }

    private void FixedUpdate()
    {}
}

还有一点,就是如果多个对象引用同一个对象池,那么我们在实现对象池代码的时候还需要注意类型安全,线程安全等特性。

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

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

相关文章

NSIS 打包发布 exe 安装包之 配置文件参数说明

一、打包exe教程 详见上期博客&#xff1a;visual studio打包QT工程发布exe安装包 二、参数说明 1、程序图标显示无效问题 在nsi配置文件中找到以下行&#xff0c;分别在尾部追加 “” “$INSTDIR\logo-ico.ico” &#xff0c; logo-ico.ico为程序图标名称&#xff0c;Setup…

Flutter学习目录

学习Dart语言 官网&#xff1a;https://dart.cn/ 快速入门&#xff1a;Dart 语言开发文档&#xff08;dart.cn/guides&#xff09; 学习Flutter Flutter生命周期 点击跳转Flutter更换主题 点击跳转StatelessWidget和StatefulWidget的区别 点击跳转学习Flutter中新的Navigato…

基于Java的汽车租赁系统【附源码】

论文题目 设计&#xff08;论文&#xff09;综述&#xff08;1000字&#xff09; 当今社会&#xff0c;汽车租赁已成为一种受欢迎的出行方式。本文旨在探讨汽车租赁行业的发展趋势、市场规模及其对环境的影响。目前&#xff0c;汽车租赁行业正在经历着快速的发展。随着经济的发…

昇思25天学习打卡营第9天|使用静态图加速

一、简介&#xff1a; AI编译框架分为两种运行模式&#xff0c;分别是动态图模式以及静态图模式。MindSpore默认情况下是以动态图模式运行&#xff0c;但也支持手工切换为静态图模式。两种运行模式的详细介绍如下&#xff1a; &#xff08;1&#xff09;动态图&#xff1a; …

维基百科:12种维基百科推广技术让你成为行业专家

维基百科&#xff08;Wikipedia&#xff09;作为全球最大的免费网络百科全书&#xff0c;已经成为人们获取知识的重要源泉之一。对于想要在特定领域成为行业专家的人来说&#xff0c;利用维基百科进行推广是一种非常有效的方式。本文将介绍12种维基百科推广技术&#xff0c;帮助…

奔驰汽车的通信如此固若金汤的原因

随着摄像系统、距离控制、航线保持等功能以及制动辅助系统、制动力分配系统、车身侧倾干预与缓解系统等功能的飞速发展,汽车的系统功能之间已经不再独立,而是呈现互相合作的关系,各功能之间的无缝集成更是各大整车厂追求的目标。俗话说,外练筋骨皮,内练一口气,有了各式安…

alibaba easyexcel 导出excel使用

需求 传统导出&#xff0c;一般都是通过Workbook > Sheet > Row > Cell 获取详细Cell 设置值&#xff0c;比较麻烦&#xff0c;偶然遇到alibaba easyexcel 直接通过注解设置哪些需要导出 哪些忽略&#xff0c;发现特别好用。 pom依赖 <dependency><groupId…

yolov10打包为exe

一、前言 本节实验将官方yolov10推理程序打包为exe运行 二、代码 首先下载官方代码至本机&#xff0c;并使用conda创建虚拟环境&#xff0c;并安装好yolov10所需库 conda create --prefix E:/pyenv/myYolo10 python3.8 pip install -r requirements.txt 下载官方模型权重 …

HarmonyOS Next开发学习手册——内存管理(GC)

GC&#xff08;全称 Garbage Collection&#xff09;&#xff0c;即垃圾回收。在计算机领域&#xff0c;GC就是找到内存中的垃圾&#xff0c;释放和回收内存空间。当前主流编程语言实现的GC算法主要分为两大类&#xff1a;引用计数和对象追踪&#xff08;即Tracing GC&#xff…

springcloud-config 客户端启用服务发现client的情况下使用metadata中的username和password

为了让spring admin 能正确获取到 spring config的actuator的信息&#xff0c;在eureka的metadata中添加了metadata.user.user metadata.user.password eureka.instance.metadata-map.user.name${spring.security.user.name} eureka.instance.metadata-map.user.password${spr…

Java中的程序异常处理介绍

一、异常处理机制 Java提供了更加优秀的解决办法&#xff1a;异常处理机制。 异常处理机制能让程序在异常发生时&#xff0c;按照代码的预先设定的异常处理逻辑&#xff0c;针对性地处理异常&#xff0c;让程序尽最大可能恢复正常并继续执行&#xff0c;且保持代码的清晰。 Ja…

Spring事务的源码底层实现

文章目录 事务理论执行过程EnableTransactionManagement底层实现 事务 在线流程图 理论执行过程 通过事务管理器创建一个连接对象connection1设置事务隔离级别、是否只读等conn1.autocommit(false)将conn1存入ThreadLocal中Map<DataSource,Connection>执行目标方法、多…

c++习题01-ljc的暑期兼职

目录 一&#xff0c;题目描述 二&#xff0c;思路 三&#xff0c;伪代码 四&#xff0c;流程图 五&#xff0c;代码 一&#xff0c;题目描述 二&#xff0c;思路 1&#xff0c;根据题目要求需要声明4个变量&#xff1a;a,b,c,d ;牛奶价格a&#xff0c;活动要求b&…

浅析Resource Quota中limits计算机制

前言 在生产环境中&#xff0c;通常需要通过配置资源配额&#xff08;Resource Quota&#xff09;来限制一个命名空间&#xff08;namespace&#xff09;能使用的资源量。在资源紧张的情况下&#xff0c;常常需要调整工作负载&#xff08;workload&#xff09;的请求值&#xf…

java基于ssm+jsp 毕业生就业信息管理系统

1管理员功能模块 管理员输入个人的用户名、密码、角色登录系统&#xff0c;这时候系统的数据库就会在进行查找相关的信息&#xff0c;如果我们输入的用户名、密码不正确&#xff0c;数据库就会提示出错误的信息提示&#xff0c;同时会提示管理员重新输入自己的用户名、密码&am…

MYSQL存储过程的创建

关于存储过程的题目 1、创建存储过程,查看user表中的所有数据 2、创建存储过程avg_order_quantity,返回所有订单的平均工资 3、创建存储过程show_max_bprice,用来查看bookS的单价最贵的价格 4、创建存储过程show_min_bprice,用来查看bookS的单价最低的价格&#xff0c;并将…

JS在线加密简述

JS在线加密&#xff0c;是指&#xff1a;在线进行JS代码混淆加密。通过混淆、压缩、加密等手段&#xff0c;使得JS源代码难以阅读和理解。从而可以有效防止代码被盗用或抄袭&#xff0c;保护开发者的知识产权和劳动成果。常用的JS在线加密网站有&#xff1a;JShaman、JS-Obfusc…

美业管理系统的优势和功能分析,美业系统你选对了吗?Java源码/演示视频分享

在当今竞争激烈的美业市场中&#xff0c;有效的管理对于提高效率、增强客户体验和推动业务增长至关重要。美业管理系统通过其各种功能和优势&#xff0c;成为现代美业企业不可或缺的利器。 本文将探讨美业管理系统的优势和功能&#xff0c;以及它们对美业企业的重要性。 1.预…

来聊聊Redis客户端的概念

写在文章开头 对于每一个建立的连接redis都会通过redisClient来管理建立的socket连接的信息&#xff0c;本文将从源码的分析的角度来剖析的Redis客户端的基本设计和实现。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 C…

通信协议总结

IIC 基本特点 同步&#xff0c;半双工 标准100KHz&#xff0c;最高400KHz&#xff08;IIC主要应用于低速设备&#xff09; 硬件组成 需外接上拉电阻 通信过程 空闲状态 SDA和SCL都处于高电平 开始信号S和终止信号P 在数据传输过程中&#xff0c;当SCL0时&#xff0c;SDA才…