首页 > 代码库 > Unity 协程与调用函数的区别以及示例应用

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

 

 

                wKioL1QFCA2iJSWEAACxjzqDg5s443.jpg

       

                                     图 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

 

 

wKiom1QFCCGxR53xAAFy7Na8GZs892.jpg

                       图 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://liuguozhu.blog.51cto.com/9142031/1547645

Unity 协程与调用函数的区别以及示例应用