日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《学Unity的猫》——第九章:状态机与Unity协程,好奇猫与铁皮怪水管

發(fā)布時(shí)間:2023/12/20 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《学Unity的猫》——第九章:状态机与Unity协程,好奇猫与铁皮怪水管 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

        • 9.1 會(huì)吐水的鐵皮怪
        • 9.2 狀態(tài)機(jī)是什么
        • 9.3 使用協(xié)程實(shí)現(xiàn)狀態(tài)機(jī)
        • 9.4 進(jìn)程與線程
          • 9.4.1 什么是進(jìn)程
          • 9.4.2 什么是線程
        • 9.5 Unity的協(xié)程
          • 9.5.1 Unity的協(xié)程是什么
          • 9.5.2 Unity生命周期對(duì)協(xié)程的影響
          • 9.5.3 協(xié)程的啟動(dòng)
          • 9.5.4 協(xié)程的退出
          • 9.5.5 協(xié)程的主要應(yīng)用

簡(jiǎn)介:我是一名Unity游戲開發(fā)工程師,皮皮是我養(yǎng)的貓,會(huì)講人話,它接到了喵星的特殊任務(wù):學(xué)習(xí)編程,學(xué)習(xí)Unity游戲開發(fā)。
于是,發(fā)生了一系列有趣的故事。

9.1 會(huì)吐水的鐵皮怪

我把衣服丟進(jìn)洗衣機(jī)里,倒入洗衣粉,調(diào)節(jié)水量,按了速洗,啟動(dòng)。
皮皮豎著尾巴跟過來,我伸了個(gè)懶腰回到電腦前繼續(xù)寫文章。
不久,聽到水聲嘩嘩嘩地流,不祥的預(yù)感。我趕緊起身去看,水漫金山了。
“手欠貓!又把洗衣機(jī)的水管掏出來了!”
看了眼皮皮幼稚的圓臉,算了算了。
皮皮:“這個(gè)鐵皮怪為什么可以一次性吐那么多水出來?”
我一臉黑線:“這個(gè)叫洗衣機(jī),它的功能就是洗衣服,水是從上面進(jìn)水口進(jìn)來的。”
皮皮舔舔自己的腳毛,仿佛在質(zhì)疑洗衣機(jī)。

9.2 狀態(tài)機(jī)是什么

我拿出紙和筆,畫了洗衣機(jī)的狀態(tài)圖。

我:“你可以把洗衣機(jī)看成是一個(gè)有限狀態(tài)機(jī)。”
皮皮:“什么是有限狀態(tài)機(jī)?”
我:“有限狀態(tài)機(jī)是一種數(shù)學(xué)模型,英文全稱是Finite State Machine,縮寫FSM,簡(jiǎn)稱狀態(tài)機(jī),它是現(xiàn)實(shí)事物運(yùn)行規(guī)則抽象而成的一個(gè)數(shù)學(xué)模型。”
我繼續(xù)講:“看這里,洗衣機(jī)有幾個(gè)狀態(tài):開始、進(jìn)水、漂洗、排水、脫水、結(jié)束。這些狀態(tài)由一系列事件來驅(qū)動(dòng),比如按啟動(dòng)按鈕,開始進(jìn)水,水位達(dá)到目標(biāo)水位,進(jìn)入漂洗狀態(tài),正轉(zhuǎn)5秒,停2秒,反轉(zhuǎn)5秒,停2秒,循環(huán)執(zhí)行10次,然后進(jìn)入排水狀態(tài),達(dá)到最低水位,進(jìn)入脫水狀態(tài),脫水30秒,接著又回到進(jìn)水狀態(tài),重復(fù)上述流程3次,最終結(jié)束。”
皮皮:“哇,好復(fù)雜,它也是程序控制的嗎?”
我:“是的呀,我們可以用代碼寫一個(gè)簡(jiǎn)單的狀態(tài)機(jī)。”

9.3 使用協(xié)程實(shí)現(xiàn)狀態(tài)機(jī)

我打開Unity,創(chuàng)建了一個(gè)腳本CoroutineTest.cs。

CoroutineTest.cs代碼如下

