最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Unity熱更新哪些事

2022-08-05 17:56 作者:獨(dú)立游戲人-老雷  | 我要投稿

前言

  • 本文想要給大家分享的是Unity熱更那些事兒,會帶大家了解

    • 在打包時(shí)為什么選擇使用Mono作為腳本引擎的后臺?

    • JIT與Mono有什么關(guān)系?

    • IOS熱更新的問題

    • Lua如何進(jìn)行熱更新?

    • ILRunTime熱更新介紹

版權(quán)聲明

  • 本文為“優(yōu)夢創(chuàng)客”原創(chuàng)文章,您可以自由轉(zhuǎn)載,但必須加入完整的版權(quán)聲明

  • 更多學(xué)習(xí)資源請加QQ:1517069595或WX:alice17173獲?。ㄆ髽I(yè)級性能優(yōu)化/熱更新/Shader特效/服務(wù)器/商業(yè)項(xiàng)目實(shí)戰(zhàn)/每周直播/一對一指導(dǎo))

  • 點(diǎn)贊、關(guān)注、分享可免費(fèi)獲得配套學(xué)習(xí)資源

  • 詳細(xì)內(nèi)容可觀看文末完整視頻

議題

  • 理想當(dāng)中的熱更新流程

  • 現(xiàn)實(shí)中的熱更新的流程

  • Unity程序編譯和打包方式

  • Unity程序的執(zhí)行方式

  • IOS平臺的App為什么不能熱更新

  • 解決方案(能支持所有平臺熱更新的通用解決方案)

什么是游戲熱更新

  • 游戲熱更新的更新流程

    • 從軟件商店下載游戲App并安裝到手機(jī)上,啟動手機(jī)上的游戲時(shí)會連接游戲的服務(wù)器,檢查服務(wù)器上面有沒有更新文件列表,如果有,就會把更新內(nèi)容下載下來,沒有就不更新,這是游戲玩家的熱更新流程

    • 游戲開發(fā)者的熱更流程

      • 1,制作游戲更新內(nèi)容(可能是新的資料包、新玩法、新道具、數(shù)值調(diào)整)

      • 2,更新內(nèi)容制作完成后,開發(fā)者會把它們打包成Unity里的AB包并上傳到資源更新服務(wù)器上,上傳更新內(nèi)容的同時(shí)還要上傳更新目錄索引,更新目錄索引中儲存的是文件、模型、貼圖資源的索引和資源內(nèi)容

      • 3,如何這時(shí)玩家打開游戲就會檢查更新,并下載更新資源

  • 更新是不是直接把舊的內(nèi)容覆蓋掉或者把新增的內(nèi)容加上去后游戲就能使用更新以后的內(nèi)容?

    • 沒有那么簡單,特別是游戲代碼熱更新,并不是把最新內(nèi)容下載完以后就能直接執(zhí)行到更新以后的內(nèi)容,中間還有一些過程和步驟,這也是本文要著重講的內(nèi)容,也就是上圖中的第六步:如何在資源下載完以后執(zhí)行熱更新

游戲熱更新的種類

  • 游戲的熱更新的種類分為資源熱更新和代碼熱更新

  • 資源熱更新可以參考我們的《Unity小白的游戲夢》,也可以在文末添加愛麗絲老師進(jìn)行咨詢

  • 代碼熱更新

    • 程序員寫的程序代碼也是一種資源,叫做腳本資源,腳本在Unity中打包以后是以動態(tài)鏈接庫文件的形式存在于磁盤上面的,所以代碼也是一種資源,可以和其他資源一樣按照統(tǒng)一步驟進(jìn)行熱更新

  • 如何更新Unity腳本代碼?

    • 要進(jìn)行代碼熱更新其實(shí)無非就是用新的Unity開發(fā)中的代碼所形成的同名文件去覆蓋舊的同名文件,這是熱更新最理想的情況,現(xiàn)實(shí)中并沒有這么容易

