【功能开发篇】使用对象池的注意事项

”功能开发篇“系列文章记录了我在平时工作中遇到的问题以及一些和游戏功能相关的项目经验。


对象池是一个老生常谈的东西。
根据我的经验,使用对象池时需要注意以下问题:

1. 如果对象在多个场景经常使用,那么这个对象所在的对象池不应该在过场景的时候清空。

典型的跟玩家相关的东西就需要保留,比如脚步烟啊,攻击特效啊这些,如果进入每个场景都重新生成,显然浪费内存。而每个场景很有可能不一样的东西,比如每关刷出来的敌人,它的对象池就应该清空。
具体的做法是在对象池的类里加一个属性:

public bool reserved {
    
     get; set; }

在生成对象池的时候由调用方来决定这个池需不需要保留,默认不保留:

public void Allocate( GameObject prefab, int count = 1, bool reserved = false )
{
    
    
	...
	pool.reserved |= reserved;
	...
}

上面代码使用位运算符|,a|b的意思是a和b只要有一个是true结果就是true。


2. 小心重复回收对象的情况。

想象一个情况,A和B同时持有对象C的引用。A先对C进行回收,B又对C进行回收。像这样重复调用同一函数容易出bug。当然你可以在回收的时候判断一下对象已经被回收或者对象是否为空。不过这种做法只是在掩盖问题而不是解决问题。比较好的做法是使用System.Environment.StackTrace来记录上次调用函数的调用栈,下次调用的时候,把这个打印出来并报个警告。


3. 注意初始化函数的调用时机

在对象池里,创建池通常有下列操作:
创建一组对象,然后Disable

for( int i = 0; i < count; i++ )
{
    
    
	var obj = Object.Instantiate( this.prefab );
	obj.SetActive( false );
	...
}

Unity的生命周期想必大家都很熟悉:
Awkae —— OnEnable —— Start

但是如果Instantiate一个gameobject后立刻把它disable它会调用哪些生命周期函数呢?答案是:
Awake —— OnEnable —— OnDisable

注意看没有Start!!!

那么Start什么时候会调用?答案是第一次从池子里拿出来的时候,并且会在Enable的下一帧进行调用
看下面的代码:

  • 这个是调用方:

      public GopObj obj;
      private GameObject _gameObject;
      
      void Start()
      {
          
          
         _gameObject = GameObject.Instantiate( obj ).gameObject;
         _gameObject.SetActive( false );
      }
      void Update()
      {
          
          
          if( Input.GetKeyDown( KeyCode.K ) )
          {
          
          
              _gameObject.SetActive( true );
              _gameObject.SendMessage( "OnNew", SendMessageOptions.DontRequireReceiver );
          }
          
      }
    
  • 这个是被调用方:

    public class GopObj : MonoBehaviour
    {
          
          
    	private void OnNew()
    	{
          
          
    		Debug.Log( "OnNew" );
    	}
    	void Start()
    	{
          
          
    		Debug.Log( "Start" );
    	}
    }
    

第一次SetActive:
OnNew —— Start
第N次SetActive:
OnNew
综上,使用对象池的时候,不建议把初始化代码放在Start里面。如果你的项目里已经使用了这种做法,请使用GameObject.SendMessage进行强制调用。


4. 遍历删除list对象记得从后往前遍历。

当对象池不再使用时,需要逐个删除池子里的对象。
如果从前往后遍历进行删除会导致列表删不干净,是新手常犯的错误。
错误写法:

	for( int i = 0; i < _list.count; i-- )
	{
    
    
		_list[i].RemoveAt(i);
	}

因为list每次删除对象时会把对象里的元素往前移,而循环里的i是不断往后的,最终导致list前面的几个元素没有移除。

正确写法:

	for( int i = _list.count - 1; i > 0; i-- )
	{
    
    
		_list[i].RemoveAt(i);
	}

以上就是我在使用对象池时遇到的一些坑,以后如果想到了新的内容会进行更新。



既然都看到这里了,不如关注一下吧

关于作者:

  • 水曜日鸡,简称水鸡,ACG宅。曾参与索尼中国之星项目研发,具有2D联网多人动作游戏开发经验。

CSDN博客:https://blog.csdn.net/j756915370
知乎专栏:https://zhuanlan.zhihu.com/c_1241442143220363264
Q群:891809847

Guess you like

Origin blog.csdn.net/j756915370/article/details/106159863