using System.Collections; using UnityEngine;public class CoroutineTest : MonoBehaviour {/// <summary>/// 當(dāng)前狀態(tài)/// </summary>private int m_state;void Start(){// 設(shè)置初始狀態(tài)m_state = 0;// 使用協(xié)程啟動(dòng)狀態(tài)機(jī)StartCoroutine(TestFSM());}/// <summary>/// 使用協(xié)程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的狀態(tài)機(jī)/// </summary>/// <returns></returns>private IEnumerator TestFSM(){Debug.Log("初始狀態(tài):" + m_state);while (true){switch (m_state){case 0:{// 檢測(cè)空白鍵是否按下if (Input.GetKeyDown(KeyCode.Space)){Debug.Log("按下了空白鍵,狀態(tài)切換: 0->1");m_state = 1;}}break;case 1:{// 檢測(cè)空白鍵是否按下if (Input.GetKeyDown(KeyCode.Space)){Debug.Log("按下了空白鍵,狀態(tài)切換: 1->0");m_state = 0;}}break;}yield return null;}} }

將腳本掛到Main Camera上,點(diǎn)擊運(yùn)行。
輸出了

初始狀態(tài):0

如下

按一下空白鍵,輸出了

按下了空白鍵,狀態(tài)切換: 0->1

如下

再按一下空白鍵,輸出了

按下了空白鍵,狀態(tài)切換: 1->0

如下

皮皮:“上面的代碼有點(diǎn)看不懂,StartCoroutine、IEnumerator、yield return null是什么?”
我:“上面用到了Unity的協(xié)程。”
皮皮:“你之前都沒教我協(xié)程,直接一上來就寫我看不懂的代碼,不厚道。”
我:“程序員是一個(gè)不斷學(xué)習(xí)和成長(zhǎng)的職業(yè),實(shí)際項(xiàng)目中遇到一些沒學(xué)過的東西很正常,特別是現(xiàn)在這個(gè)知識(shí)爆炸的時(shí)代。不懂就查,自學(xué)能力是程序員最重要的能力之一,不要總是依賴別人教你。”
我心想會(huì)不會(huì)有點(diǎn)過分,皮皮只是拔了洗衣機(jī)的水管。
沒想到皮皮很認(rèn)真地點(diǎn)了點(diǎn)頭,然后望著我呆呆地問:“怎么查?”
我的錯(cuò),我之前沒教過皮皮如何使用搜索引擎。
我打開CSDN,說:“以后你有問題可以在CSDN搜索,我給你注冊(cè)個(gè)賬號(hào),實(shí)在不懂,你就訪問這個(gè)人的博客 https://blog.csdn.net/linxinfa,給他留言或者私信,他看到了會(huì)耐心回答你的問題的。”
剛好,這個(gè)時(shí)候衣服洗好了,我去把衣服拿出來晾好。
我回到屋內(nèi)時(shí),皮皮轉(zhuǎn)過頭說:“查了很多文章,還是沒明白協(xié)程的準(zhǔn)確定義。”
我:“看在你這么認(rèn)真的態(tài)度,我來講給你聽吧。要搞明白協(xié)程,需要先理解進(jìn)程與線程。”

9.4 進(jìn)程與線程

9.4.1 什么是進(jìn)程

進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序在一個(gè)數(shù)據(jù)集上的一次動(dòng)態(tài)執(zhí)行的過程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,是應(yīng)用程序運(yùn)行的載體。
簡(jiǎn)單來說,進(jìn)程就是應(yīng)用程序的啟動(dòng)實(shí)例,比如我們打開Unity編輯器,其實(shí)就是啟動(dòng)了一個(gè)Unity編輯器進(jìn)程。我們可以在任務(wù)管理器中看到操作系統(tǒng)中運(yùn)行的進(jìn)程。推薦使用ProcessExplorer來查看進(jìn)程。
ProcessExplorer下載地址:https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer
如下,在ProcessExplorer中看到了Unity.exe進(jìn)程,一個(gè)進(jìn)程可以啟動(dòng)另一個(gè)進(jìn)程,比如Unity.exe進(jìn)程又啟動(dòng)了UnityCrashHandle64.exe這個(gè)進(jìn)程來監(jiān)聽Unity.exe的崩潰。

9.4.2 什么是線程

線程是程序執(zhí)行中一個(gè)單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調(diào)度和分派的基本單位。
一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程,各個(gè)線程之間共享程序的內(nèi)存空間,也就是所在進(jìn)程的內(nèi)存空間。
同樣使用ProcessExplorer,可以查看某個(gè)進(jìn)程中的線程。
右鍵Unity.exe進(jìn)程,點(diǎn)擊菜單Properties。

點(diǎn)擊Threads標(biāo)簽頁,可以看到它創(chuàng)建的線程,可以看到Unity.exe進(jìn)程創(chuàng)建了97個(gè)線程。

9.5 Unity的協(xié)程

9.5.1 Unity的協(xié)程是什么

簡(jiǎn)單來說,協(xié)程是一個(gè)有多個(gè)返回點(diǎn)的函數(shù)。

協(xié)程不是多線程,協(xié)程還是在主線程里面。進(jìn)程和線程由操作系統(tǒng)調(diào)度,協(xié)程由程序員在協(xié)程的代碼里面顯示調(diào)度。

在Unity運(yùn)行時(shí),調(diào)用協(xié)程就是開啟了一個(gè)IEnumerator(迭代器),協(xié)程開始執(zhí)行,在執(zhí)行到y(tǒng)ield return之前和其他的正常的程序沒有差別,但是當(dāng)遇到y(tǒng)ield return之后會(huì)立刻返回,并將該函數(shù)暫時(shí)掛起。在下一幀遇到FixedUpdate或者Update之后判斷yield return后邊的條件是否滿足,如果滿足則向下執(zhí)行。

9.5.2 Unity生命周期對(duì)協(xié)程的影響

我拿出紙和筆,畫了MonoBehvaviour生命周期的一部分。

皮皮:“我記得FixedUpdate、Update和LateUpdate這三個(gè)函數(shù),上次你講MonoBehvaviour生命周期的時(shí)候有講到。”
我:“記性不錯(cuò),本質(zhì)上,Unity的協(xié)程是一個(gè)迭代器,遇到y(tǒng)ield return的時(shí)候就掛起來,然后在MonoBehvaviour的生命周期中判斷條件是否滿足,滿足地話則迭代器執(zhí)行下一步。”

9.5.3 協(xié)程的啟動(dòng)

使用StartCoroutine啟動(dòng)協(xié)程,例:

IEnumerator TestCoroutine() {yield return null; }

啟動(dòng)協(xié)程

// 得到迭代器 IEnumerator itor = TestCoroutine(); // 啟動(dòng)協(xié)程 StartCoroutine(itor);// 也可以直接這樣寫 // StartCoroutine(TestCoroutine());

皮皮:“這個(gè)IEnumerator是什么?”
我:“IEnumerator是一個(gè)迭代器接口,它有一個(gè)重要的方法MoveNext。”

public interface IEnumerator {object Current { get; }bool MoveNext();void Reset(); }

Unity的協(xié)程遇到y(tǒng)ield return的時(shí)候就掛起來,迭代器游標(biāo)記錄了當(dāng)前運(yùn)行的位置,即Current,調(diào)用MoveNext()的時(shí)候,迭代器游標(biāo)就下移一步,協(xié)程就從上一次的位置繼續(xù)運(yùn)行。
皮皮:“沒有看到哪里去調(diào)用了這個(gè)MoveNext()呀。”
我:“Unity底層幫我們調(diào)用的,就像MonoBehvaviour的Update函數(shù)一樣。”
皮皮:“那如果我把MonoBehvaviour腳本禁用,協(xié)程還會(huì)繼續(xù)執(zhí)行嗎?”
我:“協(xié)程的運(yùn)行是和MonoBehvaviour平行的,執(zhí)行了StartCoroutine之后,禁用MonoBehvaviour腳本,不會(huì)影響協(xié)程的運(yùn)行,不過如果禁用了gameObject,則協(xié)程會(huì)立即退出,即使重新激活gameObject,協(xié)程也不會(huì)繼續(xù)運(yùn)行。”

9.5.4 協(xié)程的退出

做個(gè)簡(jiǎn)單的測(cè)試,CoroutineTest.cs腳本代碼如下:

using System.Collections; using UnityEngine;public class CoroutineTest : MonoBehaviour {void Start(){// 啟動(dòng)協(xié)程StartCoroutine(TestCoroutine());}IEnumerator TestCoroutine(){while(true){Debug.Log("Coroutine is running");yield return null;}} }

將CoroutineTest.cs腳本掛到一個(gè)空物體上

可以看到Console窗口輸出了日志,輸出了Coroutine is running。

我們可以從調(diào)用堆棧中看到,第一條日志是我們通過StartCoroutine啟動(dòng)協(xié)程,內(nèi)部其實(shí)是執(zhí)行了一次迭代器的MoveNext方法。
而后面的日志,是通過UnityEngine.SetupCoroutine對(duì)象調(diào)用InvokeMoveNext方法,再執(zhí)行了迭代器的MoveNext方法。

此時(shí),我們把CoroutineTest腳本禁用,并不會(huì)影響協(xié)程的運(yùn)行,日志會(huì)繼續(xù)輸出。

但如果把gameObject禁用,則協(xié)程立即停止了,即使重新激活gameObject,協(xié)程也不會(huì)繼續(xù)運(yùn)行了。

皮皮:“上面是我們通過禁用gameObject讓協(xié)程退出,如果使用代碼的方式,如何強(qiáng)制退出協(xié)程呢?”
我:“有兩種方式。”
方式一,啟動(dòng)協(xié)程是,把迭代器對(duì)象緩存起來,

// 啟動(dòng)協(xié)程 var itor = TestCoroutine(); StartCoroutine(itor);

然后我們就可以使用StopCoroutine方法來強(qiáng)制退出協(xié)程了。

// 退出協(xié)程 StopCoroutine(itor);

方式二,是在協(xié)程內(nèi)部執(zhí)行yeild break。

IEnumerator TestCoroutine() {while(true){Debug.Log("Coroutine is running");// yield break會(huì)直接退出協(xié)程yield break;}Debug.Log("這里永遠(yuǎn)不會(huì)被執(zhí)行到"); }
9.5.5 協(xié)程的主要應(yīng)用

我:“協(xié)程的方便之處就是可以使用看似同步的寫法來寫異步的邏輯,這樣可以避免大量的委托回調(diào)函數(shù)。”
皮皮:“什么是回調(diào)函數(shù)?”
我:“舉個(gè)例子,剛剛洗衣機(jī)的狀態(tài)圖還記得嗎,進(jìn)水是一個(gè)過程,需要等,站在程序的角度說,它是一個(gè)耗時(shí)的操作,當(dāng)達(dá)到設(shè)定水位的時(shí)候,才進(jìn)入漂洗狀態(tài)。如果不用協(xié)程,我們可能就需要申明一個(gè)委托函數(shù),把進(jìn)入漂洗狀態(tài)的函數(shù)設(shè)置給這個(gè)委托,當(dāng)達(dá)到設(shè)定水位的時(shí)候,調(diào)用這個(gè)委托函數(shù),即可進(jìn)入漂洗狀態(tài),這個(gè)委托函數(shù)就是回調(diào)函數(shù)。”
類似下面這樣

using UnityEngine;// 洗衣機(jī) public class Washer : MonoBehaviour {public enum WASHER_STATE{/// <summary>/// 準(zhǔn)備/// </summary>INIT,/// <summary>/// 加水/// </summary>ADD_WATER,/// <summary>/// 漂洗/// </summary>POTCH}/// <summary>/// 狀態(tài)/// </summary>private WASHER_STATE m_state;/// <summary>/// 飄洗的委托/// </summary>System.Action m_potchDelegate;/// <summary>/// 水位/// </summary>int m_waterLevel;private void Start(){StartWasher();}void Update(){switch (m_state){case WASHER_STATE.ADD_WATER:{m_waterLevel += 1;// 判斷是否達(dá)到水位if (m_waterLevel >= 60){// 調(diào)用漂洗委托if(null != m_potchDelegate){m_potchDelegate();}}}break;case WASHER_STATE.POTCH:{// TODObreak;}}}// 啟動(dòng)洗衣機(jī)void StartWasher(){// 把漂洗函數(shù)賦值給委托m_potchDelegate = Potch;m_state = WASHER_STATE.INIT;// 加水AddWater();}// 進(jìn)水void AddWater(){// 進(jìn)入進(jìn)水狀態(tài)m_state = WASHER_STATE.ADD_WATER;}// 漂洗void Potch(){// 進(jìn)入漂洗狀態(tài)m_state = WASHER_STATE.POTCH;} }

如果使用協(xié)程,則代碼可以簡(jiǎn)潔。

using System.Collections; using UnityEngine;// 洗衣機(jī) public class Washer : MonoBehaviour {/// <summary>/// 水位/// </summary>int m_waterLevel;private void Start(){StartCoroutine(StartWasher());}// 啟動(dòng)洗衣機(jī)IEnumerator StartWasher(){// 加水while (true){m_waterLevel += 1;if(m_waterLevel >= 60){break;}yield return null;}// TODO 漂洗} }

皮皮:“太酷了,看出來狀態(tài)機(jī)很適合使用協(xié)程來實(shí)現(xiàn)。”
我:“是的呀,現(xiàn)在看明白了吧。”
皮皮:“那個(gè)yield return null是不是可以看做是等一幀的意思?”
我:“是的,執(zhí)行yield return null,協(xié)程就掛起了,在下一幀Update之后會(huì)執(zhí)行yield null,就會(huì)執(zhí)行協(xié)程迭代器的MoveNext,從而繼續(xù)執(zhí)行協(xié)程。”
皮皮:“生命周期中有個(gè)yield WaitForSeconds,這個(gè)WaitForSeconds是等n秒的意思嗎?”
我:“是的,我可以使用它實(shí)現(xiàn)一個(gè)簡(jiǎn)單的延時(shí)調(diào)用。”
示例:

IEnumerator DelayCallTest() {Debug.Log("測(cè)試 WaitForSeconds");yield return new WaitForSeconds(3);Debug.Log("這里會(huì)在3秒后被執(zhí)行"); }

皮皮:“可以了,我現(xiàn)在需要停下去休息一下,yield return new WaitForSeconds(9999);”
我:“我也要去休息一下了,yield break。”


《學(xué)Unity的貓》——第十章:Unity的物理碰撞,流浪喵星計(jì)劃

總結(jié)

以上是生活随笔為你收集整理的《学Unity的猫》——第九章:状态机与Unity协程,好奇猫与铁皮怪水管的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。