如何打包?

  • 很簡單,點(diǎn)擊Unity文件菜單底下的Build選項(xiàng)就會彈出如上圖左邊一樣的對話框,這個(gè)對話框是打包設(shè)置對話框,點(diǎn)擊下面的Unity引擎設(shè)定(PlayerSeting)按鈕,就會打開上圖右邊的屬性面板

  • 在屬性面板里打包時(shí)需要決定一個(gè)非常重要的選項(xiàng),就是腳本引擎后臺,也就是上圖右邊的ScriptingBackend,這個(gè)選項(xiàng)是一個(gè)下拉框,其中有兩個(gè)選項(xiàng)

    • 1,Mono方式打包

    • 2,IL2CPP方式打包

  • 選擇Mono方式打包出來的程序只能支持32位程序

    • 現(xiàn)在的電腦上預(yù)裝的Windows系統(tǒng)或者M(jìn)ac蘋果電腦,包括手機(jī)上的操作系統(tǒng)一般都會裝64位的系統(tǒng),那么32位跟64位有什么區(qū)別呢?

      • 32位系統(tǒng)所能支持的內(nèi)存范圍比較小,只能支持4個(gè)GB的內(nèi)存,所以大部分人會選擇64位系統(tǒng),它能支持的內(nèi)存范圍比較大

    • 如果使用Mono方式打包就只能打一個(gè)32位的系統(tǒng)包,這也就意味著你的程序雖然跑在64位的系統(tǒng)上,但它只能作為一個(gè)兼容的32位程序來運(yùn)行,最多只能支持使用4個(gè)G的內(nèi)存

    • 對于一個(gè)大型游戲而言只使用4個(gè)G的內(nèi)存是完全不夠用的,所以要注意:使用Mono方式打包的程序不支持64位系統(tǒng)

    • 如果把腳本后臺切換為IL2CPP方式就能夠支持64位的系統(tǒng)平臺

  • 這兩種打包方式的區(qū)別

  • Mono打包方式

    • 用Mono方式來進(jìn)行打包的程序會出現(xiàn)一堆動態(tài)鏈接庫,程序員寫的程序控制邏輯就在上圖左邊被紅框框住的Assembly-CSharp.dll動態(tài)鏈接庫里面

    • 這個(gè)動態(tài)鏈接庫里包括了所有的功能代碼,當(dāng)要執(zhí)行程序時(shí),就必須在程序啟動之前把它加載到Mono虛擬機(jī)里面

      • Mono本身是一個(gè)虛擬機(jī),因?yàn)镃#本身是運(yùn)行在DotNet平臺上面的,而DotNet平臺本身就是一個(gè)基于虛擬機(jī)的的平臺,Mono虛擬機(jī)是對DotNet虛擬機(jī)的跨平臺移植

  • IL2CPP打包方式

    • 使用IL2CPP方式打出的包是沒有動態(tài)連接庫的,它將Mono虛擬機(jī)和Assembly-CSharp.dll動態(tài)鏈接庫整合在了一起,放在libil2cpp.so文件里(如上圖右邊最下方的圖片)

