Unity 協程的使用簡介

猴子原創,歡迎轉載。轉載請註明: 轉載自Cocos2D開發網–Cocos2Dev.com,謝謝!

原文地址: http://www.cocos2dev.com/?p=496


Coroutine在Unity3D中叫做協程或協同程序,和多線程類似,也就是說開啓協同程序就是開啓一個線程。但是在任意指定時刻只有一個協程執行,其他協程掛起。


 Coroutine的相關函數:

StartCoroutine:啓動一個協程。

StopCoroutine:終止一個協程。

StopAllCoroutine:終止所有協程。

WaitForSeconds:等待幾秒。

WaitForFixedUpdate:等到下一次FixedUpdate調用時執行。


使用MonoBehaviour.StartCoroutine方法即可開啓一個協同程序,也就是說該方法必須在 MonoBehaviour 或繼承於MonoBehaviour的類中調用。


(下面一段是引用與網絡上教程,因爲講的很詳細了,所以這裏直接引用下。爲找到原始出處)


注意:在unity3d中,

StartCoroutine(string methodName)

StartCoroutine(IEnumerator routine)

都可以開啓一個線程。

區別在於使用字符串作爲參數可以開啓線程並在線程結束前終止線程,相反使用IEnumerator 作爲參數只能等待線程的結束而不能隨時終止(除非使用StopAllCoroutines()方法);

另外使用字符串作爲參數時,開啓線程時最多隻能傳遞 一個參數,並且性能消耗會更大一點,而使用IEnumerator 作爲參數則沒有這個限制。

在Unity3D中,使用StopCoroutine(string methodName)來終止一個協同程序,使用StopAllCoroutines()來終止所有可以終止的協同程序,但這兩個方法都只能終止該 MonoBehaviour中的協同程序。


還有一種方法可以終止協同程序,即將協同程序所在gameobject的active屬性設置爲false,當再次設置active爲ture時,協同程序並不會再開啓;

如是將協同程序所在腳本的enabled設置爲false則不會生效。這是因爲協同程序被開啓後作爲一個線程在運行,而 MonoBehaviour也是一個線程,他們成爲互不干擾的模塊,除非代碼中有調用,他們共同作用於同一個對象,只有當對象不可見才能同時終止這兩個線 程。

然而,爲了管理我們額外開啓的線程,Unity3D將協同程序的調用放在了MonoBehaviour中,這樣我們在編程時就可以方便的調用指定腳本 中的協同程序,而不是無法去管理,特別是對於只根據方法名來判斷線程的方式在多人開發中很容易出錯,這樣的設計保證了對象、腳本的條理化管理,並防止了重 名。

注意:在GameObject.active = false 時他會銷燬你的任何一個協同程序過程,並且不會等待過程結束後銷燬,GameObject.enable時他並不可以恢復。

協同程序的返回類型爲Coroutine類型。在Unity3D中,Coroutine類繼承於YieldInstruction,所以,協同程序的返回類型只能爲null、等待的幀數(frame)以及等待的時間。


