Senior Unity Developer 面試題目分享
分享前陣子尋找新機會所遇到的 Unity 考題 ,十分有水準與微難度,事後回想並整理成此問題筆記。關於該機會的綜合心得放置在文章頁尾。
基本問題考題
在電話中直接詢問考題,回答需考慮精簡並提到要點。
什麼是 Coroutine,與 Thread 有什麼不同?
Coroutine 像是一個可執行暫停、中斷執行、等待其中作業的函式,由 Unity 所提供的機制,透過 yield return
將執行權交還給 Unity 主執行緒 (main thread),該機制並不是多重執行緒 (multithreading)。
Thread 執行緒,則是向 OS 底層要求資源建立,物理意義上可同時執行的機制 (in multi-CPU systems)。唯一要注意多執行緒處理存取相同資源時,得迴避 race condition 的發生。
更多參考資料:
AssetBundles 機制是什麼,如何打包 AssetBundles?
AssetBundles 為 Unity 所提供的資源打包機制 (archive),能將各式遊戲中的資源,例如貼圖 (textures)、動畫 (animations)、遊戲物件 (prefabs)、模型網格 (mesh)、或是設定檔 (scriptable object) 等等,打包成數多資源,類似於 7-zip 分多檔案打包並壓縮。
在 Unity 中使用 AssetBundleBuild
資料結構來描述如何打包,使用 BuildPipeline.BuildAssetBundles
來執行打包 AssetBundles。另外可選取位於 Project 的 assets,在 Inspector 設定對應的 AssetBundle tags,以利建立打包資訊。
或是使用官方或是第三方的圖形化工具,來協助編輯如何打包 AssetBundles,例如 Unity Asset Bundle Browser tool。
更多參考資料:
如果沒有載入 AssetBundle 依賴性資源 (dependency) 會發生什麼事情?
若 AssetBundle A 中的材質球依賴於 AssetBundle B 的貼圖,在正式發佈執行階段中 (runtime, not in editor),若僅載入 AssetBundle A 並使用該材質球,會發現該貼圖會是 missing 的狀態,其材質成像結果可能會呈現白色 (看 shader 實作)。
若是在編輯器模式下 (in editor),則要看專案內是否有存在該貼圖 (由 GUID 去尋找),若存在該貼圖則不會發生 missing 情況,反之亦然。
更多參考資料:
關於 ScriptableObject 以及 MonoBehaviour 有什麼差別?
ScriptableObject
描述專案中參數檔的結構,通常還需要在 Project 視窗下建立對應的 asset 才可以使用。
MonoBehaviour
則是描述組建的行為運作 (behavior),得依附在場景中的 GameObject 之下,例如描述 Awake
、Start
、或是 Update
等等行為,會由 Unity 在遊戲運行時呼叫。
更多參考資料:
- Unity Manual: ScriptableObject
- Unity Tutorials: Introduction to Scriptable Objects
- Unity ScriptReference: CreateAssetMenuAttribute
- Unity Manual: Creating and Using Scripts
- Unity ScriptReference: MonoBehaviour
關於 Testing 經驗?
被問到這個很尷尬,雖然知道 Unity 有提供 Testing framework,但是從來沒在寫 UnitTest 的啊,只能誠實回答面試官這個問題。(台灣有幾家遊戲公司有認真寫 UnitTest 呢?)
Unity 有提供 Unity Test Runner 可以使用。
更多參考資料:
關於 UI 如何減少 drawcalls?什麼是 FrameDebugger?
若是使用 Unity GUI,得考慮 UGUI 如何執行 Canvas renderer draw calls 的 bathing 機制,考慮 Sprite packing 貼圖合併 (texture atlas) 等機制。
FrameDebugger 則是 Unity 所提供的檢測工具,可將特定遊戲畫面的一幀的 draw calls 命令記錄下來,並重新 Unity 經過哪些 draw calls 完成該畫面的繪製,UGUI 效能優化少不了這個工具的使用。
更多參考資料:
如何檢測遊戲效能?
使用 Unity Profile 工具偵測檢查,確保 CPU 以及 GPU 在遊戲執行過程中,每幀不會花費 16.33 ms 以上,來維持穩定 FPS 60 的效能。
更多參考資料:
說明之前使用過的 design patterns?
這太多了,但自己都沒有再記名字⋯⋯例如 Singleton、Observer、Factory 等等。
更多參考資料:
什麼是 LINQ?
IEnumerable
的擴充函式庫,提供大量方便使用的函數使用,用類似於 SQL 的語法,從集合中取得過濾想要的資料。
不過因部份實作需大量配置記憶體,通常只會在寫 Editor 程式碼使用,不會在遊戲 runtime 使用。
更多參考資料: Microsoft Docs: Getting Started with LINQ in C#
什麼是 Lambda expression?
類似於匿名函數 (anonymous method) 的宣告語法,通常用於 LINQ 中。
更多參考資料: Microsoft Docs: Lambda Expressions (C# Programming Guide)
什麼是 GC,什麼時候被觸發?
C# 的經典考題。
當系統偵測到無足夠的記憶體時,或是遊戲要求記憶體但 heap 不足時,會自動觸發 GC。
或是透過 System.GC.Collect()
強迫觸發,通常會用在關卡切換或是載入資源後,釋放無用的記憶體,確保之後遊戲順利。
要注意的是當 GC 觸發會佔用許多 CPU 資源處理,可能會導致遊戲卡頓 (lag),遊戲優化項目之一,便是減少 runtime 執行下持續配置新的記憶體。
更多參考資料:
Class 與 Struct 有什麼不同?
C# 的經典考題。
主要差別在存取性與記憶體配置不同,Class 為 reference types,記憶體配置於 heap,而 Struct 為 value type,記憶體配置於 stack。
更多參考資料:
Streaming Asset & Resources 有何差異?
在遊戲建置時,會連同 Resources 資源一並打包近執行檔資源,遊戲起動時會將該些 Resources 載入進記憶體中,即使還沒有使用到。官方已不再建議使用 Resources 進行大檔案的資源載入 (prefabs, textures, etc…)。
Streaming Asset 則是在建置時,會使用一般的檔案系統來存放 (a folder),並且在 Unity 中能透過指定路徑來存取該些 assets,通常使用在能開放外部修改的大檔案資源,例如影片。
更多參考資料:
有寫過 Shader 的經驗嗎?
開放性問題。
有,cg language,不過不會是從零開始,不會特定從 vertex shader 或是 pixel shader 開始,會直接使用 Unity 已包裝的 surface shader 寫所需特效。會是更進一步,直接從 Unity 官方下載內建 shaders 原始碼進行修改。
更多參考資料:
- Unity 根據美術需求客製化 Sprite shader,基於官方 shader 開始改起
- Unity Manual: Materials, Shaders & Textures
- Unity Manual: Shader Reference
- Unity Manual: Writing Surface Shaders
程式碼考題
你認為以下程式碼能做哪些改進?
using UnityEngine;
public class RotateOnMouseDown : MonoBehaviour
{
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
transform.position += new Vector3(1f,0f,0f);
GetComponent<Renderer>().material.color = Color.red;
}
else
{
GetComponent<Renderer>().material.color = Color.white;
}
}
}
Class naming
類別名稱與行為不相同,應當重新取名。例如 TranslationOnMouseDown
,或是該 feature 的名稱。
Component caching
GetComponent
本身效率很差,最好在 Awake
或是 Start
就能取得該組建的 reference,並且儲存起來使用。
using UnityEngine;
public class TranslationOnMouseDown : MonoBehaviour
{
Renderer mainRenderer;
void Awake()
{
mainRenderer = GetComponent<Renderer>();
}
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
transform.position += new Vector3(1f,0f,0f);
mainRenderer.material.color = Color.red;
}
else
{
mainRenderer.material.color = Color.white;
}
}
}
Material creation
當第一次取得 renderer.material
時 (property get),Unity 內部會從 renderer.sharedMaterial
複製一份新的實體 (clone new instance),若場景中存在數千個該組件,這將十分浪費資源。
比較好的方式是根據該遊戲 feature,在 Project 建立按下材質球與一般材質球,根據需求直接替換該材質球,而非修改材質球的內部屬性,以避免複製建立新的材質球實體。
using UnityEngine;
public class TranslationOnMouseDown : MonoBehaviour
{
Renderer mainRenderer;
Material defaultMaterial;
[SerializeField]
Material toggleMaterial = null;
void Awake()
{
mainRenderer = GetComponent<Renderer>();
defaultMaterial = mainRenderer.sharedMaterial;
}
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
transform.position += new Vector3(1f,0f,0f);
mainRenderer.material = toggleMaterial;
}
else
{
mainRenderer.material = defaultMaterial;
}
}
}
Delta time
Update
進行物件操作都需要考慮 delta time,畢竟每幀所經過的時間,會依據效能等因素而不盡相同。為了達到平滑的位移,應該修改 position 的操作,考慮 delta time:
transform.position += new Vector3(1f * Time.deltaTime,0f,0f);
或甚至將數值提取,建立速度屬性:
using UnityEngine;
public class TranslationOnMouseDown : MonoBehaviour
{
[SerializeField]
Vector3 velocity = new Vector3(1f,0f,0f);
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
transform.position += velocity * Time.deltaTime;
}
}
}
小專案測試
給予一個賽道場景完成以下事項,在編輯器中:
- 建立封閉式賽道編輯器
- 可新增或是移除節點 (nodes)
- 可移動節點座標位置
- 可在邊其中顯示路徑 (path)
- 能持續更新節點的序列化資料 (keep update of serialized data)
在遊戲執行中 (in runtime):
- 從 streaming folder 讀取 json 檔案,並將該設定資料與玩家資料放置在記憶體中
{ "GameConfiguration": { "lapsNumber": "4", “playersInstantiationMillisecondsDelay": "1000" }, "Players": [ { “Name”: "Kika", "Velocity": "16", “Color": "#ffffff", "UIIcon": "https://example.com/car.png" }, ... ] }
- 隨機從玩家列表中 (
Player
) 取得八名玩家參賽,由速度 (Velocity
) 最慢到最高排序,並且每playersInstantiationMillisecondsDelay
毫秒在賽道中生成 (instantiation) - 每輛玩家車輛必須以等速 (uniform velocity) 在賽道中移動,必須由物理引擎 (unity physics engine) 控制,並能與賽道物理碰撞
- 實作一個簡單的 AI,使得每輛玩家車輛能迴避與其他車輛相撞
- 在賽事過程中,提供排名 UI 資訊,以每輛玩家車輛位置進行排序,並能提供以下資訊顯示
- 玩家名稱
- 總進度 (total laps progress)
- 當有玩家車輛完成
lapsNumber
圈數的賽道後,應當跳出 UI 視窗顯示勝利者資訊,操作者可關閉該視窗並重新啟動新的一場賽事 (new match)
小專案評分標準
- 程式碼的品質與易讀性
- 良好的類別設計與系統設計
- 在手機上的執行效能
- 任何的註解或是與 3D 美術溝通的文件
微微心得
這是在倫敦 (London) 所獲得的機會,先是電話訪問 (phone interview),再來是小專案技術測驗 (technical test),最後才是面對面面試 (face to face interview)。只是因為已獲得其他 offer,最終婉拒完成該小專案以及之後的招聘流程。
雖然在台灣並沒有太多的 Unity 的面試考試經驗,但覺得這些題目覺得相當精實,都是一位資深 Unity 工程師所需要知道的,小專案則希望得在四天內完成。但對我最大的挑戰莫過於是全英文的電話對談,有好幾次不太明白面試官的問題,還需要請對方重新換個方式問一次,
相較於此 Unity 工程師機會 (from Product Madness),反倒覺得 Backend 工程師的問題或是專案還比較容易些呢⋯⋯。
沒有留言: