Unity引擎与C#脚本简介
歡迎大家前往騰訊云+社區,獲取更多騰訊海量技術實踐干貨哦~
由?QQ會員技術團隊 發布在云+社區
1. Unity編輯器基礎
從原理上講,游戲開發就是將一系列變動的場景呈現在玩家面前,并根據玩家的輸入修改游戲畫面;而游戲畫面則是通過調用目標操作系統上的圖形圖像庫來繪制的。比較知名的圖形圖像庫有Windows上的DirectX,*nix系統、macOS和iOS等系統上用到的OpenGL以及Android用到的Vulkan等。
一般來講,底層的圖形圖像API只能進行最基本的三角形繪制,但是,因為是通過計算機的GPU進行的操作,具有并行計算的優勢,在短短六十分之一秒時間內,也可以繪制出成千上萬個三角形,而這么多小三角形堆疊起來看,視覺效果也就和真實場景差別不大了。
[ 圖一:古墓麗影勞拉變化圖 ]
現代游戲引擎一般都會把游戲人物的“建模”工作交給第三方,引擎本身只負責游戲場景和人物的繪制以及內部交互邏輯。第三方建模軟件通過模擬人物的真實3D外觀來將虛擬人物表面“三角形化”,附帶上游戲人物在做出不同動作時的外觀數據,最后生成游戲引擎可識別格式的文件,這個過程就是所謂的3D建模。
[ 圖二:繪制流程 ]
3D模型制作完成后,會由游戲引擎進行繪制,這個過程一般稱作“著色”(Shading)。著色的核心是叫做“著色器(Shader)”的GPU程序 - GPU通過輸入一些參數信息,然后執行著色器程序就能生成最終的游戲圖像。
GPU需要的參數信息主要有兩種:一是紋理,二是材質。
紋理是指一個模型的表面,可以理解成一件衣服平鋪起來的樣子。如果是一個三維物體,其表面的紋理可以想象成是把它的表面拆開,然后壓扁后的樣子。什么是材質呢?材質(Material)從字面上理解的話就是材料,比如木頭和大理石,看起來就是不一樣的效果。同樣的紋理,用不一樣的材質來繪制,會得到不一樣的效果圖,因為材質有一些關鍵的參數,會影響著色器的繪制效果。
比較重要的一個參數是反射率(Albedo)。
光滑材質的反射率比較高,看起來就會亮一些。在自然白光的照射下,這樣的材質看起來會偏白,如果沿著光照方向看過去,會出現光斑效果(太陽光照射下的湖面看起來會有一種很耀眼的效果)。粗糙材質的反射率比較低,看起來就比較柔和。典型的高反射率材質比如光滑的金屬表面,典型的低反射率材質有布料、地面等。在3D場景中,反射率高的物體受周圍物體的影響更大。譬如,一個平靜的湖面會倒映出地面的建筑物。因此,高反射率的材質通常需要更多的繪制步驟。
[ 圖三:一個金屬球體在場景中的效果圖 ]
材質的另一個重要參數是法向圖(Normal Map)。
法向就是物體表面的方向。法向圖表示的是材質的表面細節,比如凹槽、斑點、凸起或者空洞等,法向圖通常以紋理圖來表示。然而不同于一般的紋理圖,法向圖的每個像素點稱作“紋素(texel)”,它表示的是紋理在此位置處的光照反射方向,紋素的RGB分量分別對應反射方向的XYZ分量。
[ 圖四:法向圖示例 ]
一個3D模型的表面紋理被分割成一個個小三角形,而法向圖就表示此表面的每個像素點位置的光照反射方向。方向不同的三角形繪制出來和周圍的三角形看起來顏色是不同的,從而產生了視覺上的凸起/凹陷效果。這種物體的表面細節,如果在3D建模階段通過修改模型外觀的方式來實現的話,會增加很多物體表面的細小的繪制操作。通過材質的法向圖來實現,將物體“表面”和物體的實際皮膚剝離開了,可以實現同一個人物穿上不同衣服的效果。
[ 圖五:繪制效果圖 ]
如上圖所示,右邊的物體采用左邊的法向圖來繪制,注意看凸起位置的顏色
2. C#腳本語言
2.1 為什么需要腳本?
長久以來,游戲引擎開發都采用底層語言如C++來進行,這對于游戲上層開發來說,并不友好。很難想象如果使用一款引擎修改某個人物的動作,還需要直接調用C++底層的接口,這樣既不安全,也不方便。因此,一般引擎從設計之初就會把封裝好的繪制接口通過某些上層語言暴露出來,給游戲制作方使用。這些上層語言就叫做游戲腳本語言。
lua是腳本語言里面比較流行的一種,因其虛擬機小巧、API豐富、可靈活定制而深受游戲引擎開發商的喜愛。Unity使用了C#和Unity Script(現已廢棄)來作為腳本語言。C#語言因為建立在.NET IL之上而具有跨平臺擴展性。這樣,游戲開發者只需要一套代碼就可在多個平臺運行。
[ 圖六:.NET CIL和CLR ]
2.2 IL是什么?
IL(Intermediate Language,在.NET平臺下是CIL,Common Intermediate Language)是一種中間語言格式,類似于Java的字節碼(byte code),這種格式的代碼需要一個虛擬機來“解釋”執行。IL的所有指令都是基于虛擬堆棧的:調用函數前,先將參數push到虛擬堆棧里面;函數執行的時候,從虛擬堆棧里面取出參數,然后將結果壓入虛擬堆棧。由于調用方式簡單,IL語言的指令集也比較精簡。
IL作為腳本語言的獨到之處在于可以將C#上層語言的各種特性(如泛型、協程等)轉換成基本的IL指令集,但是這樣的轉換也是有代價的 — 轉換后的IL指令比普通的函數調用多出數倍。因此,在游戲開發中,不宜在每一幀中都進行這一類的調用。
另外,IL語言執行需要一個虛擬機翻譯成目標平臺的機器碼,雖然.NET虛擬機已經比較高效了(可參考.NET與Java的對比),但是和平臺原生代碼比起來,依然有一些差距。在iOS平臺上,由于蘋果禁止使用JIT方式,IL指令需要預先編譯成目標平臺庫文件,然后在最終二進制文件打包的時候作為第三方庫鏈接進去。Unity游戲幾乎所有的游戲邏輯都是通過腳本來實現的,一個大型游戲,成千上萬個腳本,AOT方式打包造成的效率低下,是不得不考慮的問題。因此,Unity在5.3.4版本中引入了il2cpp技術。
2.3 il2cpp原理
顧名思義,il2cpp就是把中間語言轉換成cpp代碼的工具。上面我們講到,在iOS平臺上,由于無法使用JIT方式執行IL指令,所以需要先將游戲腳本打包成.NET Managed Assembly(這里的Managed是指二進制文件是在.NET層面打包的,可能會依賴.NET底層庫,可以理解為“安全的”庫文件。另外有些庫文件是通過直接封裝C/C++接口方式生成的,由于有如指針之類的底層內存操作,所以稱作是Unmanaged Assembly),然后和.NET CLR的Assembly鏈接之后生成最終的平臺二進制文件。il2cpp的作用是去掉鏈接.NET CLR的步驟,將C#腳本生成的Managed Assembly“翻譯”成C++文件,最后用目標平臺的編譯器編譯這些C++文件來生成最終的游戲可執行文件。
[ 圖七:il2cpp工作原理示意圖 ]
il2cpp會先讀取.NET二進制文件,解析其中的符號,然后將其中C#方法轉換成對應的C方法。雖然名為il2cpp,但其實它只用到了很少部分的C++特性,絕大多數轉換后的代碼都是C函數。
[ 圖八:il2cpp轉換后的代碼示例 ]
在游戲運行前,il2cpp會啟動一個小的虛擬機,用于動態解析C方法。其會將所有方法的簽名放在一個叫做global-metadata.dat的文件里,方法調用的時候會先從此文件里讀取C函數地址,然后再調用。
獲取函數指針的方法是這個:
inline Il2CppMethodPointer il2cpp_codegen_resolve_icall (const char* name){Il2CppMethodPointer method = il2cpp::vm::InternalCalls::Resolve (name); if (!method){il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetMissingMethodException(name));} return method; }[ 圖九:獲取函數指針 ]
Unity確保了所有采用il2cpp平臺實現的游戲,其metadata的格式都是一樣的。metadata加載時采用了內存映射技術,上述函數實際上會從一張內存的數據表里查找方法名對應的鍵值,也即目標函數的地址。
為何Unity要采用文件來記錄方法名?一是游戲有動態解析方法的需求;再者是這樣可以隱藏掉游戲內部邏輯的實現,起到一部分混淆的作用;最后還有一個重要的原因是Unity編輯器里可以設置腳本執行時候的延遲時間,而這些信息可以很方便的放在文件里。
Unity C#層面的接口暴露給游戲開發者,開發者通過C#腳本編寫游戲邏輯,然后通過il2cpp將腳本翻譯成C++文件,接著鏈接上Unity C#接口的底層C++實現,最終生成游戲的二進制文件,這就是Unity游戲開發的大致過程。
按照Unity的說法,通過il2cpp方式打包有多種好處:
以上就是游戲開發的一些基本知識。
相關閱讀
基于騰訊云的視頻聊天研究
ios微信內存監控
2017年數據庫技術盤點
此文已由作者授權騰訊云+社區發布,轉載請注明文章出處
原文鏈接:https://cloud.tencent.com/developer/article/1047768
總結
以上是生活随笔為你收集整理的Unity引擎与C#脚本简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UDA机器学习基础—交叉验证
- 下一篇: git如何利用分支进行多人开发