Mono方式腳本編譯流程

  • Unity的項(xiàng)目當(dāng)中可以寫很多C#腳本,C#腳本在打包時(shí)會被Mono平臺中的C#源碼編譯器翻譯成一種匯編語言

  • 在游戲運(yùn)行時(shí),這些匯編語言會跟游戲項(xiàng)目中其他的第三方的DLL一起放入Mono虛擬機(jī),由Mono虛擬機(jī)來解析并執(zhí)行這些中間匯編指令,這就是Mono方式編譯流程

  • C#程序經(jīng)過編譯器的被翻譯成的中間匯編語言在微軟的技術(shù)術(shù)語里叫做CIL,就是通用中間匯編語言

  • 為什么叫通用中間匯編呢?

    • 因?yàn)椴还苁怯肅#還是其他語言寫的腳本,它們經(jīng)過翻譯以后都是翻譯成同一種匯編指令

    • 這也是DotNet框架非常強(qiáng)大的一點(diǎn),不管是用什么語言寫的程序,最終在虛擬機(jī)里面執(zhí)行時(shí)它執(zhí)行的都是相同的一套指令、并且這套指令是跟操作系統(tǒng)無關(guān)的

  • 它是做到機(jī)器指令跟具體平臺是無關(guān)的?

    • 這種匯編指令相當(dāng)于指定了一個(gè)規(guī)范,這個(gè)規(guī)范的執(zhí)行是通過CLR中間語言編譯器執(zhí)行的,它會把與具體平臺無關(guān)的指令翻譯成能夠在具體平臺上執(zhí)行的跟平臺相關(guān)的指令

    • 所以不管使用什么樣的語言寫DoNetT程序都可以被成功編譯,因?yàn)榫幾g以后形成的是中間匯編語言,中間匯編可以運(yùn)行在各種各樣的平臺上

  • 當(dāng)你打開Unity項(xiàng)目時(shí),VS里會形成四個(gè)子項(xiàng)目,這四個(gè)子項(xiàng)目分別是

    • Assembly-CSharp(由程序員寫的程序邏輯)

    • Assembly-CSharp-Editor(由程序員寫的一些編輯器擴(kuò)展)

    • Assembly-CSharp-Editor-firstpass(針對編輯器擴(kuò)展的一些插件)

    • Assembly-CSharp-firstpass(第三方的腳本插件)

  • 當(dāng)Unity自動編譯或者程序員手動編譯Unity腳本時(shí),它就會形成如上圖中間所示的跟項(xiàng)目名稱相對應(yīng)的動態(tài)鏈接庫,這些動態(tài)鏈接庫里存放了程序源代碼對應(yīng)的機(jī)器指令

  • 這些機(jī)器指令長就是右邊的第三幅圖的樣子,它是微軟匯編語言的指令集所形成的代碼

  • 總結(jié)

    • CLI相當(dāng)于中間匯編語言、CIL相當(dāng)于微軟的虛擬機(jī)、CLR是微軟平臺相關(guān)的運(yùn)行時(shí)庫,各個(gè)平臺的功能都包裝在這個(gè)庫里,它們構(gòu)成了微軟的虛擬機(jī)

    • 這個(gè)虛擬機(jī)有兩個(gè)功能

      • 前端把C#代碼編譯成匯編語言

      • 后端在運(yùn)行時(shí)把這些中間匯編語言翻譯成具體平臺的原生機(jī)器指令

    • CLR

      • CLR負(fù)責(zé)了平臺相關(guān)的功能,這些功能包括了進(jìn)程和線程的管理、內(nèi)存分配、垃圾收集、文件管理等

  • Mono虛擬機(jī)

    • 微軟傳統(tǒng)的虛擬機(jī)叫做DotNet虛擬機(jī),DotNet平臺本身是不支持跨平臺,但經(jīng)過Mono的移植,使它能夠運(yùn)行在很多平臺上,包括常見的安卓、蘋果、BSD、Linux、Windows等等

    • 所以Unity能支持跨平臺,其實(shí)并不是Unity自身的能力,而是利用了開源項(xiàng)目的能力

  • Mono虛擬機(jī)運(yùn)行匯編語言有三種方式

    • JIT模式

      • 在這種模式下,虛擬機(jī)會加載動態(tài)鏈接庫文件里的匯編指令,然后進(jìn)行逐條翻譯,翻譯成針對某一個(gè)特定的手機(jī)平臺機(jī)器指令后交給CPU執(zhí)行

    • AOT方式

      • AOT方式是在程序編譯成中間匯編以后進(jìn)一步把程序直接編譯成針對特定平臺的原生機(jī)器碼,然后運(yùn)行時(shí)交給CPU執(zhí)行

      • 這種模式下的程序都是提前編譯好的,它的缺點(diǎn)就是編譯時(shí)間長,優(yōu)點(diǎn)是運(yùn)行速度快,因?yàn)樗阉械某绦蛉刻崆熬幾g好了

      • 這種方式還有一個(gè)問題:如果采用AOT方式,它有一部分代碼還是會在運(yùn)行時(shí)動態(tài)編譯,這就引出了第三種模式

    • FullAOT模式

      • FullAOT模式也可以叫做完全提前編譯模式,它會在程序形成中間匯編以后,把這些中間匯編全部翻譯成一些原生碼,然后在運(yùn)行時(shí)執(zhí)行

      • 這樣就會形成一個(gè)特征,就是使用完全AOT模式編譯的代碼不會在運(yùn)行時(shí)動態(tài)生成任何代碼,這件事情究竟是好是壞呢?這要從兩方面來看

        • FullAOT模式的優(yōu)點(diǎn)是:安全性比較好,因?yàn)樗谶\(yùn)行時(shí)不允許動態(tài)執(zhí)行程序代碼

        • 缺點(diǎn):如果想要熱更新一些新的程序代碼,那么用FullAOT模式就很不方便,因?yàn)樗辉试S動態(tài)更新程序代碼,而熱更新是要求在程序啟動時(shí)動態(tài)加載程序代碼的

        • 這也是熱更新的困境所在,IOS平臺并不支持即時(shí)編譯,安卓平臺則能夠支持即時(shí)編譯,也就是說哪怕你在程序里通過熱更藏了一個(gè)病毒或者木馬,那么安卓手機(jī)照樣可以執(zhí)行,這也是它的安全問題所在

        • 而FullAOT模式是完全禁止動態(tài)運(yùn)行的,所以你沒有機(jī)會把一些從網(wǎng)上下載的病毒木馬放到內(nèi)存里面執(zhí)行,從而保證了程序安全

        • 安卓系統(tǒng)可以支持上面第三種模式,而IOS由于安全性考慮只支持第三種,所以IOS想要進(jìn)行熱更就不太方便