準備寫個例子,但是MOMO的這個已經非常詳細了,而且我的是基礎版,所以這裏就直接把MOMO這篇放過來了。(點擊查看原文地址


異步任務相信大家應該不會陌生,那麼本章內容MOMO將帶領大家學習Unity中的一些異步任務。在同步加載遊戲場景的時候通常會使用方法 Application.LoadLevel(“yourScene”);  這句代碼執行完畢後程序會幹什麼呢??如下圖所示,這是我隨便找了一個遊戲場景, 在Hierarchy視圖中我們可以看到該場景中“天生”的所有遊戲對象。天生的意思就是運行程序前該場景中就已經存在的所有遊戲對象。然後這些對象就會在執行完Application.LoadLevel(“yourScene”);方法後加載至內存當中。如果該場景中的遊戲對象過多那麼瞬間將會出現卡一下的情況,因爲LoadLevel()方法是同步進行的。MOMO把這種加載起個名字叫A形式加載。




下面我說說“後天“加載的遊戲對象。意思是這些遊戲對象是通過腳本動態的創建出來的。比如常用方法 :

  1. GameObject Obj = (GameObject)Instantiate(prefab);  
GameObject Obj = (GameObject)Instantiate(prefab);

這句代碼執行完畢後同樣會在Hierarchy視圖中添加對應的遊戲對象。MOMO把這種加載起個名字叫B形式加載。
 
下面我們學習異步加載遊戲場景,異步異步顧名思義就是不影響當前遊戲場景的前提下加載新場景。通常異步加載的方式分爲兩種:第一種是異步加載新遊戲場景,當新場景加載完成後進入新場景並且銷燬之前的場景。第二種:同樣異步加載新場景,新場景加載完畢後,保留舊場景的遊戲對象並且進入新場景。 這裏加載的內容就是上面提到的A形式加載。然後B形式加載不會記入這裏的加載。
第一種異步加載遊戲場景對應的方法是:

  1. Application.LoadLevelAsync(“yourScene”);//only pro  
Application.LoadLevelAsync("yourScene");//only pro

第二種異步家在遊戲場景對應的方法是:

  1. Application.LoadLevelAdditiveAsync (“yourScene”);  
Application.LoadLevelAdditiveAsync ("yourScene");

這兩種方法加載的方式完全一樣。異步加載其實重要還是應用於遊戲LOADING界面,畢竟LOADING如果採用同步的機制會影響用戶體驗,說到這裏MOMO告訴大家如何在Unity中製作遊戲進度條。我們應當在Unity中創建一個專門用於讀取進度的場景,假設A場景到C場景,我們應當讓A場景先到讀取進度的場景B場景,當異步任務完成後在進入C場景。 A – 》B -》 C ,在B場景中繪製遊戲進度條或讀取動畫。因爲B場景僅僅是個顯示LOADING動畫的場景,所以讀取該場景是瞬間就完成的。
程序在切換場景時應當有一個全全局的靜態變量來記錄簡要讀取的場景名稱。這裏簡單的寫一下。

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class Globe  
  5. {  
  6.          //在這裏記錄當前切換場景的名稱  
  7.      public static string loadName;  
  8. }  
using UnityEngine;
using System.Collections;

public class Globe
{
         //在這裏記錄當前切換場景的名稱
     public static string loadName;
}


在A場景中通過某些觸發條件 調用LoadLevel進入B場景。

  1. //記錄LOADING場景中需要讀取的C場景名稱  
  2. Globe.loadName = ”C”;  
  3. //先進入B場景  
  4. Application.LoadLevel (”B”);  
//記錄LOADING場景中需要讀取的C場景名稱
Globe.loadName = "C";
//先進入B場景
Application.LoadLevel ("B");


OK我們在B場景中異步讀取C場景與 播放讀取動畫,Loading.cs 綁定在B場景的攝像機對象身上。當C場景異步讀取完畢後即可直接進入C場景。

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class Loading : MonoBehaviour {  
  5.   
  6.     private float fps = 10.0f;  
  7.     private float time;  
  8.     //一組動畫的貼圖,在編輯器中賦值。  
  9.     public Texture2D[] animations;  
  10.     private int nowFram;  
  11.     //異步對象  
  12.     AsyncOperation async;  
  13.   
  14.     //讀取場景的進度,它的取值範圍在0 - 1 之間。  
  15.     int progress = 0;  
  16.   
  17.     void Start()  
  18.     {  
  19.         //在這裏開啓一個異步任務,  
  20.         //進入loadScene方法。  
  21.         StartCoroutine(loadScene());  
  22.     }  
  23.   
  24.     //注意這裏返回值一定是 IEnumerator  
  25.     IEnumerator loadScene()  
  26.     {  
  27.         //異步讀取場景。  
  28.         //Globe.loadName 就是A場景中需要讀取的C場景名稱。  
  29.         async = Application.LoadLevelAsync(Globe.loadName);  
  30.   
  31.         //讀取完畢後返回, 系統會自動進入C場景  
  32.         yield return async;  
  33.   
  34.     }  
  35.   
  36.     void OnGUI()  
  37.     {  
  38.         //因爲在異步讀取場景,  
  39.         //所以這裏我們可以刷新UI  
  40.         DrawAnimation(animations);  
  41.   
  42.     }  
  43.   
  44.     void Update()  
  45.     {  
  46.   
  47.         //在這裏計算讀取的進度,  
  48.         //progress 的取值範圍在0.1 - 1之間, 但是它不會等於1  
  49.         //也就是說progress可能是0.9的時候就直接進入新場景了  
  50.         //所以在寫進度條的時候需要注意一下。  
  51.         //爲了計算百分比 所以直接乘以100即可  
  52.         progress =  (int)(async.progress *100);  
  53.   
  54.         //有了讀取進度的數值,大家可以自行製作進度條啦。  
  55.         Debug.Log(”xuanyusong” +progress);  
  56.   
  57.     }  
  58.     //這是一個簡單繪製2D動畫的方法,沒什麼好說的。  
  59.     void   DrawAnimation(Texture2D[] tex)  
  60.     {  
  61.   
  62.         time += Time.deltaTime;  
  63.   
  64.          if(time >= 1.0 / fps){  
  65.   
  66.              nowFram++;  
  67.   
  68.              time = 0;  
  69.   
  70.              if(nowFram >= tex.Length)  
  71.              {  
  72.                 nowFram = 0;  
  73.              }  
  74.         }  
  75.         GUI.DrawTexture(new Rect( 100,100,40,60) ,tex[nowFram] );  
  76.   
  77.         //在這裏顯示讀取的進度。  
  78.         GUI.Label(new Rect( 100,180,300,60), “lOADING!!!!!” + progress);  
  79.   
  80.     }  
  81.   
  82. }  
using UnityEngine;
using System.Collections;

public class Loading : MonoBehaviour {

    private float fps = 10.0f;
    private float time;
    //一組動畫的貼圖,在編輯器中賦值。
    public Texture2D[] animations;
    private int nowFram;
    //異步對象
    AsyncOperation async;

    //讀取場景的進度,它的取值範圍在0 - 1 之間。
    int progress = 0;

    void Start()
    {
        //在這裏開啓一個異步任務,
        //進入loadScene方法。
        StartCoroutine(loadScene());
    }

    //注意這裏返回值一定是 IEnumerator
    IEnumerator loadScene()
    {
        //異步讀取場景。
        //Globe.loadName 就是A場景中需要讀取的C場景名稱。
        async = Application.LoadLevelAsync(Globe.loadName);

        //讀取完畢後返回, 系統會自動進入C場景
        yield return async;

    }

    void OnGUI()
    {
        //因爲在異步讀取場景,
        //所以這裏我們可以刷新UI
        DrawAnimation(animations);

    }

    void Update()
    {

        //在這裏計算讀取的進度,
        //progress 的取值範圍在0.1 - 1之間, 但是它不會等於1
        //也就是說progress可能是0.9的時候就直接進入新場景了
        //所以在寫進度條的時候需要注意一下。
        //爲了計算百分比 所以直接乘以100即可
        progress =  (int)(async.progress *100);

        //有了讀取進度的數值,大家可以自行製作進度條啦。
        Debug.Log("xuanyusong" +progress);

    }
    //這是一個簡單繪製2D動畫的方法,沒什麼好說的。
    void   DrawAnimation(Texture2D[] tex)
    {

        time += Time.deltaTime;

         if(time >= 1.0 / fps){

             nowFram++;

             time = 0;

             if(nowFram >= tex.Length)
             {
                nowFram = 0;
             }
        }
        GUI.DrawTexture(new Rect( 100,100,40,60) ,tex[nowFram] );

        //在這裏顯示讀取的進度。
        GUI.Label(new Rect( 100,180,300,60), "lOADING!!!!!" + progress);

    }

}


OK 下面我們繼續學習在遊戲場景中加載對象,文章的開始MOMO已經告訴大家,遊戲場景中Hierarchy視圖中的所有的對象在切換場景的時候都會加載。其實有一種方法可以讓某些遊戲對象不會被加載,如下圖所示,首先在Hierarchy視圖中選擇一個遊戲對象,在右側監測面板視圖中我們可以看到一個 “小對勾”默認情況下是勾選狀態,說明該遊戲對象處於激活狀態,如果點掉的話該對象將被隱藏。這個小功能在開發中其實用處非常大,請大家務必記住哈。





此時此刻大家相像一個遊戲場景,默認進入的時候是沒有任何遊戲對象的,然後運行遊戲時開啓一個異步任務將它們一個一個的加載顯示出來,這種方式適合異步的加載一個比較大的遊戲場景。
Test.cs 把它掛在攝像機對象中。


  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class Test : MonoBehaviour {  
  5.   
  6.     //這裏是需要加載激活的遊戲對象  
  7.     public GameObject  [] Objects;  
  8.   
  9.     //當前加載的進度  
  10.     int load_index =0;  
  11.     void Start ()  
  12.     {  
  13.         //開啓一個異步任務,加載模型。  
  14.         StartCoroutine(loadObject());  
  15.     }  
  16.   
  17.     IEnumerator loadObject()  
  18.     {  
  19.         //便利所有遊戲對象  
  20.         foreach(GameObject obj in Objects)  
  21.         {  
  22.             //激活遊戲對象  
  23.             obj.active = true;  
  24.             //記錄當前加載的對象  
  25.             load_index ++;  
  26.   
  27.             //這裏可以理解爲通知主線程刷新UI  
  28.             yield return 0;  
  29.         }  
  30.         //全部便利完畢返回  
  31.         yield return 0;  
  32.     }  
  33.   
  34.     void OnGUI ()  
  35.     {  
  36.         //顯示加載的進度  
  37.         GUILayout.Box(”當前加載的對象ID是: ” + load_index);  
  38.     }  
  39. }  
using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

    //這裏是需要加載激活的遊戲對象
    public GameObject  [] Objects;

    //當前加載的進度
    int load_index =0;
    void Start ()
    {
        //開啓一個異步任務,加載模型。
        StartCoroutine(loadObject());
    }

    IEnumerator loadObject()
    {
        //便利所有遊戲對象
        foreach(GameObject obj in Objects)
        {
            //激活遊戲對象
            obj.active = true;
            //記錄當前加載的對象
            load_index ++;

            //這裏可以理解爲通知主線程刷新UI
            yield return 0;
        }
        //全部便利完畢返回
        yield return 0;
    }

    void OnGUI ()
    {
        //顯示加載的進度
        GUILayout.Box("當前加載的對象ID是: " + load_index);
    }
}

