读《虚幻引擎程序设计浅析》笔记

Posted cartzhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读《虚幻引擎程序设计浅析》笔记相关的知识,希望对你有一定的参考价值。

本文章由cartzhang编写,转载请注明出处。 所有权利保留。
文章链接: https://blog.csdn.net/cartzhang/article/details/88427849
作者:cartzhang

有两年没有怎么写东西了,
希望今年有时间和精力,多读书,多写些文字。

一、 c++编程

1.运行时类型识别

UE4不能直接使用dynamic_cast,因为虚幻引擎开启了/GR-编译器参数。
具体参考:https://msdn.microsoft.com/zh-cn/library/we6hfdy0.aspx
可以继承UObject,然后使用Cast<>来实现。UE自己实现了一套运行时的识别方案。

2.对象

Actor: 可以被挂组件。
Pawn:卒子,可被Controller操纵。
Chracter:继承自Pawn,添加了CharacterMovement。
Controller:就是控制器,可以Posses/UnPosses控制一个Pawn.

  1. new

纯C++类可以使用智能指针来管理。TSharedPtr/TShared_Refl来管理。

TSharedPtr<YourClass> ClassPtr = MakeShareable(new YourClass());
  1. 字符处理

FName : 无法修改的字符串,大小写不敏感。是借助哈希表个,字符串到FName转换,根据Key查询,整个表里只会存放一个。

FText,用于显示的字符串。这个不支持修改操作。对于被显示的字符串来说,修改是一个非常不安全的操作。
slate文字参数往往是FText,这个是为了本地化要求。

FString,这个是常用的,可以修改,消耗也大于FName和FText.

二、核心系统

1. 内存分配。

window 下提供了三种内存分别方式,分别为Malloc(ANSI),Intel TBB 内存分配器,和Binned 内存分配器。

对于内存分配,主要有两个方面,

一个是通过内存池降低Malloc 消耗。
一个是通过对齐,降低缓存命中失败消耗。

2. 引擎初始化过程

不同的平台,文件都存放在Engine\\Source\\Runtime\\Launch\\Private 文件夹下面。

Linux 平台下更容易看一点:


int main(int argc, char *argv[])

	FPlatformMisc::SetGracefulTerminationHandler();

	int ErrorLevel = 0;

	// read the command line file
	// 初始化命令行
	InitCommandLine();
	FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Final commandline: %s\\n"), FCommandLine::Get());

#if !UE_BUILD_SHIPPING
	GAlwaysReportCrash = true;	// set by default and reverse the behavior
	if (FParse::Param(FCommandLine::Get(), TEXT("nocrashreports")) || FParse::Param(FCommandLine::Get(), TEXT("no-crashreports")))
	
		GAlwaysReportCrash = false;
	
#endif

	if (!IncreasePerProcessLimits())
	
		fprintf(stderr, "Could not set desired per-process limits, consider changing system limits.\\n");
		ErrorLevel = 1;
		return ErrorLevel;
	
// 		if (!FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash)
// 		
// 			// Use default crash handler, but we still need to set this up to register signal handlers
// 			FPlatformMisc::SetCrashHandler(nullptr);
// 		
// 		ErrorLevel = GuardedMain(FCommandLine::Get());


	// initialize the engine
	//预初始化引擎
	GEngineLoop.PreInit(0, NULL, FCommandLine::Get());

	// initialize HMDs 
	// @todo Lumin: I guess we don't need this?
	// InitHMDs();

	UE_LOG(Logandroid, Display, TEXT("Passed PreInit()"));

	GLog->SetCurrentThreadAsMasterThread();
    //引擎初始化
	GEngineLoop.Init();

	UE_LOG(LogAndroid, Log, TEXT("Passed GEngineLoop.Init()"));

#if !UE_BUILD_SHIPPING
	if (FParse::Param(FCommandLine::Get(), TEXT("Messaging")))
	
		// initialize messaging subsystem
		FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
		TSharedPtr<ISessionService> SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService();
		SessionService->Start();

		// Initialize functional testing
		FModuleManager::Get().LoadModule("FunctionalTesting");
	
#endif

	// tick until done
	while (!GIsRequestingExit)
	
// 		FAppEventManager::GetInstance()->Tick();
// 		if(!FAppEventManager::GetInstance()->IsGamePaused())
		
		    // 主循环Tick
			GEngineLoop.Tick();
		
