今天先來補充一下關于Unity和UE的一些問題,后續開始深挖項目:
Unity
關于fixed update和update:同一幀中物理更新優先執行?
關于協程:
協程是基于迭代器實現的,而迭代器是基于狀態機實現的。協程的本質是編譯器將?yield
?語法轉化為狀態機類,Unity 通過迭代器接口在幀生命周期中調度其執行,從而實現單線程上的異步編程體驗。
void Start() {Debug.Log("【1】Start開始"); StartCoroutine(MyCoroutine()); // 啟動協程Debug.Log("【3】Start結束"); // ? 協程掛起后立即執行!
}IEnumerator MyCoroutine() {Debug.Log("【2】協程開始"); // ? 同步執行yield return new WaitForSeconds(2); // ?? 掛起點Debug.Log("【4】2秒后恢復執行"); // ? 等待2秒后觸發
}
這就是一個協程運作的基本流程,我們通過StartCoroutine啟動協程,協程正常運作到yield return之前都一切正常,遇到yield return之后我們會把協程掛起,然后這個時候我們會回到start函數中繼續執行后續的邏輯,直到協程從掛起恢復后再執行協程后續的內容。
關于Resources,AB包和Addressable:
對于后兩種方案來說,AB包和Addressables加載的資源是無法在Unity的構建過程中進行構建的,因為這些資源會被Unity識別為外部依賴,Unity的Build只會去構建Asset中的資源。
Unity必需的文件夾:之前也提到過了,Asset,Library,ProjectSettings,以及如果有第三方插件所需要的Packages文件夾。Unity 的資源構建過程嚴格限定在項目的?Assets
?目錄內,無論采用何種打包策略,最終構建的內容均源于此目錄下的資源。在開發環境中,Assets
?文件夾存儲所有原始資源文件(如模型、貼圖、音頻),Library
?文件夾由 Unity 自動生成,用于緩存導入后的中間數據(如?.meta
?文件和優化后的資源副本),而?Project Settings
?則保存項目的全局配置(如物理參數、渲染設置)。若項目包含第三方插件,通常會被置于?Packages
?目錄(本質是?Assets
?的子集),同樣參與構建。
關于LOD和Mipmap:LOD(Level of Detail)和Mipmap是Unity中兩類核心的渲染優化技術,它們均通過動態調整資源精度來平衡畫質與性能。
關于固定幀率模式和追趕幀率模式:
固定幀率模式其核心是通過主動休眠強制延長幀時間,確保每幀耗時嚴格匹配目標幀率要求。例如目標為60FPS(每幀16.67ms),若某幀邏輯僅耗時5ms,系統會調用?Thread.Sleep()
?或?WaitForSeconds()
?休眠剩余11.67ms,再執行下一幀。
當幀耗時超過目標時間?(如目標16.67ms但實際耗時25ms),系統會跳過當前幀的渲染階段,直接進入下一幀的輸入處理與邏輯更新,通過多次執行邏輯更新(如連續調用?Update()
)消化累積的時間延遲。例如連續兩幀超時后,第三幀若未超時則渲染最新邏輯狀態。
關于UGUI如何觸發事件:本質上就是基于射線檢測實現的,UGUI會在鼠標點擊的位置發射一條射線,如果檢測到UI元素就會觸發綁定的事件。
如何創建一個有限狀態機:
針對不同需求的狀態機大體上有兩種實現方法:
在實現簡單狀態機時,我們首先定義一個枚舉類型來明確所有可能的狀態(如Idle
、Walk
),然后在狀態機類中聲明一個當前狀態變量(currentState
),通過Update()
方法內的switch
語句根據當前狀態執行對應邏輯;在每個case
分支中,我們直接編寫狀態轉換的條件判斷(例如檢測輸入事件或條件滿足),一旦條件成立就立即更新currentState
的值,從而在下一幀切換到新狀態。這種方法將狀態行為與轉換邏輯集中在一個類中,適合狀態數量少(≤5個)且邏輯不復雜的場景,但擴展性較差,狀態增多后代碼會臃腫。
對于復雜狀態機,我們采用狀態模式:先定義一個基礎狀態接口(如IState
),要求所有具體狀態類實現Enter()
、Update()
、Exit()
三個方法;接著為每個狀態(如IdleState
、WalkState
)創建獨立類,在這些類的Update()
方法內檢測轉換條件(如按鍵事件),并直接調用狀態機類的ChangeState()
方法(需持有狀態機引用)觸發切換;狀態機類負責管理當前狀態(private IState currentState
),在ChangeState()
中依次執行舊狀態的Exit()
、更新狀態引用、新狀態的Enter()
,同時通過自身的Update()
驅動當前狀態的Update()
。最終,在角色控制器中初始化狀態機并設置初始狀態,每幀調用狀態機的Update()
即可完成閉環。這種方式將狀態邏輯分散到各狀態類,通過多態實現動態行為分發,支持高擴展性和維護性,適用于狀態多或邏輯復雜的系統。
實現了三個Update的驅動。
OnPreProcessTexture和OnPostProcessTexture的作用:一句話總結就是前者是導入紋理前執行的方法后者是導入紋理后執行的方法。
AssetModificationProcessor是什么?這是Unity提供的一個編輯器類,用于監聽資源在編輯器中的操作?(如創建、刪除、移動),而非導入流程。其核心方法包括:
?OnWillCreateAsset
:資源創建前觸發。
?OnWillDeleteAsset
:資源刪除前觸發。
?OnWillMoveAsset
:資源移動前觸發。
如果要使用這個類中的方法只需要去繼承這個類然后重寫調用相關的函數即可。
AB包底層原理:
AB包本質是一種平臺特定的二進制壓縮文件,用于存儲Unity資源(如模型、貼圖、材質、預制體、音效等)。它通過將資源打包成獨立于應用安裝包的二進制文件,實現資源的動態加載與更新。
這里補充一下Resources,我們總說盡量少使用這個文件,但具體為什么呢?Unity 在構建項目時,會將所有?Resources
?文件夾內的資源整合為一個序列化文件(resources.assets
),并生成一個紅黑樹作為索引數據結構。游戲啟動時,Unity 會完整加載整個紅黑樹索引到內存中,且該索引不可卸載,會持續占用內存直至游戲結束,紅黑樹本身占據的內存就不小,且構建和維護紅黑樹也要很多時間,所以如果Resources中的文件過多,會導致游戲加載卡頓。
在Unity中打包AB包時,首先需要在編輯器中為資源手動標記所屬的包名(如將角色模型標記為characters/hero
),相同包名的資源會被合并打包;接著選擇壓縮格式——LZMA適合最小化包體但需整體解壓,LZ4則支持按需加載局部資源,更推薦用于平衡性能與體積;打包過程中Unity會自動分析資源間的引用關系(如多個模型共享的材質),將公共依賴提取到獨立AB包避免冗余,最終通過BuildPipeline.BuildAssetBundles()
生成二進制文件及記錄依賴鏈的主清單文件,此過程必須指定目標平臺(如Android/iOS)以確保兼容性。
在加載與卸載AB包時,若資源在本地存儲(如StreamingAssets),可通過AssetBundle.LoadFromFile()
同步加載或LoadFromFileAsync
異步加載,后者避免主線程阻塞;加載后AB包數據暫存于內存鏡像區?(壓縮態),需調用LoadAsset()
解壓到活動內存才能實例化使用;關鍵的是必須通過主清單遞歸加載所有依賴包(如材質包),否則資源引用失效(如模型變粉);卸載時若調用Unload(false)
僅釋放內存鏡像區的壓縮數據,已加載資源保留(需后續手動管理),而Unload(true)
則強制卸載所有關聯資源,但若場景物體仍引用這些資源會導致材質丟失或報錯,因此最佳實踐是在場景切換時先Unload(false)
釋放AB包,再調用Resources.UnloadUnusedAssets()
清理殘留資源實例。
UE
UE必需的文件?
- 項目名.uproject?-?項目的主配置文件,定義項目信息和模塊結構
- Content/?-?存儲所有游戲資源(藍圖、材質、貼圖、網格、音頻等),這是最重要的目錄
- Config/?- 包含引擎配置文件(DefaultEngine.ini、DefaultGame.ini、DefaultInput.ini等)
- Source/?- C++源代碼目錄(僅C++項目需要),包含模塊文件和構建配置
這四個文件是必須的,還會額外生成諸如Binaries(編譯輸出)、Intermediate(中間文件)、Saved(日志和用戶數據)、DerivedDataCache(派生數據緩存)這些文件。
Binaries/存儲編譯后的可執行文件和庫文件,當你雙擊.uproject文件或運行項目時,引擎會讀取這個文件夾中的.exe和.dll文件來啟動游戲程序;Intermediate/存儲編譯過程中的臨時文件,當你在Visual?Studio中編譯C++代碼時,編譯器會在這里生成.obj文件和預編譯頭文件,這些是編譯的中間產物;Saved/存儲運行時和編輯器生成的數據,當你需要查看項目運行日志、調試信息、性能分析數據或用戶配置時,就會在這個文件夾中查找相應的文件;DerivedDataCache/存儲引擎處理后的資產數據,當你導入貼圖、音頻等原始資產時,引擎會進行壓縮、優化等處理,處理后的數據就緩存在這里,下次加載相同資產時直接讀取緩存,避免重復處理以提升加載速度。
UE的資產管理系統?
虛幻引擎的資產管理系統基于UObject和反射機制構建,每個資產都有唯一GUID標識并通過FAssetRegistry全局注冊管理。系統采用異步加載機制,通過UAssetManager實現按需加載和內存優化,同時維護派生數據緩存(DDC)來存儲處理后的資產數據以提升加載速度。所有資產都繼承自UObject,通過序列化系統保存為.uasset文件,支持版本控制和熱重載。系統還提供完整的依賴關系追蹤,當父資產更新時會自動觸發相關子資產的重新編譯,并通過反射機制實現與藍圖系統和編輯器的無縫集成。
虛幻引擎的派生數據緩存(Derived Data Cache)是什么?
虛幻引擎的派生數據緩存(DDC)是一個本地緩存系統,用于存儲引擎處理過的資產數據。當引擎導入原始資產(如貼圖、音頻、模型)時,會進行各種處理操作(壓縮、優化、編譯等),這些處理后的數據就存儲在DDC中。主要的作用在于避免重復處理,大幅提升加載速度。比如一張高分辨率貼圖第一次導入時需要壓縮和生成mipmap,處理完成后會緩存到DDC,下次加載時直接讀取緩存數據,無需重新處理。
什么是藍圖?它的主要用途是什么?
藍圖是虛幻引擎的可視化編程系統,允許開發者通過節點式圖形界面來創建游戲邏輯,而無需編寫傳統代碼。藍圖采用節點連接的方式,每個節點代表一個函數或變量,通過連線定義數據流和控制流,支持事件驅動編程、函數封裝、類繼承等面向對象概念。
藍圖有哪些類型?和C++的關系是什么?性能特點是什么?
主要包括:Level Blueprint(關卡藍圖,控制關卡級別的邏輯)、Class?Blueprint(類藍圖,創建可重用的游戲對象)、Function Library(函數庫,封裝可重用的函數)、Interface(接口藍圖,定義通用接口)、Widget?Blueprint(UI控件藍圖)、Animation Blueprint(動畫藍圖,控制角色動畫)。
藍圖可以繼承C++類,C++可以調用藍圖函數,兩者可以相互通信。藍圖實際上是C++類的可視化包裝,編譯后生成C++代碼,支持熱重載(修改后無需重啟編輯器)。
藍圖是解釋執行的,性能相對C++較低,但開發效率高。藍圖代碼會被編譯成字節碼,在運行時由虛擬機執行,對于大部分游戲邏輯來說性能足夠,但對于計算密集型的操作建議使用C++。
藍圖中的事件圖表和函數圖表有什么區別?
事件圖表(Event?Graph)是藍圖的主要執行入口,處理各種事件觸發,比如游戲開始、按鍵輸入、碰撞檢測、定時器等。事件圖表中的節點通常以事件開始(如Event?BeginPlay、Event?Tick),然后連接執行邏輯,是藍圖程序的控制流程;函數圖表(Function?Graph)用于封裝可重用的邏輯代碼,類似于傳統編程中的函數。你可以在函數圖表中創建自定義函數,然后在事件圖表或其他地方調用這些函數,實現代碼復用和模塊化設計。
C++的代碼具體是如何演變到藍圖的?
C++類通過反射系統暴露給藍圖。使用UCLASS()宏標記類,UPROPERTY()宏暴露屬性,UFUNCTION()宏暴露函數,UFUNCTION(BlueprintCallable)讓藍圖可以調用C++函數,UFUNCTION(BlueprintImplementableEvent)讓C++可以調用藍圖函數。編譯器掃描這些宏生成元數據,存儲在UClass中,藍圖編輯器讀取這些元數據生成可視化節點。當你在藍圖中調用C++函數時,實際上是調用生成的包裝函數,該函數通過反射系統調用真正的C++實現。
UE定義宏?→?編譯器掃描宏?→?生成元數據?→?反射系統讀取元數據?→?藍圖可視化
具體來說,虛幻引擎定義了自己的宏(如UCLASS、UPROPERTY、UFUNCTION),當編譯器掃描到這些宏時,會生成額外的元數據代碼,包含類的結構、屬性、函數等信息。然后引擎的反射系統讀取這些元數據,將C++類的信息提供給藍圖編輯器,藍圖編輯器就能將這些C++類轉換為可視化的節點和界面,讓開發者可以通過圖形化方式調用C++代碼。
虛幻引擎的內存管理機制是什么?
虛幻引擎的內存管理機制基于垃圾回收(Garbage Collection)和智能指針系統構建。
所有繼承自UObject的對象都參與垃圾回收,引擎會定期掃描對象引用關系,自動釋放不再被引用的對象。UObject使用引用計數和標記清除算法,當對象沒有強引用時會被自動回收。UE提供TSharedPtr、TWeakPtr、TUniquePtr等智能指針,自動管理內存生命周期,避免內存泄漏。TSharedPtr提供共享所有權,TWeakPtr提供弱引用(不阻止垃圾回收),TUniquePtr提供獨占所有權。(類C++智能指針)
UE中的類繼承關系?
UObject是所有UE對象的根基類,提供垃圾回收、序列化、反射等核心功能。AActor繼承自UObject,是游戲世界中可放置對象的基類,增加了位置、旋轉、生命周期管理等特性。UComponent繼承自UObject,是功能組件的基類,不能獨立存在。APawn繼承自AActor,是玩家控制的對象的基類。ACharacter繼承自APawn,是角色類的基類,增加了角色特有的功能。AController繼承自AActor,是控制器的基類,負責控制Pawn的行為。UClass、UFunction、UProperty等繼承自UObject,是反射系統的工具類。UActorComponent繼承自UObject,是附加到Actor上的功能組件。USceneComponent繼承自UActorComponent,是具有空間變換功能的組件基類。
UWorld是什么?如何理解虛幻引擎中的關卡和世界的概念?
虛幻引擎中的UWorld是游戲世界的容器和管理者,代表一個完整的游戲世界,負責管理所有Actor對象、物理世界、音頻系統、網絡復制等各個子系統。關卡(Level)是UWorld中的子集,對應編輯器中的.umap文件,代表具體的游戲場景或區域。一個UWorld可以包含多個關卡,通過Level?Streaming系統動態加載和卸載關卡。項目文件(.uproject)對應一個完整的游戲項目,可以包含多個不同的游戲世界,每個世界通過GameMode定義游戲規則,通過關卡流送管理多個關卡的組合。GameInstance作為全局單例管理整個游戲的生命周期,而World?Settings在關卡中設置世界級別的參數。簡單來說,UWorld是"游戲世界的管理者",關卡是"具體的游戲區域",項目是"包含多個世界的容器"。
如何在C++中創建自定義UE中可使用的組件?
在虛幻引擎中,如果你想通過C++編寫自定義類并讓藍圖系統能夠識別和使用,就必須使用UE提供的反射宏系統。具體來說,你需要使用UCLASS()宏來標記類,添加GENERATED_BODY()宏讓代碼生成系統插入必要的代碼,然后根據需求選擇性使用UPROPERTY()宏暴露屬性給藍圖,使用UFUNCTION()宏暴露函數給藍圖。對于自定義組件,你需要繼承UActorComponent類,使用UCLASS()宏標記,添加GENERATED_BODY(),重寫必要的生命周期函數(如BeginPlay、Tick等),并根據需要添加UPROPERTY()和UFUNCTION()宏來暴露特定的屬性和函數。這樣通過UE的反射系統,你的C++代碼就能被藍圖系統識別和使用,實現C++和藍圖的無縫集成。
虛幻引擎的委托系統是什么?
虛幻引擎的委托系統包含Delegate(單播委托)、Multicast Delegate(多播委托)、Event(事件)和Dynamic Delegate(動態委托),底層原理是對函數指針的封裝,使用模板和反射系統實現,當委托被觸發時引擎會調用所有綁定的函數并支持參數傳遞和返回值處理。使用方法包括使用DECLARE_DELEGATE等宏聲明委托類型,使用Bind()、AddDynamic()等方法綁定函數到委托,使用Execute()、Broadcast()等方法觸發委托,使用Unbind()、RemoveDynamic()等方法解綁函數。權限控制方面,普通委托可以在任何地方觸發,Event只能在聲明它的類內部觸發但外部可以綁定解綁,Dynamic Delegate專門用于藍圖集成支持在藍圖中綁定和觸發。實際應用常用于事件通知、回調處理、UI交互、游戲邏輯解耦等場景,實現對象間的松耦合通信。
哎喲我,不行了,今天下午做了個筆試,那編程題的IDE不支持cout的,我頂不住了,有點累有點累今天,先到這,明早繼續。