一、判斷題1 C#支持繼承多個類,達到重用代碼功能的效果。 (×) 2 修改Renderer的sharedMaterial,所有使用這個材質(zhì)球的物體都會被改變,并且也改變儲存在工程里的材質(zhì)設(shè)置。 (√) 3 Unity中可以創(chuàng)建子線程,并在子線程中直接修改UI對象。 (×) 4 Unity不支持在協(xié)程中嵌套調(diào)用協(xié)程。 (×) 5 C#不同命名空間中可以存在相同類名。 (√) 6 Unity會自動為MonoBehaviour子類的public變量做序列化。 (√) 7 每個枚舉成員均具有相關(guān)聯(lián)的常數(shù)值,可以設(shè)置為負數(shù)常數(shù)。 (×) 8 只帶有 get 訪問器的屬性稱為只讀屬性,無法對只讀屬性賦值。 (√) 9 protected成員只能被本類內(nèi)部訪問,無法被子類直接訪問。 (×) 10 父物體發(fā)生Transform變化的時候,子物體跟隨一起變化,但是子物體發(fā)生變化的時候,父物體不動。 (√) 二、填空題1 Unity中 Game 視圖可以設(shè)置分辨率,在該視圖中呈現(xiàn)的就是攝像機渲染的畫面。 2 gameObject.AddComponent()的時候,Test腳本的 Awake 函數(shù)會立即被調(diào)用。 3 任何游戲?qū)ο笤趧?chuàng)建的時候都會附帶 Transform 組件,用于儲存并操控物體的位置、旋轉(zhuǎn)和縮放。 4 只在編輯器環(huán)境下運行的代碼,可以使用 UNITY_EDITOR 宏把代碼包起來。 5 Unity中可用四元數(shù)Quaternion表示 旋轉(zhuǎn) ,不受萬向鎖影響,可以進行插值運算。 6 Unity協(xié)程中可以使用 yield return null 實現(xiàn)暫緩一幀,在下一幀接著往下處理。 7 transform.forward表示物體的 z 軸的方向。 8 C#中的委托類似于C/C 中的 函數(shù)指針 ,委托類型的聲明以 delegate 關(guān)鍵字開頭。 9 Unity中的 Plugins 目錄用于放置Native插件文件,Android平臺的jar文件必須放置在 Assets/Plugins/Android/libs 目錄中。 10 在移動平臺,Resources目錄中的資源通過 Resources.Load 接口來加載,如果想實現(xiàn)資源增量更新,則一般考慮把資源打包成 AssetBundle 資源類型。 11 定義對象間的一種一對多依賴關(guān)系,使得每當一個對象狀態(tài)發(fā)生改變時,其相關(guān)依賴對象皆得到通知并被自動更新,可以使用 觀察者 設(shè)計模式, 12 Unity中每個材質(zhì)球必須綁定一個 shader (腳本),它決定了該材質(zhì)的渲染方式以及可配置屬性。 13 Unity中 StreamingAssets 文件夾是只讀的,里面的所有文件將會被原封不動地復制制到目標平臺機器上的特定文件夾里,不會被壓縮。在Android或iOS平臺,通過 WWW 類來讀取其中的文件。 14 當場景中有多個攝像機時,可以設(shè)置攝像機的 depth 值,調(diào)整相機的渲染順序。 15 為了加快渲染速度和減少圖像鋸齒,貼圖被處理成由一系列被預(yù)先計算和優(yōu)化過的圖片組成的文件,這樣的貼圖被稱為 MipMap 。 三、問答題1、C#中的委托是什么delegate int MyDelegate(int value); //聲明委托類型 C#所有的委托派生自 System.Delegate 類,委托是存有對某個方法的引用的一種引用類型變量,委托變量可以當作另一個方法的參數(shù)來進行傳遞,實現(xiàn)事件和回調(diào)方法。有點類似C 中的函數(shù)指針,但是又有所不同。在C 中,函數(shù)指針不是類型安全的,它指向的是內(nèi)存中的某一個位置,我們無法判斷這個指針實際指向什么,對于參數(shù)和返回類型難以知曉。而C#的委托則完全不同,它是類型安全的,我們可以清晰的知道委托定義的返回類型和參數(shù)類型。
延伸 委托和事件: 本質(zhì)區(qū)別:從定義上說,委托被編譯器編譯成一個類,所以它可以像類一樣在任何地方定義,而事件被編譯成一個委托類型的私有字段和兩個公有add 和 remove 方法(有點類似于屬性的定義)不過這兩個方法都有一個參數(shù),這個參數(shù)就是委托,所以,它只能定義在一個類里面。 event MyDelegate myevent; //定義事件 委托相當于一系列函數(shù)的抽象類,這一系列函數(shù)要求擁有相同的參數(shù)和返回值;而事件(event)相當于委托的一個實例,事件是特殊的委托。 事件使用 發(fā)布-訂閱(publisher-subscriber) 模型。 發(fā)布器(publisher) 是一個包含事件和委托定義的對象。事件和委托之間的聯(lián)系也定義在這個對象中。發(fā)布器(publisher)類的對象調(diào)用這個事件,并通知其他的對象。 訂閱器(subscriber) 是一個接受事件并提供事件處理程序的對象。在發(fā)布器(publisher)類中的委托調(diào)用訂閱器(subscriber)類中的方法(事件處理程序)。
為什么需要事件? 事件最常用的應(yīng)用場景是圖形用戶界面(GUI),如一個按鈕點擊事件,菜單選擇事件,文件傳輸完成事件等。簡單的說,某件事發(fā)生了,你必須要作出響應(yīng)。你不能預(yù)測事件發(fā)生的順序。只能等事件發(fā)生,再作出相應(yīng)的動作來處理。觸發(fā)事件的類本身對怎樣處理事件不感興趣。按鈕說:“我被點過了”,響應(yīng)類作出合適的響應(yīng)。 2、值類型與引用類型的區(qū)別1.值類型存儲在棧(stack)中,引用類型數(shù)據(jù)存儲在堆(heap)中,內(nèi)存單元中存放的是堆中存放的地址。 2.值類型存取快,引用類型存取慢。 3.值類型表示實際數(shù)據(jù),引用類型表示指向存儲在內(nèi)存堆中的數(shù)據(jù)的指針和引用。 4.棧的內(nèi)存是自動釋放的,堆內(nèi)存是.NET中會由GC來自動釋放。 5.值類型繼承自System.ValueType,引用類型繼承自System.Object。 延伸 數(shù)據(jù)結(jié)構(gòu)的堆和棧: 堆和棧都是一種數(shù)據(jù)項按序排列的數(shù)據(jù)結(jié)構(gòu)。 棧就像裝數(shù)據(jù)的桶,具有后進先出性質(zhì);堆像一棵倒過來的樹,堆是一種經(jīng)過排序的樹形數(shù)據(jù)結(jié)構(gòu),每個結(jié)點都有一個值。堆的存取是隨意,這就如同我們在圖書館的書架上取書,雖然書的擺放是有順序的,但是我們想取任意一本時不必像棧一樣,先取出前面所有的書。 內(nèi)存結(jié)構(gòu):
棧中分配局部變量空間; 堆區(qū)是向上增長的用于分配程序員申請的內(nèi)存空間; 靜態(tài)區(qū)是分配靜態(tài)變量、全局變量空間的; 只讀區(qū)是分配常量和程序代碼空間的; 3、接口Interface與抽象類abstract class的區(qū)別接口和抽象類是支持抽象定義的兩種機制。 接口是完全抽象的,只能聲明方法,而且只能聲明public的方法,不能聲明private及protected的方法,不能定義方法體,也不能聲明實例變量。抽象類是可以有私有的方法或者私有的變量,如果一個類中有抽象方法,那么就是抽象類。 一個類可以實現(xiàn)多個接口,但一個類只能繼承一個抽象類。 接口強調(diào)特定功能的實現(xiàn),具有哪些功能,而抽象類強調(diào)所屬關(guān)系。 盡管接口實現(xiàn)類及抽象類的子類都必須要實現(xiàn)相應(yīng)的抽象方法,但實現(xiàn)的形式不同。接口中的每一個方法都是抽象方法,都只是聲明的, 沒有方法體,實現(xiàn)類必須都要實現(xiàn);而抽象類的子類可以有選擇地實現(xiàn),只實現(xiàn)其中的抽象方法,覆蓋其中已實現(xiàn)了的方法。 延伸 如何弱化代碼依賴關(guān)系: 在代碼的控制流中,調(diào)用關(guān)系和依賴關(guān)系幾乎是完全吻合的,如果缺乏良好的封裝與接口提取,那么調(diào)用者必須掌握被調(diào)用者的代碼實現(xiàn)。而抽象良好的接口,能夠使控制流對代碼的依賴實現(xiàn)反轉(zhuǎn),比如面向同一個接口協(xié)議,被調(diào)用者需要在協(xié)議的約束下對提供的服務(wù)進行實現(xiàn),它的代碼依賴協(xié)議的制定,而調(diào)用者只用依據(jù)協(xié)議按需獲取服務(wù)即可,在控制流上依賴接口,而不再需要在代碼上依賴被調(diào)用者,此即是從接口到被調(diào)用者的控制流-代碼依賴關(guān)系反轉(zhuǎn)。 代碼依賴關(guān)系弱化,意味著業(yè)務(wù)可以模塊化、組件化,拆分的功能組團可以以“插件”的方式并行獨立開發(fā)維護,這種隔離大大提升開發(fā)運維效率,同時獨立部署的能力也更加符合軟硬件發(fā)展的趨勢。 4、Unity實現(xiàn)跨平臺的原理Unity的跨平臺技術(shù)是通過一個Mono虛擬機實現(xiàn)的。就是通過Mono將C#腳本代碼編譯成CIL,然后Mono運行時利用JIT或者AOT將CLI編譯成目標平臺的原生代碼實現(xiàn)的。 不過這個虛擬機更新太慢,不能很好地適應(yīng)眾多的平臺,所以后來推出了IL2CPP,把本來應(yīng)該再mono的虛擬機上跑的中間代碼轉(zhuǎn)換成cpp代碼,這樣再把生成的cpp代碼,利用c 的跨平臺特性,在各個平臺上通過對各平臺都有良好優(yōu)化的native c 編譯器編譯,以獲得更高的效率和更好的兼容性。 延伸 講講IL: IL是.NET框架中間語言(Intermediate Language)的縮寫。使用.NET框架提供的編譯器可以直接將源程序編譯為.exe或.dll文件,但此時編譯出來的程序代碼并不是CPU能直接執(zhí)行的機器代碼,而是一種中間語言IL(Intermediate Language)。 使用中間語言的優(yōu)點有兩點,一是可以實現(xiàn)平臺無關(guān)性,既與特定CPU無關(guān);二是只要把.NET框架某種語言編譯成IL代碼,就實現(xiàn).NET框架中語言之間的交互操作(這就是為什么unity3D里面可以c#和js混編)。 在Mac OS上,因為iOS的現(xiàn)有限制,面向iOS的C#代碼會通過AOT編譯技術(shù)直接編譯為ARM匯編代碼。而在Android上,應(yīng)用程序會轉(zhuǎn)換為IL,啟動時再進行JIT編譯。 講講JIT: JIT:即時編譯(Just In-Time compile),這是.NET運行可執(zhí)行程序的基本方式,編譯一個.NET程序時,編譯器將源代碼翻譯成中間語言,它是一組可以有效地轉(zhuǎn)換為本機代碼且獨立于CPU的指令。當執(zhí)行這些指令時,實時(JIT)編譯器將它們轉(zhuǎn)化為CPU特定的代碼。部分加密軟件通過掛鉤JIT來進行IL加密,同時又保證程序正常運行。JIT也會將編譯過的代碼進行緩存,而不是每一次都進行編譯。所以說它是靜態(tài)編譯和解釋器的結(jié)合體。 AOT:靜態(tài)編譯,它在程序運行之前就編譯好了。 5、四元數(shù)的作用四元數(shù)用于表示旋轉(zhuǎn)。 其相對于歐拉角的優(yōu)點: 1.避免萬向鎖。 2.只需要一個4維的四元數(shù)就可以執(zhí)行繞任意過原點的向量的旋轉(zhuǎn),方便快捷,在某些實現(xiàn)下比旋轉(zhuǎn)矩陣效率更高。 3.可以提供平滑插值。 延伸 什么是歐拉角? 用一句話說,歐拉角就是物體繞坐標系三個坐標軸(x,y,z軸)的旋轉(zhuǎn)角度。 1,靜態(tài):即繞世界坐標系三個軸的旋轉(zhuǎn),由于物體旋轉(zhuǎn)過程中坐標軸保持靜止,所以稱為靜態(tài)。 2,動態(tài):即繞物體坐標系三個軸的旋轉(zhuǎn),由于物體旋轉(zhuǎn)過程中坐標軸隨著物體做相同的轉(zhuǎn)動,所以稱為動態(tài)。 物體的任何一種旋轉(zhuǎn)都可分解為分別繞三個軸的旋轉(zhuǎn),但分解方式不唯一。 unity 3D歐拉角的旋轉(zhuǎn)順序(父子關(guān)系)是y-x-z。 unity中最簡單的萬向鎖就是先讓X軸旋轉(zhuǎn)90度,z軸旋轉(zhuǎn)和y軸旋轉(zhuǎn)效果是一樣。 講講萬向鎖: 萬向鎖(英語:Gimbal lock)是在使用動態(tài)歐拉角表示三維物體的旋轉(zhuǎn)時出現(xiàn)的問題。 萬向節(jié)死鎖的根本問題是歐拉角(EulerAngles)保存的信息不足以描述空間中的唯一轉(zhuǎn)向。 6、Unity腳本生命周期與執(zhí)行順序名稱 | 觸發(fā)時機 | 用途 |
---|
Awake | 腳本實例被創(chuàng)建時調(diào)用 | 用于游戲?qū)ο蟮某跏蓟⒁釧wake的執(zhí)行早于所有腳本的Start函數(shù) | OnEnable | 當對象變?yōu)榭捎没蚣せ顮顟B(tài)時被調(diào)用 |
| Start | Update函數(shù)第一次運行之前調(diào)用 | 用于游戲?qū)ο蟮某跏蓟?/td> | Update | 每幀調(diào)用一次 | 用于更新游戲場景和狀態(tài) | FixedUpdate | 每個固定物理時間間隔調(diào)用一次 | 用于物理狀態(tài)的更新 | LateUpdate | 每幀調(diào)用一次(在update之后調(diào)用) | 用于更新游戲場景和狀態(tài),和相機有關(guān)的更新一般放在這里 | OnGUI | 渲染和處理OnGUI事件 |
| OnDisable | 當前對象不可用或非激活狀態(tài)時被調(diào)用 |
| OnDestroy | 當前對象被銷毀時調(diào)用 |
|
延伸 Awake與Start: Awake和Start只會調(diào)用一次,當游戲過程中調(diào)整腳本的可見狀態(tài)時,會分別調(diào)用OnEnable, OnDisable函數(shù),而Awake和Start將不會再調(diào)用。 Start可能不會被立刻調(diào)用,比如我們之前沒有讓其enable,當腳本被enable時,Start才會被調(diào)用。 不同對象之間的Awake順序是不得而知的。 如下,MyBhv腳本在Awake中初始化speed=1f;執(zhí)行完下面的代碼,speed的值是多少? var bhv = go.AddComponent<MyBhv>()
bhv.speed = 3f;
答: 3f,因為Awake會先執(zhí)行。 Update與FixedUpdate: 同:當MonoBehaviour啟用時,其在每一幀被調(diào)用。都是用來更新的。 異:Update()每一幀的時間不固定,受場景渲染的復雜程度,還有輸入的一系列事件等等各種原因影響,游戲畫面的幀率是在不斷變化的。 FixedUpdate()每幀與每幀之間相差的時間是相對固定的(值為Time.fixedDeltaTime),默認是0.02s,可以通過Edit->ProjectSettings->Time來設(shè)置。物理相關(guān)的處理(比如Rigidbody)一般在FixedUpdate()中。 Update與LateUpdate: LateUpdate是在所有Update函數(shù)調(diào)用后被調(diào)用??捎糜谡{(diào)整腳本執(zhí)行順序。例如當物體在Update里移動時,跟隨物體的相機可以在LateUpdate里實現(xiàn)。 有2個不同的腳本同時在Update中控制一個物體,那么當其中一個腳本改變物體方位、旋轉(zhuǎn)或者其他參數(shù)時,另一個腳本也在改變這些東西,那么這個物體的方位、旋轉(zhuǎn)就會出現(xiàn)一定的反復。如果還有個物體在Update中跟隨這個物體移動、旋轉(zhuǎn)的話,那跟隨的物體就會出現(xiàn)抖動。 如果是在LateUpdate中跟隨的話就會只跟隨所有Update執(zhí)行完后的最后位置、旋轉(zhuǎn),這樣就防止了抖動。 7、講講你對Unity的協(xié)程的理解協(xié)程不是線程。協(xié)程的實現(xiàn)原理是迭代器,而迭代器的實現(xiàn)原理是狀態(tài)機。 unity中協(xié)程執(zhí)行過程中,通過 yield return XXX ,將程序掛起,去執(zhí)行接下來的內(nèi)容。在遇到 yield return XXX 語句之前,協(xié)程方法和一般的方法是相同的,也就是程序在執(zhí)行到 yield return XXX 語句之后,接著才會執(zhí)行的是 StartCoroutine() 方法之后的程序,走的還是單線程模式,僅僅是將 yield return XXX 語句之后的內(nèi)容暫時掛起,等到特定的時間才執(zhí)行。 那么掛起的程序什么時候才執(zhí)行?協(xié)同程序主要是Update() 方法之后,LateUpdate() 方法之前調(diào)用的。 通過設(shè)置MonoBehaviour 腳本的enabled 對協(xié)程是沒有影響的,但如果gameObject.SetActive(false) 則已經(jīng)啟動的協(xié)程則完全停止了,即使在Inspector 把gameObject 激活還是沒有繼續(xù)執(zhí)行。也就說協(xié)程雖然是在MonoBehvaviour 啟動的(StartCoroutine ),但是協(xié)程函數(shù)的地位完全是跟MonoBehaviour 是一個層次的,不受MonoBehaviour 的狀態(tài)影響,但跟MonoBehaviour 腳本一樣受gameObject 控制,也應(yīng)該是和MonoBehaviour 腳本一樣每幀輪詢yield 的條件是否滿足。 協(xié)程不是只能做一些簡單的延遲,如果只是單純的暫停幾秒然后在執(zhí)行就完全沒有必要開啟一個協(xié)程。 協(xié)程的真正作用是分步做一些比較耗時的事情,比如加載游戲里的資源。 延伸 講講進程、線程、協(xié)程: 進程擁有自己獨立的堆和棧,既不共享堆,亦不共享棧,進程由操作系統(tǒng)調(diào)度。 線程擁有自己獨立的棧和共享的堆,共享堆,不共享棧,線程亦由操作系統(tǒng)調(diào)度(標準線程是的)。 協(xié)程和線程一樣共享堆,不共享棧,協(xié)程由程序員在協(xié)程的代碼里顯示調(diào)度。 協(xié)程和線程的區(qū)別是:協(xié)程避免了無意義的調(diào)度,由此可以提高性能,但也因此,程序員必須自己承擔調(diào)度的責任,同時,協(xié)程也失去了標準線程使用多CPU的能力。 打個比方吧,假設(shè)有一個操作系統(tǒng),是單核的,系統(tǒng)上沒有其他的程序需要運行,有兩個線程 A 和 B ,A 和 B 在單獨運行時都需要 10 秒來完成自己的任務(wù),而且任務(wù)都是運算操作,A B 之間也沒有競爭和共享數(shù)據(jù)的問題?,F(xiàn)在 A B 兩個線程并行,操作系統(tǒng)會不停的在 A B 兩個線程之間切換,達到一種偽并行的效果,假設(shè)切換的頻率是每秒一次,切換的成本是 0.1 秒(主要是棧切換),總共需要 20 19 * 0.1 = 21.9 秒。如果使用協(xié)程的方式,可以先運行協(xié)程 A ,A 結(jié)束的時候讓位給協(xié)程 B ,只發(fā)生一次切換,總時間是 20 1 * 0.1 = 20.1 秒。如果系統(tǒng)是雙核的,而且線程是標準線程,那么 A B 兩個線程就可以真并行,總時間只需要 10 秒,而協(xié)程的方案仍然需要 20.1 秒。 講講狀態(tài)機: 狀態(tài)機是有限狀態(tài)自動機的簡稱,是現(xiàn)實事物運行規(guī)則抽象而成的一個數(shù)學模型。一個狀態(tài)機至少要包含兩個狀態(tài)。例如一個自動門,就有 open 和 closed 兩種狀態(tài),我們通常所說的狀態(tài)機是有限狀態(tài)機,也就是被描述的事物的狀態(tài)的數(shù)量是有限個,例如自動門的狀態(tài)就是兩個 open 和 closed 。
closed 狀態(tài)下,如果讀取開門信號,那么狀態(tài)就會切換為 open 。open 狀態(tài)下如果讀取關(guān)門信號,狀態(tài)就會切換為 closed 。 狀態(tài)機的四大概念: 1 State ,狀態(tài)。一個狀態(tài)機至少要包含兩個狀態(tài)。例如上面自動門的例子,有 open 和 closed 兩個狀態(tài)。 2 Event ,事件。事件就是執(zhí)行某個操作的觸發(fā)條件或者口令。對于自動門,“按下開門按鈕”就是一個事件。 3 Action ,動作。事件發(fā)生以后要執(zhí)行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時候,一個 Action 一般就對應(yīng)一個函數(shù)。 4 Transition ,變換。也就是從一個狀態(tài)變化為另一個狀態(tài)。例如“開門過程”就是一個變換。 總結(jié)一下,狀態(tài)機不是實際機器設(shè)備,而是一個數(shù)學模型,通常體現(xiàn)為一個狀態(tài)轉(zhuǎn)換圖。涉及到的相關(guān)概念是 State 狀態(tài),Event 事件,Action 動作,Transition 轉(zhuǎn)換。狀態(tài)機是計算機科學的重要基礎(chǔ)概念之一,也可以說是一種總結(jié)歸納問題的思想,應(yīng)用范圍非常廣泛。 四、場景題1、現(xiàn)在打出的Android包啟動閃退,應(yīng)該怎么定位問題?使用ADB真機調(diào)試,通過日志定位問題。 延伸 講講ADB: ADB(Android Debug Bridge)是Android SDK中的一個工具, 使用ADB可以直接操作管理Android模擬器或者真實的Andriod設(shè)備。 ADB主要功能有: 1、在Android設(shè)備上運行Shell(命令行); 2、管理模擬器或設(shè)備的端口映射; 3、在計算機和設(shè)備之間上傳/下載文件; 4、將電腦上的本地APK軟件安裝至Android模擬器或設(shè)備上; 2、現(xiàn)在要開發(fā)一個點擊屏幕開炮發(fā)射子彈的功能,說下你的做法?首先把子彈進行抽象,把屬性和行為方法提煉出來,比如具有速度、威力、碰撞大小等屬性,具有飛行、碰撞和傷害等行為。 封裝子彈的抽象類,可以不繼承MonoBehaviour。 監(jiān)聽屏幕點擊事件,觸發(fā)開炮邏輯。子彈通過對象池管理,復用子彈,防止因為頻繁創(chuàng)建銷毀帶來的性能問題。另外,子彈的坐標更新,可以統(tǒng)一由一個彈道控制器的Update遍歷每個子彈對象來計算,而不是每個子彈都掛一個MonoBehaviour去更新,因為MonoBehaviour的Update是通過反射被調(diào)用的,如果有1000顆子彈,就會調(diào)用1000次反射,這樣性能上比較差。
|