// 		else
// 		
// 			// use less CPU when paused
// 			FPlatformProcess::Sleep(0.10f);
// 		

#if !UE_BUILD_SHIPPING
		// show console window on next game tick
// 		if (GShowConsoleWindowNextTick)
// 		
// 			GShowConsoleWindowNextTick = false;
// 			AndroidThunkCpp_ShowConsoleWindow();
// 		
#endif
	
// 	FAppEventManager::GetInstance()->TriggerEmptyQueue();

	UE_LOG(LogAndroid, Log, TEXT("Exiting"));

	// exit out!
	// 引擎退出循环
	GEngineLoop.Exit();
	// 应用退出
	FEngineLoop::AppExit();

	FPlatformMisc::LowLevelOutputDebugString(TEXT("Exiting is over"));

	if (ErrorLevel)
	
		printf("Exiting abnormally (error code: %d)\\n", ErrorLevel);
	
	return ErrorLevel;

1)预初始化

主要工作有:

1)设置路径:包括当前程序路径,工作目录路径,游戏工程路径。

2)设置输出标准: 设置GLog 系统输出设备,是输出到命令行还是其他。

初始化一部分系统包括:

初始化主线程 GameThread.

初始化随机数系统

初始化TaskGraph,并按照当前平台的核心数量来设置工作线程数量。

判断引擎的启动模式,是游戏模式还是服务器模式。

完成后加载核心模块,PreInitModules 启动。
包括有引擎模块,渲染模块,动画蓝图,Slate渲染模块,Slate核心模块,贴图压缩,和地形模块。

2)初始化

若模块中有PostEngineInit函数,则会通过IProjectManager 来调用完成初始化。

3)主循环

引擎中Tick更新顺序:

更新控制台变量

请求渲染线程更新当前帧率文字

更新当前应用程序时间,也就是App::DeltaTime.

更新内存分配器的状态

请求渲染线程刷新当前底层绘制资源

等待slate 程序输入状态捕获

更新引擎GEngine,调用GEnigne->Tick

更新SlateApplication.

更新RHI

收集下一帧需要清理的对象

3.对象模型

1). 垃圾回收

UObject 分两个遍历步骤
首先,通知Uclass父类进行析构,然后是收回UOjbect占用的内存。

垃圾回收算法大致有两种,一种是引用计数发。一种是标记-清扫算法。

分类项目描述
引用计数中等文本稍微长一点的文本
中等文本稍微长一点的文本
保守/精确短文本中等文本
分类项目描述
引用计数/追踪式GC引用计数通过额外的计数来对单个对象的引用计数进行计算,当引用计数为零时候,回收对象
追踪式扫描系统对象引用网络,寻找被引用对象,留下的即没有被引用的为垃圾
保守/精确保守式可不使用额外信息,不修改现有框架。释放绝对不可能被引用的对象。原则:不求全部回收,但求不错删。
精确式需要额外信息来辅助识别指针字段,但是能够精确识别每个被引用对象。
搬迁式/非搬迁式搬迁式在GC过程中,内存位置会移动。
非搬迁式在GC过程中,内存不发生移动。

虚幻系统的垃圾回收机制,采用智能指针,用引用计数法来进行智能指针处理,
引用计数的缺点在于指针开销和环形引用。
虚幻使用弱指针方法来避免环形引用。

2)UObject 标记清扫算法

虚幻引擎使用了基于簇的垃圾回收算法,用于加速Cook 后对象的回收。

三、参考:

  1. 《大象无形-虚幻引擎程序设计浅析》
  2. https://msdn.microsoft.com/zh-cn/library/we6hfdy0.aspx

以上是关于读《虚幻引擎程序设计浅析》笔记的主要内容,如果未能解决你的问题,请参考以下文章

虚幻4引擎源码学习笔记:主循环LaunchEngineLoop

e5v2虚幻引擎打不开

学习笔记Unreal(虚幻)4引擎入门

虚幻4引擎源码学习笔记:整体文件结构

虚幻引擎 Unreal Engine 详细笔记 根据谌嘉诚视频无遗漏总结 快速上手

Epic Games官方<<理解项目和文件结构>>虚幻引擎教程学习笔记