如下圖所示,我們把需要加載的遊戲對象以數組的形式放在Objects數組中,因爲這些對象屬於未激活狀態,所以不能通過Find 等方法在腳步那種中找到他們。講到這裏我們在說說 編輯器賦值與代碼中賦值的區別,編輯器中賦值所消耗的時間都會記在loadlevel ()讀取場景中。而代碼中使用Resource.load()這類方法所消耗的時間會記在腳本中。開發中還得自行的把握一下把loading加在那裏。




當然我們還可以使用Instantiate(prefab);方法來動態的創建遊戲對象。
Main.cs 把它掛在攝像機中。


  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class Main : MonoBehaviour  
  5.  {  
  6.   
  7.     public int count;  
  8.     //在編輯器中預設一個遊戲對象  
  9.     public  GameObject prefab;  
  10.   
  11.     void Start ()  
  12.     {  
  13.         StartCoroutine(loaditem());  
  14.     }  
  15.   
  16.     void OnGUI()  
  17.     {  
  18.         GUILayout.Box(”遊戲對象已經加載到 : ” + count);  
  19.     }  
  20.   
  21.     IEnumerator loaditem()  
  22.     {  
  23.         //開始加載遊戲對象  
  24.         for(int i =0; i< 1000; i++)  
  25.         {  
  26.   
  27.             Instantiate(prefab);  
  28.             count = i;  
  29.             //可以理解爲刷新UI,顯示新加載的遊戲對象  
  30.             yield return 0;  
  31.         }  
  32.         //結束  
  33.         yield return 0;  
  34.     }  
  35. }  