IL2CPP方式腳本編譯流程

  • IL2CPP方式熱更流程的前面幾個(gè)步驟跟Mono方式是一樣的,只是IL2CPP方式不是在代碼運(yùn)行時(shí)放到虛擬機(jī)去執(zhí)行,而是進(jìn)一步再編譯,用IL2CPP工具把中間匯編語言轉(zhuǎn)換成C++代碼,然后經(jīng)過C++的本機(jī)編譯器來進(jìn)行編譯

  • 編譯完了以后會形成一些本機(jī)可執(zhí)行的匯編語言代碼機(jī)器指令,然后會把它交到IL2CPP的虛擬機(jī)來執(zhí)行

  • 所以IL2CPP其實(shí)和Mono一樣,也是由兩部分構(gòu)成,只不過IL2CPP的編譯是翻譯成中間匯編后,直接進(jìn)一步的翻譯成C++代碼,然后再把C++代碼翻譯成匯編機(jī)器指令,這樣IL2CPP在運(yùn)行時(shí)就沒有動態(tài)編譯過程了

Unity對于不同系統(tǒng)平臺的腳本后臺支持

  • Unity對于不同的系統(tǒng)平臺的腳本后臺支持也是不一樣的,安卓能同時(shí)支持Mono,及時(shí)JIT和IL2CPP,而IOS平臺就只能使用IL2CPP方式,大部分主機(jī)平臺也是一樣只能采用IL2CPP方式

IOS平臺禁止JIT編譯

  • 使用Mono腳本后臺編譯能夠支持AOT、FullAOT和JIT,而使用IL2CPP就只能支持提前編譯方式

  • IOS平臺的編譯選項(xiàng)只能支持FullAOT和IL2CPP方式,但因?yàn)镕ullAOT方式只能支持32位系統(tǒng),而蘋果要求從2016年以后都必須支持64位架構(gòu),所以只能采用IL2CPP方式

理想的熱更流程

  • 最為理想的熱更流程就是把熱更功能寫在動態(tài)鏈接庫里,然后在程序啟動時(shí)用新的同名動態(tài)鏈接庫覆蓋舊的,并通過Assembly.Load動態(tài)加載這個(gè)動態(tài)連接庫,最后通過反射來獲取到熱更DLL里的類型,并創(chuàng)建這個(gè)類的實(shí)例

  • 這是最理想的熱更流程,而且因?yàn)榘沧科脚_支持即時(shí)編譯方式,所以這樣的熱更流程方式在安卓平臺使用沒有任何問題,這也是為什么安卓平臺編譯簡單,安全性相對差一點(diǎn)的原因,但這樣的流程放在IOS平臺上面實(shí)施就會失敗

