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

Unity3D学习FPS游戏(7)优化发射子弹(对象池版)

前言:上一篇实现了发射子弹简单的版本,但是需要频繁的创建和销毁子弹,会有很大的开销。本篇利用对象池对发射子弹的方法进行优化。

对象池优化发射子弹

  • 对象池介绍
    • 对象池的作用和逻辑
    • Unity中对象池的两种实现方式
  • 对象池优化子弹管理
    • 武器部分的思路和代码
    • 子弹部分的思路和代码
  • 效果
  • 补充知识
    • 生命周期
    • SetActive

对象池介绍

对象池的作用和逻辑

场景:发射的时候会创建子弹,子弹达到一定射程之后会销毁子弹。如果频繁发射,需要频繁的创建和销毁子弹,会有很大的开销。

对象池:子弹对象池可以把本来需要销毁的子弹对象给缓存起来,等到下次要发射子弹的时候,就直接重复利用不用创建。这样就节约了系统创建和销毁的开销。

Unity中对象池的两种实现方式

实现方式通常有两种,一种是自己实现对象池,一种是Unity官方的对象池。

自己实现对象池

前面已经理解了对象池的逻辑思路,如果需要自己实现对象池类ObjectPool的话,需要实现下面几个功能。

变量参数

  • GameObject队列,用来缓存所有的子弹。
  • maxNum变量,队列缓存的子弹最大容量。
  • 预制体,用来生成子弹的预制体。

基本功能函数

  • ReturnObject(入队):当子弹达到射程后,子弹会被返还到缓存队列中;如果数量超过maxNum容量,就直接销毁。
  • GetObject(出队):当需要用到子弹的时候,从缓存队列中拿出子弹返回;如果队列中没有子弹,就创建新的子弹返回。

使用逻辑

  • 武器类:发射子弹的时候,就调用对象池类的GetObject获取子弹,然后重置参数并发射子弹。
  • 子弹类:发射函数中达到射程后,调用对象池类的ReturnObject返还子弹。

本篇文章就不自己实现对象池类了,而是采用Unity官方提供的。

Unity官方的对象池

前提Unity得是2021版本或以上的,头文件调用using UnityEngine.Pool就可以使用了。

using UnityEngine.Pool;

声明对象池:

public ObjectPool<GameObject> pool;// 子弹的话,GameObject就是子弹的预制体

构造对象池:

pool = new ObjectPool<GameObject>(createFunc,actionOnGet,actionOnRelease,actionOnDestroy,true,10,100);
// 构造参数介绍:
// createFunc函数 从对象池中获取实例,但队列为空
// actionOnGet函数 从对象池中获取实例,队列还有空余
// actionOnRelease函数 实例被放回对象池,队列还有空余
// actionOnDestroy函数 实例被放回对象池,但队列为满
// 安全检查变量,通常为true
// 默认池容量大小,这里设置为10
// 池子最大容量大小,这里设置为100

调用对象池方法:

GameObject obj = pool.Get();// 获取对象池物体
pool.Release(obj);// “销毁”对象池物体,销毁并不是真的销毁,可能只是放回对象池等待重复利用。

对象池优化子弹管理

武器部分的思路和代码

在WeaponController代码中,使用子弹对象池对子弹进行管理。
根据需求编写,构造对象池的四个函数和参数,就好了。

private ObjectPool<GameObject> bulletPool;// 子弹对象池

private void Awake()
{
	// 初始化构造对象池
    bulletPool = new ObjectPool<GameObject>(CreateBullet,BulletOnGet, BulletOnRelease, BulletOnDestory,true,10,bulletNum);
}
// 从对象池中获取实例,但队列为空:新建子弹,传递给子弹代码对象池,因为子弹销毁的使用要用
GameObject CreateBullet()
{
    GameObject obj = Instantiate(bullet, shootPoint);
    obj.GetComponent<BulletController>().bulletPool = bulletPool;
    return obj;
}
// 从对象池中获取实例,队列还有空余:直接让闲置的子弹可用,并且初始化位置参数和射程(BulletReset)
void BulletOnGet(GameObject obj)
{
	obj.gameObject.SetActive(true);
    obj.GetComponent<BulletController>().BulletReset();
}
// 实例被放回对象池,队列还有空余:子弹用完了,池子没满,设置为不可用即可,SetActive(false)的时候Update都不会更新
void BulletOnRelease(GameObject obj)
{
    obj.gameObject.SetActive(false);
}
// 实例被放回对象池,但队列为满:子弹用完了,池子满了,直接销毁就好了
void BulletOnDestory(GameObject obj)
{
    Destroy(obj);
}
// 发射子弹的协程
IEnumerator Shoot()
{
    while (isFire)
    {
        if (currentBulletNum >0)
        {
        	// GameObject newBullet = Instantiate(bullet, shootPoint);// 原先直接创建子弹
            GameObject newBullet = bulletPool.Get();// 现在改为对象池生成子弹
            currentBulletNum--;
        }
        yield return new WaitForSeconds(shootInterval);
    }
}