using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour
 {

    public int count;
    //在編輯器中預設一個遊戲對象
    public  GameObject prefab;

    void Start ()
    {
        StartCoroutine(loaditem());
    }

    void OnGUI()
    {
        GUILayout.Box("遊戲對象已經加載到 : " + count);
    }

    IEnumerator loaditem()
    {
        //開始加載遊戲對象
        for(int i =0; i< 1000; i++)
        {

            Instantiate(prefab);
            count = i;
            //可以理解爲刷新UI,顯示新加載的遊戲對象
            yield return 0;
        }
        //結束
        yield return 0;
    }
}

運行遊戲後該遊戲對象會循環1000遍逐個創建,不影響主線程。那麼今天我們其實學習最多的就是StartCoroutine(),其實就是開啓一個異步線程,這裏可能有朋友會問Thread可以代替它嗎? 答案是不行, 比如查詢數據庫的時候如果用Thread的話Unity就會報錯說不能在線程中查詢,但是在StartCoroutine()中就可以完成,所以開發中大家可以嘗試着使用它,我們還可以使用StopCoroutine(“name”)來關閉一個正在執行的異步線程。不早了晚安,MOMO祝大家學習愉快。



ok, 這個介紹是MOMO寫的,我的第一本Unity書也是買的MOMO的。










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