Unity 協程與呼叫函式的區別以及示例應用
Coroutine:協同程式(簡稱:協程),參考網路給出以下兩個定義供參考。
1>協同程式,即在主程式執行時同時開啟另一段邏輯處理,來協同當前程式的執行。換句話說,開啟協同程式就是開啟一個模擬執行緒。 [作者注: 不是真正的執行緒]
2> 是一種很古老的程式設計模型,以前的作業系統裡程序排程裡用到過,現在作業系統的程序排程都是根據時間片和優先順序來進行輪換,以前是要程式自己來釋放cpu的控制權,一直不釋放一直也就佔用著cpu,這種要求程式自己來進行排程的程式設計模型應該就叫“協程”。
語法:
function StartCoroutine (routine:IEnumerator) : Coroutine
function StartCoroutine (methodName:string, value : object = null) :Coroutine
function StopCoroutine (methodName:string) : void
function StopAllCoroutines () : void
示例:
void Start()
{
print("1: " + Time.time);
StartCoroutine(WaitAndPrint(2.0F));
print("3: " + Time.time);
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return newWaitForSeconds(waitTime);
print("2: " +Time.time.ToString());
}
輸出結果:
1: 0
3: 0
2: 2.004061
Invoke:呼叫函式,指每隔多少時間執行一次某方法。
InvokeRepeating: 重複呼叫函式,指在給定時間後指定間隔時間後重復執行方法。
語法:
function Invoke (methodName: string, time :float) : void
functionInvokeRepeating (methodName :String,time : float, repeatRate : float) : void
function CancelInvoke () : void
function IsInvoking (methodName: string) :bool
示例:
private int _IntNumber = 0; //累加數字
void Start()
{
//this.Invoke("DisplayNum",1F); //只調用一次
this.InvokeRepeating("DisplayNum", 0F, 1F); //間隔1秒反覆呼叫
}
//每隔一定時間,進行自增想加數字且輸出結果
void DisplayNum()
{
Debug.Log("自增數字: " + (++_IntNumber));
}
輸出結果:
自增數字:1
自增數字:2
自增數字:3
自增數字:4
自增數字:5
自增數字:6
(結果會無限迴圈下去...)
以上技術在Unity 實際程式設計過程中被大量應用,個人體會Invoke 與 InvokeRepeating 為 Coroutine 的簡化寫法,是按照事先定義好的固定時間間隔進行呼叫的程式設計模型。優點是結構與語法編寫簡單。Coroutine 則是更加完善,功能更加強大的程式設計模型。在處理需要製作多種間隔時間,從而達到一定功能的地方有良好的應用效果。以下是開發與教學過程中實際的應用場景舉例:
開發遊戲倒計時案例。
(示例 1.1)
using UnityEngine;
using System.Collections;
public class GUIProgress_LevelOne : MonoBehaviour {
publicGUISkin projectWukongSkin;//專案面板
publicTexture2D TexGameOver_Falure;//遊戲結束貼圖。
publicTexture2D TexNumberOne;//數字貼圖1
publicTexture2D TexNumberTwo;
publicTexture2D TexNumberThree;
//專案資訊貼圖
publicTexture2D TexProjectLogo;//專案Logo
publicTexture2D TexRedDiamond;//紅寶石
privatebool _BoolIsDrawCountDownFlag = false;//繪製倒計時數字
private int_IntDrawNumber = 3;//繪製具體數字
void Start()
{
//啟動遊戲倒計時
StartCoroutine(DrawGameStartCountDown());
}//Start_end
//遊戲開始倒計時
IEnumeratorDrawGameStartCountDown()
{
//1秒的準備
yieldreturn new WaitForSeconds(1F);
//顯示數字3
_BoolIsDrawCountDownFlag = true;
_IntDrawNumber = 3;
yieldreturn new WaitForSeconds(1F); //停留1秒的檢視時間
//空白時間
_BoolIsDrawCountDownFlag = false;
yieldreturn new WaitForSeconds(0.5F);
//顯示數字2
_BoolIsDrawCountDownFlag = true;
_IntDrawNumber = 2;
yieldreturn new WaitForSeconds(1F);
//空白時間
_BoolIsDrawCountDownFlag = false;
yieldreturn new WaitForSeconds(0.5F);
//顯示數字1
_BoolIsDrawCountDownFlag = true;
_IntDrawNumber = 1;
yieldreturn new WaitForSeconds(1F);
_BoolIsDrawCountDownFlag = false;
//開始遊戲。
GlobalInfoManger.CurrentGameState = GameState.Playing;
}
void OnGUI()
{
GUI.skin = projectWukongSkin;
/* 顯示專案資訊 */
//專案Logo
GUI.DrawTexture(new Rect(Screen.width / 2 - TexProjectLogo.width/2, 0,TexProjectLogo.width, TexProjectLogo.height), TexProjectLogo);
//紅寶石
GUI.DrawTexture(new Rect(0, 0,50,50), TexRedDiamond);
//紅寶石當前的數量
GUI.Label(newRect(50,0,100,100),GlobalInfoManger.IntRedDiamonds.ToString());
//顯示遊戲時間
GUI.Label(new Rect(170, 0,150,80),"Time:");
//當前遊戲的時間
GUI.Label(new Rect(300, 0, 100, 100),GlobalInfoManger.IntGameTime.ToString());
//繪製遊戲不成功貼圖畫面
if(GlobalInfoManger.CurrentGameState==GameState.GameOver)
{
GUI.DrawTexture(new Rect(Screen.width / 2 - TexGameOver_Falure.width /2, Screen.height / 2 - TexGameOver_Falure.height / 2, TexGameOver_Falure.width,TexGameOver_Falure.height), TexGameOver_Falure);
}
#region繪製倒計時數字貼圖
//繪製倒計時數字貼圖
if(_BoolIsDrawCountDownFlag)
{
if(_IntDrawNumber == 3)
{
GUI.DrawTexture(new Rect(Screen.width / 2 - TexNumberThree.width / 2,Screen.height / 2 - TexNumberThree.height / 2, TexNumberThree.width,TexNumberThree.height), TexNumberThree);
}
else if (_IntDrawNumber == 2)
{
GUI.DrawTexture(new Rect(Screen.width / 2 - TexNumberTwo.width / 2,Screen.height / 2 - TexNumberTwo.height / 2, TexNumberTwo.width,TexNumberTwo.height), TexNumberTwo);
}
else if (_IntDrawNumber == 1)
{
GUI.DrawTexture(new Rect(Screen.width / 2 - TexNumberOne.width / 2,Screen.height / 2 - TexNumberOne.height / 2, TexNumberOne.width,TexNumberOne.height), TexNumberOne);
}
}
#endregion
}//OnGUI_end
}//Class_end
圖 1.1
示例 1.1 參考圖1.1進行理解,基本原理如下:
以上定義的協程方法 “DrawGameStartCountDown()” 主要是運用 “_BoolIsDrawCountDownFlag”與“_IntDrawNumber ” 這兩個欄位對 OnGUI() 事件函式中“GUI.DrawTexture ” 繪製貼圖的時間間隔與繪製內容進行控制。
再舉一例:
開發遊戲戰績數值統計案例。
(示例 1.2)
usingUnityEngine;
usingSystem.Collections;
public classGUI_CountResult : MonoBehaviour {
public GUISkin ProjectGUISkin; //專案面板
public Texture2D TexGameBackground; //遊戲背景貼圖
public Texture2D TexRedDiamonds; //紅寶石貼圖
public Texture2D TexBlueDiamonds; //藍寶石貼圖
public string StrScenesName; //場景名稱。
public Texture2D TexRePlay; //"重玩"貼圖
private bool _boolRePlay = false; //是否重玩
private bool _boolExit = false; //是否退出
//開發動態文字顯示
private int _IntRunedTime = 0; //玩家跑過的時間。
private bool _BoolIsDisplayRunedTime =false; //是否顯示跑過的里程
private int _IntRedDiamondNum = 0; //玩家跑過的時間。
private bool _BoolIsDisplayRedDiamond =false; //是否顯示跑過的里程
void Start ()
{
//測試數值(等待刪除。)
//GlobalInfoManger.IntGameTime = 66;
//GlobalInfoManger.IntRedDiamonds = 88;
//開啟“動態控制顯示玩家跑過的里程(時間)”
StartCoroutine(DynamicDisplayPlayerRunLength());
//開啟“動態控制紅寶石數量統計”
StartCoroutine(DynamicDisplayRedDiamondNumber());
}//Start_end
//動態控制里程顯示
IEnumerator DynamicDisplayPlayerRunLength()
{
//等待GUI 與攝像機全部繪製完畢。
yield return new WaitForEndOfFrame();
_BoolIsDisplayRunedTime = true;
_IntRunedTime = 0;
yield return new WaitForSeconds(0.2F);
_BoolIsDisplayRunedTime = false;
while (_IntRunedTime <GlobalInfoManger.IntGameTime)
{
//不斷顯示
++_IntRunedTime;
_BoolIsDisplayRunedTime = true;
yield return newWaitForSeconds(0.03F);
_BoolIsDisplayRunedTime = false;
yield return new WaitForSeconds(0.01F);
}
_BoolIsDisplayRunedTime = true;
}
//動態控制紅寶石顯示
IEnumeratorDynamicDisplayRedDiamondNumber()
{
//等待GUI 與攝像機全部繪製完畢。
yield return new WaitForEndOfFrame();
_BoolIsDisplayRedDiamond = true;
_IntRedDiamondNum = 0;
yield return new WaitForSeconds(0.2F);
_BoolIsDisplayRedDiamond = false;
while (_IntRedDiamondNum <GlobalInfoManger.IntRedDiamonds)
{
//不斷顯示
++_IntRedDiamondNum;
_BoolIsDisplayRedDiamond = true;
yield return newWaitForSeconds(0.03F);
_BoolIsDisplayRedDiamond = false;
yield return newWaitForSeconds(0.01F);
}
_BoolIsDisplayRedDiamond = true;
}
void OnGUI()
{
GUI.skin=ProjectGUISkin;
//繪製遊戲背景貼圖
GUI.DrawTexture(newRect(Screen.width/2-TexGameBackground.width/2,Screen.height/2-TexGameBackground.height/2,TexGameBackground.width,TexGameBackground.height),TexGameBackground);
//顯示統計玩家跑的里程
GUI.Label(new Rect(550, 250, 150, 200),"時間:");
if (_BoolIsDisplayRunedTime)
{
GUI.Label(new Rect(700, 250, 150,200), _IntRunedTime.ToString());
}
//顯示獲得的紅寶石的數量
GUI.DrawTexture(new Rect(550, 300, 50,50), TexRedDiamonds);
if(_BoolIsDisplayRedDiamond)
{
GUI.Label(new Rect(700, 300, 150,50),_IntRedDiamondNum.ToString());
}
//顯示獲得的藍寶石的數量(測試資料)
GUI.DrawTexture(new Rect(550, 350, 50,50), TexBlueDiamonds);
GUI.Label(new Rect(700, 350, 50, 50),GlobalInfoManger.IntRedDiamonds.ToString());
//顯示“重玩”,"退出"。
if (GUI.Button(new Rect((Screen.width /2 - TexRePlay.width / 2)-100, Screen.height / 2 - TexRePlay.height / 2+150,TexRePlay.width, TexRePlay.height), "",ProjectGUISkin.GetStyle("Btn_Replay")))
{
_boolRePlay = true;
}
else if (GUI.Button(newRect((Screen.width / 2 - TexRePlay.width / 2)+100, Screen.height / 2 - TexRePlay.height/ 2+150, TexRePlay.width, TexRePlay.height), "",ProjectGUISkin.GetStyle("Btn_Exit")))
{
_boolExit = true;
}
}//OnGUI_end
//邏輯處理。
void Update()
{
if(_boolRePlay)
{
//呼叫指定的場景。
GlobalInfoManger.IntGameTime =0; //時間清零
Application.LoadLevel(StrScenesName);
}
else if (_boolExit)
{
//退出遊戲
Application.Quit();
}
}
}//Class_end
圖 1.2
示例 1.2 參考圖1.2進行理解,其基本原理與示例1.1基本相同,在這就不再贅述。
對於InvokeRepeating則在專案中運用的更多,請看如下示例程式碼。
(示例 1.3)
usingUnityEngine;
usingSystem.Collections;
public classScenesManager_Level1 : MonoBehaviour {
public Transform TrnHero; //主人公位置資訊
public string StrStartScenceName; //開始場景的名稱
//克隆道具
public GameObject Go_ScriptsObject; //指令碼物件
public GameObjectGoCloneOriginal_Diamend; //克隆原型_紅寶石
public GameObjectGoCloneOriginal_Tree; //克隆原型_樹木
public GameObjectGo_CloneObj_RefPosion_Left; //克隆位置基準。
public GameObjectGo_CloneObj_RefPosion_Right;
private AudioSource _ASBGMusic; //背景音樂
private AudioSource[] _ASAudioEffect; //音訊陣列
private Vector3_VecHeroPositionOfOriginal; //主人公原始位置
void Start ()
{
//遊戲結束
GlobalInfoManger.CurrentGameState =GameState.Prepare;
//設定音訊
SetAudio();
//主人公位置初始值
_VecHeroPositionOfOriginal = TrnHero.transform.position;
//檢測狀態資訊
InvokeRepeating("CheckHeroState",1F,1F);
//動態生成道具。(單次載入方式)
//DanymicCreateRedDiamond();
//分步載入方式
InvokeRepeating("DanymicCreateProperty",1F,1F);
}//Start_end
/// <summary>
/// 動態載入各種道具
/// </summary>
private void DanymicCreateProperty()
{
//載入紅寶石
if(GlobalInfoManger.IntGameTime==1)
{
this.CreateProperty(GoCloneOriginal_Diamend,15,30,100);
}
//第二次
if (GlobalInfoManger.IntGameTime ==15)
{
//載入寶石
this.CreateProperty(GoCloneOriginal_Diamend, 20, 30, 150);
//載入樹木
this.CreateProperty(GoCloneOriginal_Tree,5, 30, 100);
}
//第三次。
if (GlobalInfoManger.IntGameTime ==20)
{
//載入地刺
//載入寶石
this.CreateProperty(GoCloneOriginal_Diamend, 50, 30,650);
//載入樹木
this.CreateProperty(GoCloneOriginal_Tree,10,30, 600);
}
}//DanymicCreateProperty_end
/// <summary>
/// 建立道具
/// </summary>
/// <paramname="goCloneOriginal">克隆原型</param>
/// <paramname="intCloneNumber">克隆數量</param>
/// <paramname="intDestroyGOTime">銷燬時間</param>
/// <paramname="intAddingGameobjectLenth">克隆物件的距離</param>
void CreateProperty(GameObjectgoCloneOriginal,int intCloneNumber,int intDestroyGOTime,intintAddingGameobjectLenth)
{
/* 資料集合準備 */
System.Object[] ObjArray = newSystem.Object[8];
ObjArray[0] = goCloneOriginal; //克隆的原型
ObjArray[1] = intCloneNumber; //克隆的數量
ObjArray[2] = intDestroyGOTime; //克隆體銷燬的時間
//X 座標最小數值
ObjArray[3] =Go_CloneObj_RefPosion_Left.transform.position.x;
//X 座標最大數值
ObjArray[4] =Go_CloneObj_RefPosion_Right.transform.position.x;
//Y座標
ObjArray[5] =Go_CloneObj_RefPosion_Left.transform.position.y;
//Z 座標最小數值
ObjArray[6] =Go_CloneObj_RefPosion_Left.transform.position.z;
//Z 座標最大數值
ObjArray[7] =Go_CloneObj_RefPosion_Left.transform.position.z + intAddingGameobjectLenth;
/* 呼叫動態克隆方法 */
Go_ScriptsObject.SendMessage("DynamicCreProperty", ObjArray,SendMessageOptions.DontRequireReceiver);
}
/// <summary>
/// 檢測主人公狀態資訊
/// </summary>
void CheckHeroState()
{
//遊戲時間自增
++GlobalInfoManger.IntGameTime;
//如果主人公Y軸出現較大的變化,判斷主人公掉下橋體,遊戲結束
if (TrnHero.transform.position.y <(_VecHeroPositionOfOriginal.y-10))
{
GlobalInfoManger.CurrentGameState= GameState.GameOver;
}
//判斷遊戲結束後,處理方式。
if (GlobalInfoManger.CurrentGameState== GameState.GameOver)
{
_ASBGMusic.Stop(); //關閉背景音樂
_ASAudioEffect[1].Play(); //播放GameOver 音訊
//3秒後返回開始場景
Invoke("ReturnStartScenes", 3F);
}
}
//設定音訊
void SetAudio()
{
//背景音樂音量大小判斷
_ASBGMusic =GameObject.Find("_AudiosManager/_AudioBGMusic").GetComponent<AudioSource>();
switch (GlobalInfoManger.ProVolumns)
{
case ProjectVlumns.None:
break;
case ProjectVlumns.NoneVolumns:
_ASBGMusic.volume = 0F;
break;
case ProjectVlumns.HalfVolumns:
_ASBGMusic.volume = 0.5F;
break;
case ProjectVlumns.FullVolumns:
_ASBGMusic.volume = 1F;
break;
default:
break;
}//switch_end
//音效處理
_ASAudioEffect =GameObject.Find("_AudiosManager/_AudioEffect").GetComponents<AudioSource>();
_ASAudioEffect[1].volume = 1F;
_ASAudioEffect[1].loop = false;
}
//返回開始開始場景
void ReturnStartScenes()
{
Application.LoadLevel(StrStartScenceName);
}
}//Class_end
示例 1.3 可以參考圖1.1進行理解,基本原理如下:
以上在Start() 事件函式中定義的“CheckHeroState()”[檢測主角的狀態資訊]與“DanymicCreateRedDiamond()”[動態生成道具] 方法分別都是被InvokeRepeate 進行迴圈呼叫,按照一定的固定時間間隔。 這裡在本應用場景中的以上兩個方法中由於不需要產生不固定的時間間隔,所以就不需要使用協程了。
最後提及一下Coroutine(協程)與InvokeRepeating(重複呼叫函式),在被指令碼禁用的時候是不會自動停止呼叫的,需要手工編寫對應的停止呼叫函式進行停止,否則會出現很多問題。且還要注意呼叫與停止呼叫的時機問題,我會在下一篇部落格中關於討論Unity 指令碼的生命週期中進行討論,謝謝。
(跑酷遊戲)原始碼下載連結:
http://pan.baidu.com/s/1sj8X3lV
轉載於:https://blog.51cto.com/liuguozhu/1547645