子弹部分的思路和代码

子弹销毁的时候,调用对象池的Rlease功能,对象池在创建的时候就从武器那边被传递了。同时设置一个Reset函数给对象池调用的时候初始化重置。

public ObjectPool<GameObject> bulletPool;

private void FixedUpdate()
{
    transform.position+=transform.forward *bulletSpeed * Time.fixedDeltaTime;
    range -= bulletSpeed * Time.fixedDeltaTime;
    if (range <= 0)// 达到射程后
    {
        //Destroy(this.gameObject);// 原先是直接销毁
        bulletPool.Release(this.gameObject);// 回归对象池
    }
}
// 队列有空余调用的时候,进行的子弹重置参数
public void BulletReset()
{
    transform.localPosition = new Vector3(0,0,0);
    range = 100;
}

效果

发射子弹的时候,如果子弹对象池中有可用的,会直接激活对象池的中的子弹数据。
在这里插入图片描述

补充知识

生命周期

下面是一个Unity生命周期,有很多可以先不用管,关注最右边的和一些常用的生命周期函数就好了。

最右边,可以简单理解为几个阶段:

  • 初始化:Initializaiton、Editor
  • 物理和输入:Physics、Input events
  • 游戏逻辑:Game logic
  • 场景和UI渲染:Scene rendering、Gizmo rendering、GUI rendering
  • 暂停和帧结束:End of frame、Pausing
  • 关闭:Decommissioning

在这里插入图片描述
常用的生命周期函数:
在这里插入图片描述

SetActive

用来禁用对象,可以设置GameObject是否可用,和GameObject中勾选的功能是一样。
在这里插入图片描述
设置为false后,物体不可见,同时停用GameObject会禁用每个组件,包括附加的渲染器、碰撞器、刚体和脚本。
注意脚本中,大部分生命周期函数不会执行,但是内置的事件监测的方法,例如OnMouseDown(),OnTriggerEnter()、调用自定义函数都是可以运行的。

常常会和Enable比较。Enable是用于启用或禁用特定组件的方法,例如Renderer、Collider、Light等。当组件被禁用时,它将停止执行其相应的功能,但游戏对象本身及其其他组件仍然处于活动状态。

Enable禁用组件,SetActive禁用游戏对象。


http://www.kler.cn/a/381835.html

相关文章:

  • 如何看待AI技术的应用场景:现状与未来的全面解析
  • 导入和部署自定义 LLM 大模型
  • Linux——Linux基础指令
  • 【北京迅为】《STM32MP157开发板嵌入式开发指南》-第七十二章 Debian文件系统
  • 智能语音机器人智能在哪里?AI人工智能电话机器人部署
  • 基于STM32的手式电视机遥控器设计
  • 【LeetCode】【算法】128. 最长连续序列
  • 【系统架构设计师】六、UML建模与架构文档化
  • 传智杯 第六届-复赛-第二场-B
  • Rust 跨平台构建与部署实战:构建并部署跨平台应用
  • SpringCloudGateway — 网关路由
  • 宝塔Linux面板安装PHP扩展失败报wget: unable to resolve host address ‘download.bt.cn’
  • VLAN高级+以太网安全
  • C++原创游戏宝强越狱第二季即将回归
  • Kafka 之消息广播消费
  • C++简单工厂模式
  • vue 3:监听器
  • Chrome与火狐哪个浏览器的性能表现更好
  • 计算机性能监控体系:Quark2.0
  • 用例设计方法之等价类划分法
  • Linux常用命令(你一定用得上!)
  • 基于Redis缓存机制实现高并发接口调试
  • 【大数据学习 | kafka】消费者的分区分配规则
  • WEB 应用防护系统的部署方式
  • 软件工程3.0和软件工程2.0的区别
  • 深度学习注意力机制类型总结pytorch实现代码