dddd

发布时间 2023-05-26 13:59:29作者: tomato-haha

06/21/2020

UE4文件结构

现在UE4游戏引擎现在都很简单,可以从Epic Games Launcher启动器中直接下载不同版本的UE4。我下载了版本是UE_4.24版本,双击打开文件夹,里面的结构是见图
UE_4,24文件结构
FeaturePacks,Samples和Templates是由UE4制作的一些模板和样本,比如有第三人称设计游戏等等,我们主要关注Engine文件夹里面内容
打开Engine文件夹
UE_4.24Engine文件夹
UE4 我们都知道是开源的,源码放在Source文件夹中,游戏引擎还提供了做好的游戏资源给我们,比如材质,纹理和模型,放在了Content里面。打开Source文件夹
UE.4_24Engine下Source文件夹内容
这里面包含了UE4游戏引擎的源码,主要分为5大内容,有Developer,Editor,Programs,Runtime,ThirdParty,游戏引擎核心代码放在Runtime中,相当于main的启动过程,和初始窗口和显卡部分,因为UE4是跨平台的,各个平台之间肯定有不同的地方。
打开UE4,创建一个C++项目。

UE4 Editor

UE4C++创建
当项目创建启动,它会默认打开默认的IDE,我设置的是VS2019,进而打开了UE4游戏引擎编辑器(Editor)窗口
U
UE4Editor

UE4 C++ 项目文件夹结构

UE4.24_C++MyProject文件夹
我使用VS2019打开MyProjct.sln文件,也可以开启编译并显示处UE4Editor可视化窗口

UE4 源码分析的起点

UE4Engine
再一次看到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()
	
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

UObject 继承树1

AGameModeBase继承树

注意:UObject属于比较上层的,而自己创建的项目是AGameModeBase

Gameplay类:Objects,Actors,Componets

UObject继承图

  父类 Class UObject
  	一级派生Class UActorComponent 角色组件
  		子类有:
  		1.Class UInputComponent 输入组件
  	 	2.Class UMovementComponent 移动组件
   	 	3.Class USceneComponent 场景组件
   	二级派生Class USceneComponent 包含Transform
   		子类有:
    	1.Class UPost ProcesComponent 处理效果
    三级派生Class UPrimitiveComponent 图源(Render 渲染 Physical 物理计算)
    	子类有:
  		1. Class UMeshComponent 网格组件
        	(1).Class UStatic MeshComponent 静态网格组件
            	a.Class UStatic Mesh 网格实例

        	(2).Class USkinned MeshComponent 皮肤网格组件
            	b.Class USkeleta MeshComponent 骨骼网格组件
                	b1.Class USkeleta Mesh 骨骼实例

   		2.Class UBrushComponent 笔刷组件
    	3.Class ULandscapeComponent 场景组件
      	4.Class ULightComponent 灯光组件
        	(1).Class ULightComponent Base 灯光大类组件
            	a.Class ULightComponent 光源组件
                	a1.Class UDirect LightComponent 平行光组件
                	a2.Class UPoint LightComponent 点光源组件
                    	a2_1.Class USpot LightComponent 射光源组件
            	b.Class USky LightComponent 天空光组件

    	5.Class UShapeComponent 形状组件
        	(1).Class UBoxComponent 正方体组件
        	(2).Class UCapsuleComponent 胶囊组件
        	(3).Class USphereComponent 圆柱组件

    	6.Class UAudioComponent 音频组件
    	7.Class UCameraComponent 摄像头组件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Launch文件夹:引擎的起点

不同平台的Main函数

最近使用Direct3D在windows下完成了一款简易游戏,但现在我需要把这个游戏跨平台到PS4上,所以我需要了解一些跨平台的知识。在Engine/Source/Runtime/Lanuch/Lanuch.cpp中有关如何启动不同平台的main函数 - GuardedMain
UE4_Lanuch文件夹
查看Lanuch.cpp 找到GuardMain函数
UE4_Launch文件
这里有关于如何区分Windows和其他平台的方法,用的是宏定义,Windows就是和其他平台不一样,单独需要有一个main函数呢

注意:Launch文件夹表示引擎的起点

跨平台方法之一(宏定义if-else-endif)

//Launch.h   ------ Main 函数
#if PLATFORM_WINDOWS
int32 GuardedMain(const TCHAR* CmdLine,/*....HINSTANCE hInInstance ...*/) //绕不开的HINSTANCE
#else
int32 GuardedMain(const TCHAR* CmdLine,/*....*/)
#endif
{

//....
EnginePreInit(CmdLine); 
#if WITH_EDITOR
		if (GIsEditor)
		{
			ErrorLevel = EditorInit(GEngineLoop);
		}
		else
#endif
		{
			ErrorLevel = EngineInit();
		}
	}