IOS禁止為動態(tài)分配內(nèi)存賦予執(zhí)行權(quán)限

  • 上面是我準(zhǔn)備的一段實(shí)驗(yàn)代碼,它是在蘋果電腦上執(zhí)行的,這段代碼做了哪些事情呢?

    • 這段代碼中create_space函數(shù)是一個(gè)內(nèi)存分配函數(shù),它會按照指定的字節(jié)數(shù)來創(chuàng)建一個(gè)內(nèi)存映射文件,創(chuàng)建內(nèi)存映射文件可以理解為創(chuàng)建一塊內(nèi)存,這塊內(nèi)存具有執(zhí)行權(quán)限,創(chuàng)建好這塊內(nèi)存后會返回這塊內(nèi)存區(qū)域

    • 內(nèi)存分配函數(shù)下面的copy_code_2_space函數(shù)的功能是指定一個(gè)地址,然后往地址里寫一段程序代碼,這個(gè)代碼很簡單,可以把它理解成一個(gè)兩個(gè)數(shù)相加的代碼,或是簡單的賦值,函數(shù)中的memcpy就是把這段程序代碼的機(jī)器指令拷貝到m所代表的內(nèi)存里

    • 最下方main函數(shù)指定一塊內(nèi)存,大小是1024,然后通過create_space按照內(nèi)存大小來創(chuàng)建一塊內(nèi)存,創(chuàng)建了這塊內(nèi)存地址以后會往這塊內(nèi)存地址里寫入一個(gè)函數(shù)

  • 在Mac電腦中執(zhí)行這段代碼時(shí)會得到運(yùn)行報(bào)錯(cuò),為什么會報(bào)錯(cuò)?

    • 因?yàn)榇a中的分配內(nèi)存函數(shù)為這塊內(nèi)存賦予了執(zhí)行權(quán)限,而IOS平臺是禁止為動態(tài)分配的內(nèi)存塊賦予執(zhí)行權(quán)限的,這就是為什么IOS平臺無法通過動態(tài)加載程序代碼進(jìn)行熱更新的原因

  • 所以IOS只能采用靜態(tài)編譯方式,靜態(tài)編譯方式有兩種方案:Full-AOT和IL2CPP,但即使采用了這兩種方式的任意一種,也不能完全避免一些由于不合理使用代碼所帶來的問題

  • 下面是一段IOS平臺上的程序

  • 在這段程序中我創(chuàng)建了一個(gè)管理器接口和一個(gè)接收者接口,管理器接口可以發(fā)送消息,接收者接口可以響應(yīng)消息

  • 通過管理器接口繼承實(shí)現(xiàn)管理器類,在這個(gè)管理器類里實(shí)現(xiàn)SendMessage方法,當(dāng)管理器類實(shí)現(xiàn)SendMessage方法時(shí),它會對IReceiver類型的target對象調(diào)用OnMessage方法,傳入的參數(shù)是泛型類型的value,這里會出現(xiàn)一些問題

    • 調(diào)用SendMessage方法時(shí)會執(zhí)行到OnMessage方法,問題在于OnMessage的參數(shù)是泛型類型

    • 由于采用了Full-AOT和IL2CPP來進(jìn)行編譯,而泛型代碼由于在運(yùn)行之前無法提前得知泛型的實(shí)際數(shù)據(jù)類型,所以當(dāng)Mono虛擬機(jī)以Full-AOT的方式執(zhí)行編譯代碼,或者是IL2CPP虛擬機(jī)執(zhí)行這樣代碼時(shí)就會直接跳過OnMessage的執(zhí)行

    • 因?yàn)镕ull-AOT方式包括和IL2CPP方式是靜態(tài)編譯的,所以不能執(zhí)行這些在程序運(yùn)行當(dāng)中動態(tài)指定類型的代碼

  • 所以實(shí)際上IL2CPP根本就沒有把泛型類型對應(yīng)的實(shí)際類型代碼編譯到最終程序當(dāng)中,當(dāng)你執(zhí)行OnMessage時(shí)就會看到下面的報(bào)錯(cuò),意思是你嘗試在AOT編譯的程序中執(zhí)行動態(tài)類型的代碼

  • 要解決這個(gè)問題可以強(qiáng)制AOT編譯系統(tǒng)在程序運(yùn)行之前提前生成針對某種類型的代碼,比如你發(fā)送消息時(shí)要發(fā)送的類型是AnyEnum類型的消息,那么就可以提前寫一段OnMessage(AnyEnum.Zero)

  • 這樣就會強(qiáng)制IL2CPP編譯這段代碼,從而生成針對AnyEnum類型的程序代碼,但這樣也就喪失了泛型的靈活性

  • 腳本限制其實(shí)還是很多的,大家如果想要具體的了解IOS是如何禁止動態(tài)內(nèi)存執(zhí)行的,可以參考一下我們的《Unity小白的游戲夢》課程,如果想要了解如何通過反射像病毒一樣動態(tài)生成代碼也可以參考一下《Unity小白的游戲夢》課程

