【功能開發篇】使用對象池的注意事項

”功能開發篇“系列文章記錄了我在平時工作中遇到的問題以及一些和遊戲功能相關的項目經驗。


對象池是一個老生常談的東西。
根據我的經驗,使用對象池時需要注意以下問題:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章