// 死循环的开始 ---- UE4引擎开始
while( !IsEngineExitRequested() )
	{
		EngineTick();  //FEngineLoop GEngineLoop.Tick();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

IEngineLoop 和FEngineLoop:UE4引擎的开始

开始循环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

	/**
	 * 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();
	/** Advances the main loop. */
	virtual void Tick() 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();
	
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • FEngineLoop 类基本是初始化各个资源,比如窗口,显卡,时间和应用程序
  • FEngineLoop是一个全局的实例,最为UE4引擎的主循环
  • 基本执行顺序FEngineLoop —> Editor初始化 ----->时间计算 — > Tick函数(不是游戏的循环)

FEngineLoop 的核心函数-Tick函数

以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); //游戏引擎的开始
	//....
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

UEngine类:游戏世界引擎开始

  • UEngine是一个全局的实例
  • 继承了UObject和FExec
    • 里面有UObject的实例:UObject *GameSingleton;
  • 游戏世界引擎的开始
//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();
	//...

	///** Update everything. */
	virtual void Tick( float DeltaSeconds, bool bIdleMode ) PURE_VIRTUAL(UEngine::Tick,);
}
/** Global engine pointer. Can be 0 so don't use without checking. */
extern ENGINE_API class UEngine*			GEngine;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

注意:UEngine是一个全局的实例

UGameEngine 游戏的核心

UGameEngine是UEngine的一个子类,游戏的框架从这里开始

/**
 * Engine that manages core systems that enable a game.
 */
class ENGINE_API UGameEngine
	: public UEngine
{
	virtual void Init(class IEngineLoop* InEngineLoop) override;
	virtual void Start() override;
	virtual void PreExit() override;
	virtual void Tick( float DeltaSeconds, bool bIdleMode ) override;  // 核心 update
	//...

	/**
	 * This is a global, parameterless function used by the online subsystem modules.
	 * It should never be used in gamecode - instead use the appropriate world context function 
	 * in order to properly support multiple concurrent UWorlds.
	 */
	UWorld* GetGameWorld();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • Tick函数代表游戏循环
  • UGameEngine继承了UEngine,UEngine继承了UObject和FExec,即UGameEngine 是UObject
class UGameEngine::Tick(float DeltaSeconds,bool bIdleMode)
{
	//Begin Ticking worlds
	Context.World()->Tick(LEVELTICK_ALL,DeltaSeconds);
	//tick media framework

	//tick the viewport
	GameViewport->Tick(DeltaSeconds);
	//Render everything
	RedrawViewports();
	//update resource streaming
	//...
	//update audio
	//...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • Tick主要一部分先更新worlds,另一部分更新viewport后绘图

总结

UE4自己首先需要加载模块,并且初始化自己UE4引擎循环和编译器界面,当你点击Play按钮之后,会触发UGameEngine来开始游戏的循环。

  • 针对问题去了解游戏引擎,比如这篇针对的是关于UE4如何跨平台和找到游戏引擎的Main函数
  • 全局引擎变量:FEngineLoop,UEngine
  • 游戏世界的元素:Objects,Actors,Components
  • To be continued

跨平台

  • Main函数是第一个跨平台,因为不同的操作系统main函数会有差别,主要是微软的WinMain函数
  • Render是第二个跨平台,图形编译器不一样,微软用的是HLSL着色器,还有OpenGL等等
    • RHI:Interface to render API,渲染接口
  • To be continued

UE4知识补充

UE4命名规则234

  • 派生自 Actor 的类前缀为 A,比如 AController,AActor,APlayerController

    • Actor相当于游戏世界中的一个游戏对象,有组件等等
  • 派生自 UObject(Unreal Object) 的类前缀为 U,比如 UComponent

    • UObject不仅包括游戏对象,还包括所有的UE4对象,例如游戏世界(UWorld)
  • 派生自 SWidget(Slate UI)的类前缀为 S,比如 SButton

  • Enums 的前缀为 E,比如 EFortificationType

  • Interface 类的前缀通常为 I,比如 IEngineLoop

  • Template 类的前缀为 T,比如 TArray。

  • 其余类的前缀均为 字母 F ,比如 FEngineLoop

注意:UObject是AActor的父类

UE4文件结构介绍
UE4启动从Main函数开始
UE4官网FEngineLoop


  1. UE4官网 AGameModeBase ↩︎

  2. UE4命名规则 ↩︎

  3. UE4官网Gameplay元素 ↩︎

  4. UE4官网介绍命名规则和基础知识 ↩︎