06/21/2020
UE4源码分析的起点
UE4文件结构
现在UE4游戏引擎现在都很简单,可以从Epic Games Launcher启动器中直接下载不同版本的UE4。我下载了版本是UE_4.24版本,双击打开文件夹,里面的结构是见图
FeaturePacks,Samples和Templates是由UE4制作的一些模板和样本,比如有第三人称设计游戏等等,我们主要关注Engine文件夹里面内容
打开Engine文件夹
UE4 我们都知道是开源的,源码放在Source文件夹中,游戏引擎还提供了做好的游戏资源给我们,比如材质,纹理和模型,放在了Content里面。打开Source文件夹
这里面包含了UE4游戏引擎的源码,主要分为5大内容,有Developer,Editor,Programs,Runtime,ThirdParty,游戏引擎核心代码放在Runtime中,相当于main的启动过程,和初始窗口和显卡部分,因为UE4是跨平台的,各个平台之间肯定有不同的地方。
打开UE4,创建一个C++项目。
UE4 Editor
当项目创建启动,它会默认打开默认的IDE,我设置的是VS2019,进而打开了UE4游戏引擎编辑器(Editor)窗口
U
UE4 C++ 项目文件夹结构
我使用VS2019打开MyProjct.sln文件,也可以开启编译并显示处UE4Editor可视化窗口
UE4 源码分析的起点
再一次看到UE4Engine文件夹,我们就可以从这里开始阅读UE4源码
项目中的main函数
从上述项目中MyProject2里面Source文件夹中会先运行MyProject2.cpp 里面的函数再运行MyProject2GameModeBase.cpp
//MyProject2.h
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, MyProject2, "MyProject2" );
//MyProject2GameModeBase.h
UCLASS() class MYPROJECT2_API AMyProject2GameModeBase : public AGameModeBase
{
GENERATED_BODY()
};
编译游戏主要模块,然后初始化Editor并加载游戏模块。
跨平台(Main函数)
最近使用Direct3D在windows下完成了一款简易游戏,但现在我需要把这个游戏跨平台到PS4上,所以我需要了解一些跨平台的知识。在Engine/Source/Runtime/Lanuch/Lanuch.cpp中有关如何启动不同平台的main函数 - GuardedMain
查看Lanuch.cpp 找到GuardMain函数
这里有关于如何区分Windows和其他平台的方法,用的是宏定义,Windows就是和其他平台不一样,单独需要有一个main函数呢
跨平台方法之一(宏定义if-else)
//Launch.h ------ Main 函数
#if PLATFORM_WINDOWS
int32 GuardedMain(const TCHAR* CmdLine,/*....HINSTANCE hInInstance ...*/) //绕不开的HINSTANCE
#else
int32 GuardedMain(const TCHAR* CmdLine,/*....*/)
#endif
{
/*
Debug
FileManager - log
*/
//-----------
EnginePreInit(CmdLine);
#if WITH_EDITOR
if (GIsEditor)
{
ErrorLevel = EditorInit(GEngineLoop);
}
else
#endif
{
ErrorLevel = EngineInit();
}
}
// 时间开始 ----游戏开始
while( !IsEngineExitRequested() )
{
EngineTick(); //FEngineLoop GEngineLoop.Tick();
}
}
IEngineLoop 和FEngineLoop
开始循环UE4游戏引擎和启动UE4Editor界面
//UnrealEngin.h -----------EngineLoop 游戏死循环
class IEngineLoop
{
public:
virtual int32 Init() = 0; //初始化
virtual void Tick() = 0; //游戏时间
virtual void ClearPendingCleanupObjects() = 0;
};
//LaunchEngineLoop.h ----------------全局类
class FEngineLoop:public IEngineLoop
{
public:
/**
* Pre-Initialize the main loop, and generates the commandline from standard ArgC/ArgV from main().
*
* @param ArgC The number of strings in ArgV.
* @param ArgV The command line parameters (ArgV[0] is expected to be the executable name).
* @param AdditionalCommandLine Optional string to append to the command line (after ArgV is put together).
* @return Returns the error level, 0 if successful and > 0 if there were errors.
*/
int32 PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline = nullptr); //main函数的形参
/**
* Pre-Initialize the main loop - parse command line, sets up GIsEditor, etc.
*
* @param CmdLine The command line.
* @return The error level; 0 if successful, > 0 if there were errors.
*/
int32 PreInit(const TCHAR* CmdLine);
/** First part of PreInit. */
int32 PreInitPreStartupScreen(const TCHAR* CmdLine);
/** Second part of PreInit. */
int32 PreInitPostStartupScreen(const TCHAR* CmdLine);
/** Load all modules needed before Init. */
void LoadPreInitModules();
/** Load core modules. */
bool LoadCoreModules();
#if WITH_ENGINE
/** Load all core modules needed at startup time. */
bool LoadStartupCoreModules();
/** Load all modules needed at startup time. */
bool LoadStartupModules();
/**
* Initialize the main loop (the rest of the initialization).
*
* @return The error level; 0 if successful, > 0 if there were errors.
*/
virtual int32 Init() override;
/** Initialize the timing options from the command line. */
void InitTime();
/** Performs shut down. */
void Exit();
/** Whether the engine should operate in an idle mode that uses no CPU or GPU time. */
bool ShouldUseIdleMode() const;
/** Advances the main loop. */
virtual void Tick() override;
/** Removes references to any objects pending cleanup by deleting them. */
virtual void ClearPendingCleanupObjects() override;
#endif // WITH_ENGINE
/** RHI post-init initialization */
static void PostInitRHI();
/** Pre-init HMD device (if necessary). */
static void PreInitHMDDevice();
public:
/** Initializes the application. */
static bool AppInit();
/**
* Prepares the application for shutdown.
*
* This function is called from within guarded exit code, only during non-error exits.
*/
static void AppPreExit();
/**
* Shuts down the application.
*
* This function called outside guarded exit code, during all exits (including error exits).
*/
static void AppExit();
};
- FEngineLoop 类基本是初始化各个资源,比如窗口,显卡,时间和应用程序
- 基本执行顺序FEngineLoop ---> Editor初始化 ----->时间计算 --- > Tick函数(还不是游戏时间的时间)
FEngineLoop 的核心函数-Tick函数
//LanuchEngineLoop.cpp 是一个超级长的函数
void FEngineLoop::Tick()
{
//.....
//set FApp::CurrentTime, FApp::DeltaTime .... 应用程序的时间,游戏世界的时间
GEngine->UpdateTimeAndHandleMaxTickRate();
//.....
//Beginning of RHI frame
ENQUEUE_RENDER_COMMAND(BeginFrame)([CurrentFrameCounter](FRHICommandListImmediate& RHICmdList)
{
BeginFrameRenderThread(RHICmdList, CurrentFrameCounter);
});
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
UWorld* CurrentWorld = Context.World();
if (CurrentWorld)
{
FSceneInterface* Scene = CurrentWorld->Scene;
ENQUEUE_RENDER_COMMAND(SceneStartFrame)([Scene](FRHICommandListImmediate& RHICmdList)
{
Scene->StartFrame();
});
}
}
//
//.....
CalculateFPSTiming(); //FPS/MS
GEidtor->PlayWorld; // Editor 有一个Play的按键
//main game engine tick (world,game objects ....)
GEngine->Tick(FApp::GetDeltaTime,bIdleMode);
//...
UGameEngine* GameEngine = Cast<UGameEngine>(GEngine); //游戏引擎的开始
//....
}
UEngine类
- UEngine是一个全局的实例
- 继承了UObject和FExec
//Engine.h
/**
* Abstract base class of all Engine classes, responsible for management of systems critical to editor or game systems.
* Also defines default classes for certain engine systems.
*/
class ENGINE_API UEngine
: public UObject
, public FExec
{
//....
/** A UObject spawned at initialization time to handle game-specific data */
UPROPERTY()
UObject *GameSingleton;
//Texture Font Asset Material ...
//..
/** Initialize the game engine. */
virtual void Init(IEngineLoop* InEngineLoop);
/** Start the game, separate from the initialize call to allow for post initialize configuration before the game starts. */
virtual void Start();
//....
}
/** Global engine pointer. Can be 0 so don't use without checking. */
extern ENGINE_API class UEngine* GEngine;
UGameEngine 游戏的核心
UGameEngine是UEngine的一个子类,游戏的框架从这里开始
/**
* Engine that manages core systems that enable a game.
*/
class ENGINE_API UGameEngine
: public UEngine
{
// UEngine interface
virtual void Init(class IEngineLoop* InEngineLoop) override;
virtual void Start() override;
virtual void PreExit() override;
virtual void Tick( float DeltaSeconds, bool bIdleMode ) override;
virtual float GetMaxTickRate( float DeltaTime, bool bAllowFrameRateSmoothing = true ) const override;
virtual void ProcessToggleFreezeCommand( UWorld* InWorld ) override;
virtual void ProcessToggleFreezeStreamingCommand( UWorld* InWorld ) override;
virtual bool NetworkRemapPath(UNetDriver* Driver, FString& Str, bool bReading = true) override;
virtual bool ShouldDoAsyncEndOfFrameTasks() const override;
}
总结
UE4自己首先需要加载模块,并且初始化自己UE4引擎循环和编译器界面,当你点击Play按钮之后,会触发UGameEngine来开始游戏的循环。
- 针对问题去了解游戏引擎,比如这篇针对的是关于UE4如何跨平台和循环游戏的
- To be continued