Unity游戏开发优化

不羁的心 提交于 2019-11-28 13:04:08

原文引用https://www.dazhuanlan.com/2019/08/25/5d6237a09e2b6/


概述

## 注意! 本篇博文的代码风格骤变,主要是最近看了一些C#规范,自己也就稍微注意了点。

如何解决?

想一想,当我们创建一个GameObject的时候,我们并没有使用new关键字,而是使用MonoBehaviour的静态方法Instantiate()。这样来我们就不得不把新的对象池组件挂到场景里去,这就产生关于MonoBehaviour单例实现的问题,不过这并不是本文的关键,所以不做详细解释。第二个问题在于GameObject对象的状态回收与初始化。GameObject并没有提供任何可供初始化的接口,事实上我们对他们的初始化往往也是来自外部,这样一来我们可以考虑使用委托的方式对GameObject实例进行初始化和回收的操作。

改进对象池

根据上述提出的一些问题和解决思路,适用于GameObject的对象池代码如下:

public class GameObjectPool : MonoBehaviour {

    public int Size {
		get {
			return idleObjects.Count + busyObjects.Count;
		}
	}

    private bool _ready;
    private int _maxCapacity;
    private string _loadPath;
    private Action<GameObject> _initAction;
    private Action<GameObject> _recycleAction;
    private ObjectPoolLimitOption _limitOption;

    private GameObject _prototype;
    private Queue<GameObject> _idleObjects;
    private List<GameObject> _busyObjects;

    public void Init(int maxCapacity, string loadPath, Action<GameObject> initAction, Action<GameObject> recycleAction, ObjectPoolLimitOption limitOption = ObjectPoolLimitOption.Rob) {
        if (_ready) return;

        _maxCapacity = maxCapacity;
        _loadPath = loadPath;
        _prototype = Resource.Load<GameObject>(_loadPath);
        _initAction = initAction;
        _recycleAction = recycleAction;
        _limitOption = limitOption;
        _idleObjects = new Queue<GameObject>(_maxCapacity);
        _busyObjects = new List<GameObject>(_maxCapacity);

        _ready = true;
    }

    public GameObject Get() {
        if (_ready) return null;

        if (idleObjects.Count == 0) {
			if (Size < _maxCapacity) {
				_idleObjects.Enqueue(Instantiate(_prototype));
				return Get();
			} else if (_limitOption == ObjectPoolLimitOption.Ignore) {
				return null; // 忽略,所以返回null
			} else {
				GameObject obj = _busyObjects[0]; // 获得第一个使用中对象
				Recycle(obj); // 强制回收
				return Get();
			}
		} else {
			GameObject obj = _idleObjects.Dequeue(); // 挑出对象
			_busyObjects.Add(obj); // 放入忙碌列表
			_initAction(obj);
			return obj;
		}
	}

    public void Recycle(GameObject obj) {
        if (_ready) {
            _recycleAction(obj);
            _busyObjects.Remove(obj);
            _idleObjects.Enqueue(obj);
        }
    }
}

public enum ObjectPoolMaximumOption {
	Ignore,
	Rob
}

上面代码里,由于我们有特定的GameObject对象需求,我们并不需要使用泛型。同时,我们移除了对象池的Instantiate()方法,而是用MonoBehaviour的静态方法完成。对于初始化与回收,我们通过两个带参无返回值的委托Action<GameObject>解决。由于整个脚本是继承自MonoBehaviour的,我们不得不加一个Init(int, string, Action<GameObject>, Action<GameObject>, ObjectPoolLimitOption)的方法进行初始化。这样一来,我们需要加一个_ready变量鉴定改对象池是否初始化过。如果没有初始化过,Get()就返回null,而Recycle(GameObject)就不进行操作。为了降低每次从硬盘读取数据的消耗,初始化对象池时,提前加载一个原型Prefab进入内存,方便后面生成对象实例。

缺点

首先是这个对象池的生命周期管理。由于初始化GameObject的需要,我们不得不继承自MonoBehaviour,并且放置于场景之中。这就导致我们需要手动对对象池初始化。对于一些全局的对象池,有可能虽然对象池是DontDestroyOnLoad(GameObject),但是GameObject对象的parent父对象是场景关联的,因此导致切换场景后发生错误。因此,我们需要对加载与卸载场景的事件进行监听,当检测到场景卸载时可以考虑清空对象队列或是将所有全局对象池生成的对象也归到DontDestroyOnLoad的场景下面。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!