解決方案

  • 既然IOS平臺有這么多的限制,那么應(yīng)該怎么去應(yīng)對IOS的平臺限制呢?

    • 有一個(gè)簡單粗暴的方法,就是不為IOS平臺準(zhǔn)備熱更新功能,只為安卓平臺準(zhǔn)備熱更新功能,但這種方案可行嗎?

    • IOS的用戶按照傳統(tǒng)認(rèn)為都是一些高價(jià)值用戶,而且就算不考慮高價(jià)值,IOS的用戶至少也占到手機(jī)市場三分之一,任何一個(gè)開發(fā)商都不會放棄這部分用戶

  • 所以不針對IOS做熱更是不行的,那么應(yīng)該怎樣針對IOS做熱更呢?

    • 我們的解決方案是嵌入一種腳本語言,嵌入的腳本語言有兩種

      • Lua,這是比較傳統(tǒng)的熱更方案,很多PC端的游戲都是使用Lua方式進(jìn)行熱更的,Lua熱更方案有兩種,一種是ToLua,一種是XLua

      • C#熱更方案,C#熱更方案是比較有前景的一種方式,因?yàn)镮LRunTime熱更已經(jīng)被加入到Unity官方的PackageManager里面了

  • 為什么腳本語言可以熱更?

    • 腳本語言的工作原理就是每一次啟動游戲時(shí)都要在服務(wù)器上檢測一下有沒有程序腳本更新,有就從服務(wù)器上把腳本下載到本地客戶端,下載完以后客戶端啟動時(shí)就會把腳本加載到內(nèi)存里執(zhí)行

    • 但剛才也說了IOS平臺禁止動態(tài)分配一塊內(nèi)存執(zhí)行其中的代碼,為什么兩種方案的代碼能加載到內(nèi)存里面并執(zhí)行呢?限于篇幅問題,我會在我們的《Unity小白的游戲夢》課程里花上一個(gè)專題來專門介紹這塊內(nèi)容

小結(jié)

  • 1,Unity腳本后臺有哪幾種?

  • 2,每種腳本后天分別支持哪幾種編譯方式?

  • 3,安卓/蘋果分別支持哪幾種編譯方式?

  • 4,C#腳本對反射的使用有限制,那么什么樣的反射方法可以使用呢?

  • 5,Lua/ILRunTime熱更方案都會把腳本加載到內(nèi)存并執(zhí)行,但是為什么這兩種方式就能正常執(zhí)行動態(tài)加載的腳本呢?

進(jìn)一步學(xué)習(xí)的內(nèi)容

寫在最后

  • 更多學(xué)習(xí)資源請加QQ:1517069595或WX:alice17173獲取(企業(yè)級性能優(yōu)化/熱更新/Shader特效/服務(wù)器/商業(yè)項(xiàng)目實(shí)戰(zhàn)/每周直播/一對一指導(dǎo))

  • 點(diǎn)贊、關(guān)注、分享可免費(fèi)獲得配套學(xué)習(xí)資源

  • 詳細(xì)內(nèi)容可觀看下方完整視頻


Unity熱更新哪些事的評論 (共 條)

分享到微博請遵守國家法律
建始县| 香港 | 泗阳县| 阳江市| 安远县| 合水县| 周至县| 龙江县| 洛阳市| 遵义县| 全州县| 观塘区| 天柱县| 焦作市| 雅江县| 江安县| 邓州市| 仙桃市| 广州市| 泸西县| 济南市| 邵武市| 徐汇区| 丰镇市| 铁力市| 北宁市| 宜城市| 抚州市| 余姚市| 辽中县| 涞水县| 托克逊县| 泸溪县| 松溪县| 新余市| 新民市| 普兰店市| 芮城县| 绥江县| 新平| 永嘉县|