其實(shí)游戲引擎有一個基礎(chǔ)簡單理解,那就是不斷的進(jìn)行一個循環(huán)悼瓮,在這個周期循環(huán)之中會每一幀的進(jìn)行一些表現(xiàn)渲染上的處理戈毒,處理的結(jié)果就是我們看到的游戲畫面,當(dāng)然也會有各種各樣的邏輯上的處理横堡。
Unity是單線程的游戲引擎埋市,為什么要做這個限制?因?yàn)橛螒蛑羞壿嫺潞彤嬅娓碌臅r間點(diǎn)要求有確定性命贴,必須按照幀序列嚴(yán)格保持同步道宅,否則就會出現(xiàn)游戲中的對象不同步的現(xiàn)象。多線程也能保證這個效果胸蛛,但如果引入多線程污茵,會加大同步處理的難度與游戲的不穩(wěn)定性。
unity有提供腳本周期葬项,腳本的生命周期其實(shí)最最重要的就是這張圖了泞当,簡單的可以把這個理解成,這是一個單線程的幀循環(huán)民珍,每一次繪制都會重新走一遍生命周期襟士。
Awake
游戲物體實(shí)例化后并處于激活狀態(tài)時調(diào)用,即使腳本組件沒有激活也會調(diào)用嚷量,而且總是在Start()函數(shù)之前調(diào)用
OnEnable
游戲物體與腳本組件激活時調(diào)用(會反復(fù)觸發(fā))
Start
游戲物體與腳本組件處于激活狀態(tài)陋桂,在Update()運(yùn)行前調(diào)用(只調(diào)用一次,當(dāng)物體關(guān)閉激活狀態(tài)蝶溶,再打開時不會反復(fù)觸發(fā))
不同腳本間的嗜历,執(zhí)行順序:
Start是在場景所有物體的Awake與Enable全部執(zhí)行完畢后進(jìn)行調(diào)用。在unity中身坐,多腳本的加載順序是隨機(jī)的。如果想直接進(jìn)行對不同腳本Awake實(shí)例化的順序控制的話落包,可以通過Script Execution Order來進(jìn)行設(shè)置
FixedUpdate
FixedUFipdate部蛇,是每隔Time.fixedDeltaTime被調(diào)用一次。Time.fixedDeltaTime默認(rèn)是0.02s咐蝇,可以通過Edit->ProjectSettings->Time來設(shè)置涯鲁。用來處理一些物理相關(guān)的內(nèi)容,例如:給剛體加一個作用力時,你必須應(yīng)用作用力在FixedUpdate里的固定幀抹腿,而不是Update中的幀(兩者幀長不同)岛请。
一個最簡單的游戲循環(huán)大就是這樣的,其中的frameTime 相當(dāng)于警绩,Time.deltaTime,它的時間其實(shí)是不固定的
那么如何實(shí)現(xiàn)FixedUFipdate 的固定增量時間呢崇败,實(shí)現(xiàn)原理是 一個主循環(huán)中設(shè)置一個二級循環(huán),以常量時間循環(huán)(其實(shí)在上面的生命周期中可以看出來)肩祥。
所以我們可以直接設(shè)置這個Time.fixedDeltaTime后室。
Update
update跟當(dāng)前平臺的幀數(shù)有關(guān),Update是在每次渲染新的一幀的時候才會調(diào)用混狠,也就是說岸霹,這個函數(shù)的更新頻率和設(shè)備的性能有關(guān)以及被渲染的物體(可以認(rèn)為是三角形的數(shù)量)。在性能好的機(jī)器上可能fps 30将饺,差的可能小些贡避。這會導(dǎo)致同一個游戲在不同的機(jī)器上效果不一致,有的快有的慢予弧。因?yàn)閁pdate的執(zhí)行間隔不一樣了刮吧。
LateUpdate
在調(diào)用所有Update函數(shù)后調(diào)用LateUpdate。這可用于調(diào)整腳本執(zhí)行順序桌肴。例如:當(dāng)物體在Update里移動時皇筛,跟隨物體的相機(jī)可以在LateUpdate里實(shí)現(xiàn)。
線程坠七,協(xié)程 ##(游戲用到了大量的異步操作水醋,這些是怎么實(shí)現(xiàn)的嗎?)
協(xié)程是什么呢彪置?總體來說拄踪,對與Unity,它是單線程的設(shè)計(jì)拳魁,它更傾向使用time slicing(時間分片)的協(xié)程(coroutine)去完成異步任務(wù)惶桐,融合到了剛剛提到的生命周期中。
要理解協(xié)程潘懊,先回顧下線程:線程是操作系統(tǒng)級別的概念姚糊,現(xiàn)代操作系統(tǒng)都實(shí)現(xiàn)并且支持線程,線程的調(diào)度對應(yīng)用開發(fā)者是透明的授舟,開發(fā)者無法預(yù)期某線程在何時被調(diào)度執(zhí)行救恨。基于此释树,一般那種隨機(jī)出現(xiàn)的BUG肠槽,多與線程調(diào)度相關(guān)擎淤。
而協(xié)程Coroutine是編譯器級的,本質(zhì)還是一個線程時間分片去執(zhí)行代碼段秸仙。它通過**相關(guān)的代碼使得代碼段能夠?qū)崿F(xiàn)分段式的執(zhí)行嘴拢,顯式調(diào)用yield函數(shù)后才被掛起,重新開始的地方是yield關(guān)鍵字指定的寂纪,一次一定會跑到一個yield對應(yīng)的地方席吴。因?yàn)閰f(xié)程本質(zhì)上還是在主線程里執(zhí)行的,需要內(nèi)部有一個類似棧的數(shù)據(jù)結(jié)構(gòu)弊攘,當(dāng)該coroutine被掛起時要保存該coroutine的數(shù)據(jù)現(xiàn)場以便恢復(fù)執(zhí)行抢腐。
在Unity3D中,協(xié)程是可自行停止運(yùn)行 (yield)襟交,直到給定的 YieldInstruction 結(jié)束再繼續(xù)運(yùn)行的函數(shù)迈倍。 協(xié)程 (Coroutines) 的不同用途:
? yield; 在下一幀上調(diào)用所有 Update 函數(shù)后,協(xié)同程序?qū)⒗^續(xù)運(yùn)行捣域。
? yield WaitForSeconds(2); 在指定的時間延遲之后啼染,為此幀調(diào)用所有 Update 函數(shù)之后繼續(xù)運(yùn)行
? yield WaitForFixedUpdate(); 在所有腳本上調(diào)用所有 FixedUpdate 后繼續(xù)運(yùn)行
? yield WWW 完成 WWW 下載后繼續(xù)運(yùn)行。
? yield StartCoroutine(MyFunc); 連接協(xié)同程序焕梅,并等待 MyFunc coroutine 首先結(jié)束迹鹅。
也就是說,將代碼段分散在不同的幀中贞言,每次執(zhí)行一段斜棚,下一幀再執(zhí)行yield掛起的地方。
舉個例子: 在OnStart()框架函數(shù)中調(diào)用startCoroutine(GetHttpData)執(zhí)行以下代碼端该窗,其實(shí)是第一次發(fā)起網(wǎng)絡(luò)請求弟蚀,下一次執(zhí)行時則走入yield之后的代碼段繼續(xù)執(zhí)行,從而實(shí)現(xiàn)了一個時間分片的”異步”效果酗失,而不是像線程那樣在操作系統(tǒng)層面分CPU時間片去執(zhí)行义钉。
上面我們了解到了其實(shí)untiy是一個單線程的,但這些并不意味著無法在Unity中使用多線程规肴,只是需要注意使用的場景捶闸。
試想一下,如果在幀序列的主循環(huán)單線程中處理大量耗時操作拖刃,勢必會帶來游戲畫面的卡頓删壮,幀率的下降。
因此兑牡,對于不是畫面更新央碟,也不是常規(guī)的邏輯更新(指包括AI、物理碰撞发绢、角色控制這些)硬耍,而是一些其他后臺任務(wù),則可以將這個獨(dú)立出來開辟一個子線程边酒。
所以经柴,在不使用Unity SDK的前提下,確保做好主子線程的同步(采用C#中的delegate等機(jī)制)墩朦,那么是可以合理使用子線程的坯认。Unity限制使用多線程的原因主要是1.保證數(shù)據(jù)安全,2.降低編程難度氓涣。Unity在底層實(shí)現(xiàn)了線程池牛哺,引擎底層來實(shí)現(xiàn)一些可使用多線程處理的任務(wù)
概括起來,結(jié)合過往移動端的研發(fā)經(jīng)驗(yàn)劳吠,我認(rèn)為有以下幾點(diǎn)可以在子線程中處理:
大量耗時的數(shù)據(jù)計(jì)算
網(wǎng)絡(luò)請求
復(fù)雜密集的I/O操作
Unity3D的NativePlugin中可以新建子線程引润。通過NativePlugin可以接入移動端iOS與Android中的成熟庫,可以是Objective C, Java, C++三種語言交叉混合的方式組成NativePlugin痒玩,然后使用Android或者iOS的SDK開辟子線程淳附。
參考:
https://blog.csdn.net/qq_28180261/article/details/64500720
https://blog.csdn.net/Le11eL/article/details/123464876