【USparkle专栏】如果你深怀绝技,爱“搞点钻研”,乐于分享也博采众长,咱们期待你的退出,让智慧的火花碰撞交错,让常识的传递生生不息!
一、前言
始终以来很多人都在说,空幻引擎的学习难度要比Unity大很多,其中一个起因是C++自身很难,另外一点就是因为空幻写了本人的编译系统,并且为了实现反射对C++代码进行封装,因而就算对C++根底把握的不错,也一样很难了解其中代码的意思。本文将介绍空幻引擎对编译和反射做了哪些工作,帮忙刚接触空幻引擎的开发者了解并疾速上手开发,做一篇避坑指南,本文波及到我的项目开发过程中,如何防止头文件、宏等奇怪的报错,解决常见编译问题,了解空幻引擎模块化治理代码形式,了解引擎编译和启动过程,创立插件,援用第三方库,并参考引擎代码设计,获取疾速开发技巧等等。全文大略2万字,心愿能急躁看完。
二、介绍
当咱们创立一个一般的C++空我的项目时,个别的步骤是配置平台、版本后,增加代码,创立main
函数入口,右键我的项目点击Build(生成)或者从新生成,或者间接点击运行,而后就能够测试本人的代码和性能了。
个别状况下,是不须要点从新生成(Rebuild)的,尤其是在应用UE4时,这项肯定要慎用,因为要等很久很久能力编译完。但除非你遇到了奇怪的编译谬误,曾经确保我之后说的那些配置正确之后还有问题,再点从新生成试一下是否胜利,这里附一张图阐明:
UE4运行我的项目也是如此,但不同之处在于点击“Build”的过程。首先先剖析下UE4我的项目文件构造,不然很难了解。
上面展现的是UE4我的项目带C++的样子(不带C++的我的项目这里不再赘述):文件夹数量可能有多有少,和编译指标、插件无关,都无关紧要。
当右键我的项目点击Generate Visual Studio Project选项时(如果没有该选项,查看下Epic Games Launcher是不是须要更新),就会生成Visual Studio我的项目。Visual Studio我的项目生成之后会呈现.vs、Binaries文件夹,下面说对于Rebuild慎用,这里说一个的替换形式:删除Binaries、Intermediate和.vs文件夹,并从新点Generate Visual Studio Project,关上VS生成我的项目。这样就防止Rebuild可能造成从新编译引擎的问题了。
另外,Config文件夹下是引擎和我的项目的配置文件,肯定要小心,不要删除。Content文件夹是游戏内容资源的目录;Plugins是我的项目依赖插件目录;Saved保留我的项目的一些缓存数据,包含性能调试、命令行生成的文件、保留的游戏数据和打包Cook数据等,能够删除;Source是我的项目C++代码目录。
关上VS查看我的项目构造,别离是:
Engine文件夹下蕴含引擎源码;Game下蕴含我的项目代码,包含插件;Programs文件夹下有两个重要的我的项目:UnrealBuildTool(编译工具)和UnrealHeaderTool(头文件解析工具)即UBT和UHT。
空幻引擎的代码量十分恐怖,因而须要更业余的形式治理,空幻引擎采纳模块化的形式治理代码,每个模块之前互相援用依赖,通过援用的形式递归加载对应的模块,而治理这些模块的工具就是UBT,UHT用于头文件生成解析。
应用UBT治理模块的另一个起因是为了不便跨平台,这样咱们不须要为每个平台都做对应的配置,不不便且容易出问题,对开发者不敌对。有了UBT,只须要在对应的CS文件里配置一次后就能够利用到多个平台了。跨平台的工作由UBT来替你实现。
接下来具体介绍。
应用工具
以下对于代码的剖析等应用的工具环境是:
- Windows10
- UE 4.26
- Visual Studio 2019
三、UBT(UnrealBuildTool)
到目前为止大略理解了UBT是空幻引擎治理各个模块的工具,但它不会编译代码,只是负责收集模块之间的信息而后依据平台和编译指标并通知编译器进行编译。UBT源码能够在解决方案的Programs\UnrealBuildTool\下找到。
UBT采样NMake Build System,咱们能够在我的项目属性的NMake处看到相干设置,这里显示的就是当咱们右键点击Build和Rebuild,Clear事件会执行的内容,如下图:
咱们顺着它批示的门路找,而后就会找到Build.bat文件,关上后查看内容:
@echo offsetlocal enabledelayedexpansionREM The %~dp0 specifier resolves to the path to the directory where this .bat is located in.REM We use this so that regardless of where the .bat file was executed from, we can change toREM directory relative to where we know the .bat is stored.pushd "%~dp0\..\..\Source"REM %1 is the game nameREM %2 is the platform nameREM %3 is the configuration nameIF EXIST ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe ( ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe %* popd REM Ignore exit codes of 2 ("ECompilationResult.UpToDate") from UBT; it's not a failure. if "!ERRORLEVEL!"=="2" ( EXIT /B 0 ) EXIT /B !ERRORLEVEL!) ELSE ( ECHO UnrealBuildTool.exe not found in ..\..\Engine\Binaries\DotNET\UnrealBuildTool.exe popd EXIT /B 999)
发现实质就是运行UnrealBuildTool.exe,换句话说就是给UnrealBuildTool传递配置参数并运行UnrealBuildTool.exe。
参数来自下面的选项中,包含我的项目名字、编译指标、32位还是64位等信息。而后咱们从UnrealBuildTool我的项目代码中找到入口函数,如下:
能够看到会承受对应平台等信息并进行解析,之后的内容就是读取剖析Target.cs和Build.cs文件,而后调用编译工具编译我的项目,至于细节也没必要深究,对UBT理解到这一步感觉就足够了。
模块配置
提了好几次模块,但还没有具体介绍,因为模块内容量很大,我会放在前面具体介绍,不过这里想大略介绍下每个模块下的Build.cs和我的项目中的Target.cs文件。
看下模块的根底构造:
每个模块必须存在的三个文件:
- 模块名字Build.cs文件,用于为UBT提供模块以来信息
- 模块外围类,继承自IModuleInterface,并实现两个外围办法:StartupModule和ShutdownModule,会在引擎启动加载模块的时候执行,用于初始化模块信息,个别不须要更改
Target.cs只在我的项目中存在,它相似于我的项目设置,配置指标和扩大依赖模块,前面再解释。
Generate Visual Studio Project
Generate Visual Studio Project选项作用十分大,当咱们关上VS我的项目后,咱们会发现本地磁盘文件中的Source和VS显示的内容是没有保持一致的,因为空幻引擎编译会疏忽SLN文件,SLN存在的意义只是不便关上编写代码,不会查看Source下的文件构造,如果在VS我的项目中删除某个类,它会仍然存在磁盘本地文件夹下请确保同时删除本地文件,或者当你更改或挪动某个类文件门路,本地文件理论没有发生变化,而如果你这时不通过VS挪动,而是间接挪动本地文件夹下的文件,在VS中就会提醒找不到该文件援用,学习空幻引擎的新人肯定会受此问题困扰,我当初也是,起初晓得从新Generate Project Files就好了~第一个避坑指南说完了。
个别只有你批改 [ModuleName].Build.cs 文件,或挪动文件,就须要为IDE生成解决方案文件,有时候也不必,如果出问题了就点一下,上面这三种形式一样成果:
- 运行GenerateProjectFiles.bat。
- 右键点击我的项目的.uproject文件,而后点击Generate Project Files。
- 在空幻编辑器中,点击文件(File)>刷新(Refresh)Visual Studio我的项目(Project)。
另外说一下Generate Visual Studio Project的实质也是执行UnrealBuildTool.exe的带参命令,所以当你在Visual Studio中点Build的同时点Generate Visual Studio Project,就会提醒你正在运行中,当然这两个命令的参数不同。
总结UBT工作流程
接下来说一下空幻我的项目点击Build过程的工作:
- UBT读取每个模块的Build.cs文件获取各模块之前的依赖关系(如果代码没有更改,则会跳过该模块)
- UBT调用UHT(头文件解析)依据反射属性标签(UCLASS,UFUNCTION,UPROPERTY等)生成generated.h和gen.cpp文件,用于裸露给蓝图该类的信息
- UBT通过依赖关系调用MSBuild编译C++代码
UBT配置代码的不不便之处
因为UBT是由C#代码编写的,在C++我的项目中没有智能提醒,外面的代码无奈转至定义,尽管须要编写的代码不多,但仍然很不不便,这对老手来说更加不敌对,有方法解决提醒问题吗?有。放到前面说,这里不是卖关子,只是要介绍的插件不仅仅能够用来提醒UBT开发。
四、UHT(UnrealHeaderTool)
文本解析工具UnrealHeaderTool存在的意义就是为空幻反射零碎服务,蓝图的实现原理。(反射零碎简略来说就是运行时获取类的信息,包含类的属性、办法、属性类型、名字、拜访权限、办法名字和返回值等等,获取类的信息能够做到很多事件,包含通过名字调用类的办法等。)
依据反射属性标签(UCLASS,UFUNCTION,UPROPERTY等)生成generated.h和gen.cpp文件,用于裸露给蓝图该类的信息,但凡继承自UObject的类都会生成generated.h和gen.cpp文件。
UBT的次要工作就是利用开发者手动标记的属性、办法、类等宏来辨认类的信息,而后将类信息封装到gen.cpp的代码中,当然还包含对结构析构函数等内容的批改,应用空幻自身的垃圾回收零碎进行治理。
看下它是怎么获取类的信息的:
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "MyActor.generated.h"UCLASS()class TEST_API AMyActor : public AActor{ GENERATED_BODY()public: // Sets default values for this actor's properties AMyActor(); ~AMyActor(); int a;protected: // Called when the game starts or when spawned virtual void BeginPlay() override;public: // Called every frame virtual void Tick(float DeltaTime) override; UFUNCTION(BlueprintCallable) static void StaticFun(); UFUNCTION(BlueprintCallable) void SetMesh(UStaticMesh* m); UFUNCTION(BlueprintCallable) UStaticMesh * GetMesh(); UPROPERTY(EditAnywhere,BlueprintReadWrite) TSoftObjectPtr<AActor> ptr; UPROPERTY(EditAnywhere, BlueprintReadWrite) UStaticMesh *mesh;};
能够看到最根底的Actor类的属性、办法、类等内容被一些非凡宏标注,包含:UCLASS()、UPROPERTY()、UFUNCTION()等,当然不止这些,还有很多其余不罕用的。同时还能够看到有点属性和办法没有被宏标记,他们次要区别如下:
- 下面提到过这些宏的作用是为了服务反射零碎,将类的信息裸露给反射零碎,也是蓝图能拜访的根底条件,如果不加宏就无奈被反射零碎辨认,更不用说在蓝图中调用了。当然如果须要蓝图对属性能进行更改等操作,还须要在宏括号内加标签进一步润饰。
- 当类属性有宏润饰时,空幻的垃圾回收零碎能够对该属性进行治理,没有被UPROPERTY宏润饰或在AddReferencedObjects函数被没增加援用的UObject*成员变量无奈被空幻引擎辨认,垃圾回收零碎不会治理,因而应用指针时要留神。
具体有:
每一项都有可选参数,次要是通知蓝图对于这个类/函数/构造体等内容信息,不同参数在蓝图中有不同体现。
上面有宏体内类型修饰符都有哪些的介绍,别离对应UCLASS()、UINTERFACE()、UFUNCTION()、UPROPERTY()和USTRUCT()。外面具体参数项十分多,但实际上大部分都用不到,所以这里我就挑罕用的介绍下:
对于UCLASS
当被标记为:
运行后的成果就是不可创立蓝图子类:
改为:Blueprintable后,就能够创立了,也是继承Actor的默认的选项:
如果不写默认是NotBlueprintable:
但Actor默认是Blueprintable,起因在Actor的定义处:
Actor这个类标注了Blueprintable,因而子类也就继承了这一选项,所以当一个继承自Actor的子类即使不写也是Blueprintable的。换句话就是,有些属性能够继承,具体有哪些能够在ObjectMacros.h中查看。
其余罕用的:
BlueprintType:能够在蓝图中创立这个类变量
Const:该类所有函数和属性应该是Const的,且Const标签能够继承
Abstract:该类是抽象类
留神:这里应该放弃蓝图和C++代码自身的一致性,即,如果你在UCLASS中加了Abstract,应该也要将这个类写为抽象类,不然会出各种问题,包含上面的UFUNCTION也一样,如果是BlueprintPure润饰,就应加Const润饰函数,上面会有例子。
对于UFUNCTION
罕用的是UFUNCTION(BlueprintCallable),没有BlueprintCallable,蓝图是不能调用的BlueprintPure,这个宏的意思是纯函数,但不能够了解为和C++中的纯虚函数有关系,事实上,这两个没有任何关系,当初我就是陷入这个循环无法自拔。
首先,BlueprintPure标记的函数必须有返回值,因为没有输入输出节点,看下图。其次BlueprintPure的作用有些相似于用Const润饰的函数:即不能够更改类的成员变量值,然而它的限度并不像Const那样,你改了值就报错,然而,请不要在标记为BlueprintPure的函数中更改其类的变量值,肯定不要,看一下上面的例子:
UFUNCTION(BlueprintCallable)void constBlueprintPure() const;UFUNCTION(BlueprintCallable)int constBlueprintPureWithValue() const;UFUNCTION(BlueprintPure)int blueprintPure();int AMyActor1::blueprintPure(){return 0;}void AMyActor1::constBlueprintPure()const{}int AMyActor1::constBlueprintPureWithValue()const{return 0;}
对应在蓝图中的样子:和一般函数有些区别,是绿色的,没有执行输出和输入节点,至于返回值为void的就有了,不然它就没方法在蓝图里调用。
这个就是函数中Pure的真面目了:被Const标记的函数,因而当前再用就不要在标记Pure的函数中更改类成员变量的值了。
对于UPROPERTY
每一项节选内容介绍:
- Const:const常量
- VisibleAnywhere:属性面板可见但不可编辑
- BlueprintReadOnly:蓝图中只读
- BlueprintReadWrite:可读可写
- Category:分组名字
- EditDefaultsOnly:只可在属性面板编辑
......
每一项的属性都很多,比如说属性(UPRPOPERTY)能被EditAnywhere和VisibleAnywhere等润饰,然而这两个只能同时存在一个,起因是这两个属性属于雷同的枚举类型,咱们能够转到定义处查看,确保不呈现相似谬误,另外就是多用就相熟了。
具体的参考上面的链接:
- UCLASS说明符列表
- UPROPERTY说明符列表
- UFUNCTION说明符列表
- USTRUCT说明符列表
对于meta
个别用于给蓝图节点改名字,设置默认值,限度范畴等,例如:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI", meta = (ClampMin = "1", ClampMax = "100"))int32 RowCount;
GENERATED_BODY()
你可能还会看到GENERATED_USTRUCT_BODY或者GENERATED_UCLASS_BODY,还有GENERATED_UINTERFACE_BODY和GENERATED_IINTERFACE_BODY。
首先,它们的作用都是封装了引擎给你写好的代码。包含重写构造函数等,他们的区别是什么呢,看一下定义就好了:
会发现以上提到的四个宏最终都指向了GENERATED_BODY_LEGACY和GENERATED_BODY。GENERATED_UCLASS_BODY、GENERATED_UINTERFACE_BODY和GENERATED_IINTERFACE_BODY这三个的实质是GENERATED_BODY_LEGACY,而GENERATED_USTRUCT_BODY的实质是GENERATED_BODY,当初的UCLass和UStruct个别都会用GENERATED_BODY,如果你想用GENERATED_UCLASS_BODY和GENERATED_USTRUCT_BODY也是一样的作用。
这里顺便说下,GENERATED_BODY_LEGACY()比GENERATED_BODY多了ATestActor(const FObjectInitializer& ObjectInitializer);,一个带参数的构造函数。
有的构造函数里带FObjectInitializer& ObjectInitializer,这都是空幻晚期版本,当初根本都不必了,当然为了兼容,构造函数这样写也没问题,看一下UObject的定义就晓得答案了:
至于这些宏具体干了什么,大钊老师曾经在他的反射文章里写的十分分明了,感兴趣能够看下《《InsideUE4》UObject(八)类型零碎注册-CoreUObject模块加载》。
还有,肯定要留神,GENERATED_BODY必须放在类的结尾地位,除了加friend class如:
其余
类后面模块名_API宏的作用:用于裸露这个类给其余模块拜访,如果没有,其余模块是无奈获取到这个类的信息的,即使在Build.cs中引入了该模块。
五、创立模块
模块是引擎治理代码的根本单位,插件和游戏中起码要蕴含一个模块,当创立我的项目时会主动生成游戏主模块。
当在引擎中创立一个插件时也会有一个默认模块,像这样:目前手动创立模块还没什么高效的形式,只能创立对应文件夹和模板文件。
这是一个模块最根本的构造,创立一个新模块只能先创立文件夹,而后加上对应模块名称的文件,Build.cs和ModuleName.h和cpp。
创立完这个模板之后记得右键点击我的项目的.uproject 文件,而后点击Generate Project Files。下面有说。
编写Build.cs文件
规范Build.cs中的内容:
using UnrealBuildTool;public class Menu3 : ModuleRules{ public Menu3(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","GameModule"}); //PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); }}
外围须要更改的便是依赖模块数组PublicDependencyModuleNames或者PrivateDependencyModuleNames。
这里援用大钊老师总结的内容:每个模块都有Public和Private文件夹,如果类写在最里面(不在Public文件夹内)就会被视为Private,如果蕴含模块还是无奈援用某个类,要查看头类文件是否在Public文件夹内:
如果能用Private蕴含,尽量用Private,以缩小模块依赖,缩小编译工夫。
其余序列:
- PublicIncludePathModuleNames和PublicDependencyModuleNames
二者都能使本模块能够蕴含其它模块Public目录下的头文件,从而调用函数。然而PublicIncludePathModuleNames只能调用定义全副在头文件里的函数,这里要区别什么是申明(不带实现)、什么是定义(带实现)。然而如果定义在头文件中,很容易会呈现重定义的谬误,而且会拖慢编译速度,除非用Static或者Inline润饰。
例:
// Class.hvoidA(){B();// 假如B的定义在.cpp文件中,那么这时A的函数实现由B组成,但B不在头文件中.}//则此时应用IncludePathModuleNames时,无奈调用函数A。(报错:无奈解析的内部符号B)
因而,总结就是不举荐应用PublicIncludePathModuleNames。
- PublicIncludePaths和PublicDependencyModuleNames
PublicIncludePaths仅仅是蕴含相应目录,不是援用外面的头文件,如果你的#include里的门路很长,就能够利用这种形式在include的时候不必写对应头文件门路:
PrivateIncludePaths.AddRange( new string[] { "ShooterGame/Private", "ShooterGame/Private/UI", "ShooterGame/Private/UI/Menu", "ShooterGame/Private/UI/Style", "ShooterGame/Private/UI/Widgets", } );
相当于一般C++我的项目中附加蕴含门路:
罕用的就是这些,具体参数可拜访:
https://docs.unrealengine.com/5.0/zh-CN/module-properties-in-...
DLL和LIB库导入上面独自介绍。模块属性DLL和LIB库导入上面独自介绍。
编写ModuleName.h和cpp文件
//h#include "CoreMinimal.h"#include "Modules/ModuleManager.h"class FModule1Module : public IModuleInterface{public:/** IModuleInterface implementation */virtual void StartupModule() override;virtual void ShutdownModule() override;};//cpp#include "Module1.h"#define LOCTEXT_NAMESPACE "FModule1Module"void FModule1Module::StartupModule(){// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module}void FModule1Module::ShutdownModule(){// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,// we call this function before unloading the module.}#undef LOCTEXT_NAMESPACEIMPLEMENT_MODULE(FModule1Module, Module1)
这里根本就是固定写法StartupModule和ShutdownModule两个办法别离是该模块被加载和卸载的时候执行,这里个别不须要批改什么,也能够抉择在模块启动时写加载DLL之类的逻辑。至于模块什么时候被加载,是在ulpugin或者uproject中配置的。
这里留神IMPLEMENT_MODULE(FModule1Module, Module1)宏,后面是类名,前面是模块名。
其余注意事项
- 当须要在本模块中援用另一个模块的类时,配置正确Build.cs之后,还要正确include头文件能力应用
为了正确做到下面两步,我个别抉择的形式是去官网API查问,像这样:FDesktopPlatformModule
官网会通知你引入的模块名字和include的形式,然而,这里我应用官网的形式是无奈找到头文件的,而写上门路Developer前面的全副能力正确include这个头文件,像这样,红色的是谬误写法,官网的形式,上面的是正确写法:
门路:
这个例子通知咱们:不要过于置信官网的提醒,有可能是没有更新或者脱漏。
个别状况下,include内的内容就是模块名字后classes/public/private内的路径名。Classes文件夹是历史遗留产物,代表Public,当初个别都是Public/Private。看上图门路。
- 另外,尽量不要复制代码,尤其是整体类,太容易出问题了,而且复制代码容易导致很多模块从新编译,巨慢。
援用内部LIB,DLL库
- 一般Visual Studio我的项目援用LIB形式
引入LIB库须要头文件和LIB库。
头文件增加到:
LIB加到:
LIB门路(可选:也能够在下面的glfw.lib后面加,上面就不必写了)
- UE援用形式
引入LIB库,在模块的Build.cs中退出:
PublicIncludePaths.Add(Path.Combine(ThirdPartyPath));//头文件门路PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "CaptureScreen.lib"));//LIB库门路
引入DLL库,在Build.cs中退出:
PublicIncludePaths.Add(Path.Combine(ThirdPartyPath));//头文件门路PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "CaptureScreen.lib"));//LIB库门路PublicDelayLoadDLLs.Add("CaptureScreen.dll");//DLL名字
而后在某个中央动静加载DLL,在应用前,能够写到StartupModule函数中:
FString BaseDir = IPluginManager::Get().FindPlugin("ThirdPartyLibrary")->GetBaseDir();// Add on the relative location of the third party dll and load itFString LibraryPath; #if PLATFORM_WINDOWS LibraryPath = FPaths::Combine(*BaseDir, TEXT("Source/ThirdParty/ThirdPartyLibraryLibrary/x64/Release/TestDLL.dll")); #elif PLATFORM_MAC LibraryPath = FPaths::Combine(*BaseDir, TEXT("Source/ThirdParty/ThirdPartyLibraryLibrary/Mac/Release/libExampleLibrary.dylib")); #endif // PLATFORM_WINDOWS vois * ExampleLibraryHandle = !LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr; if(ExampleLibraryHandle) //load successfully
六、创立插件
创立插件个别通过引擎创立引擎很多。
其实插件就是模块的汇合,起码有一个模块,根本构造如下:Module1是本人加进去的:
插件对模块的治理写在uplugin文件中,事例:
{ "FileVersion": 3, "Version": 1, "VersionName": "1.0", "FriendlyName": "MyPlugin", "Description": "", "Category": "Other", "CreatedBy": "", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", "SupportURL": "", "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false, "Modules": [ { "Name": "MyPlugin", "Type": "Runtime", "LoadingPhase": "Default" }, { "Name": "Module1", "Type": "Runtime", "LoadingPhase": "Default" } ]}
下面大部分信息都是形容插件信息的,要害是Modules局部,外面定义了模块的名字,加载机会等要害信息。
除此之外,还能够加上面这些信息:(但个别不须要)
最初一项AdditionalDependencies尽量在模块的Build.cs中配置,不要在这里写(PublicDependency那些)。
Type,形容的是打包到某个平台时是否要加载这个模块,比方打包游戏就不须要Editor相干的模块代码。
具体有:
namespace EHostType{ enum Type { Runtime, RuntimeNoCommandlet, RuntimeAndProgram, CookedOnly, UncookedOnly, Developer, DeveloperTool, Editor, EditorNoCommandlet, EditorAndProgram, Program, ServerOnly, ClientOnly, ClientOnlyNoCommandlet, Max, }}
罕用的就是:
Runtime(任何编译指标都会加载该模块)
Editor(只在编辑器下加载该模块)
LoadingPhase,形容什么时候加载该模块,这个比拟重要,依赖其余模块的模块应该在依赖模块之后加载,不然就会报错找不到对应模块。
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Projects...
具体有:
namespace ELoadingPhase{ enum Type { EarliestPossible, PostConfigInit, PostSplashScreen, PreEarlyLoadingScreen, PreLoadingScreen, PreDefault, Default, PostDefault, PostEngineInit, None, Max, }}
这个是按工夫排序的,默认是Default,顺便Default个别是引擎启动到百分之75左右加载这些模块,就是这个时候:
EarliestPossible是还没看到这个黑的加载界面之前就加载,简直用不到,想晓得其余的什么时候加载打断点测一下就好。
最初是治理插件,是在uproject中实现的,上面介绍。
七、创立多个游戏模块
在游戏我的项目中创立模块的形式和在插件中无异,模块的根本构造都是一样的,但惟一须要留神的是对应的主模块类里的宏会发生变化:
游戏模块(非主模块应用的宏)是IMPLEMENT_GAME_MODULE(FDefaultGameModuleImpl, GameModule);,而不是插件中的IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)。其余更改就须要留神名字匹配正确。
而主游戏模块用的宏(默认的游戏模块)是IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Test, "Test" );。
uproject
外面的内容先看下:
{"FileVersion": 3,"EngineAssociation": "4.26","Category": "","Description": "", "Modules": [ { "Name": "Test", "Type": "Runtime", "LoadingPhase": "Default" }, { "Name": "GameModule", "Type": "Runtime", "LoadingPhase": "Default" } ]}
下面版本信息不是介绍重点,次要是外面的模块,这里我创立了一个GameModule的游戏模块,外面的属性配置和下面讲的统一,要害是如果这外面不写GameModule模块,游戏引擎中是不会辨认到这个模块的(引擎C++文件夹处不会有GameModule模块代码,也因而无奈援用该模块内容,即使主模块的Build.cs中援用了GameModule模块),因而,这里肯定要加上你写的模块。这里思考一个问题:如果GameModule没有被主模块援用,打包的时候会产生什么。
Target.cs
using UnrealBuildTool;using System.Collections.Generic;public class MyProjectTarget :TargetRules{ public MyProjectTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game;DefaultBuildSettings = BuildSettingsVersion.V2; // 此处为其余属性 }}
在讲空幻模块的文章中,很少有讲到Target.cs文件的作用的,我的项目个别会默认创立两个,一个是Target.cs,另一个是Editor.Target.cs。
刚刚始终说模块的援用形式依赖Build.cs,然而没有任何模块依赖游戏主模块,游戏主模块是怎么加载进内存的呢,这就依赖了Target.cs中的ExtraModuleNames.AddRange( new string[] { } );。默认状况下,外面加的是游戏主模块名字,这也就是为什么游戏主模块能加进引擎的起因了。
回到下面提的那个问题上,如果本人写的模块没有被主模块援用,打包的时候会产生什么。
我测试了几种状况:
uproject加模块GameModule信息,主模块Build.cs加GameModule援用,ExtraModuleNames中不退出GameModule模块,测试打包存在GameModule:
uproject加模块GameModule信息,主模块Build.cs不加GameModule援用,ExtraModuleNames中不退出GameModule模块,测试打包不存在GameModule:
uproject加模块GameModule信息,主模块Build.cs不加GameModule援用,ExtraModuleNames中退出GameModule模块,测试打包存在GameModule。
uproject不加模块GameModule信息,主模块Build.cs不加GameModule援用,ExtraModuleNames中退出GameModule模块,关上引擎间接报错了。
通过以上几种状况比照可知,如果自定义的模块没有被援用,打包的时候是不会加载进去的。uproject只决定模块是否对引擎可见,于打包配置无关。
而后依据不同的编译Target,引擎会执行不同的Target.cs,如果没用实现,会执行Target.cs中的基类内容。
他们的不同在于Type = TargetType.Client;
Type = TargetType.Game; //Type = TargetType.Editor; //Type = TargetType.Client; //Type = TargetType.Server; //Type = TargetType.Program; //UnrealTargetConfiguration(Unknown, Debug, DebugGame, Development, Shipping, Test) //UnrealTargetPlatform(Win32, Win64, Linux, Mac, PS4..) DefaultBuildSettings = BuildSettingsVersion.V2;
具体可参考:
https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/...
八、Build.cs,Target.cs,uplugin和uproject
从这些文件总结下UE4的编译:
模块由Build.cs确定相互依赖关系,如果模块在插件中,加载机会和编译指标在uplugin中定义,如果是游戏我的项目模块,在uproject中定义。
Target.cs是在打包到对应平台会起作用的文件,如果你的模块没有被依赖,就须要在这里加进去,当然如果模块被其余模块依赖了就不须要再加了。
uproject里除了游戏模块,还会管制插件是否激活,在引擎里更改插件是否激活,uproject就会动静生成对应内容,个别不须要手动增加插件信息。
九、开发工具介绍
Visual Studio
最新版2022对C#代码提醒十分的智能,惋惜空幻要用C++开发。
VA_X
最惯例C++代码提醒工具,对空幻我的项目提醒能力不错,不过仅限于C++局部。
Resharper
因为UBT用C#编写代码,导致在配置模块的时候没有提醒,尽管能够在引擎模块中复制相干代码,然而还是很不敌对,这里就要出动弱小的Resharper插件了,这个插件的弱小之处不仅仅在于能提醒C++中的各种宏以及一般C++语法提醒,还能做到给Build.cs这种配置文件做出提醒,有了它,再也不须要犯愁模块配置问题了,但他的弱小不仅止于此:
- 智能提醒USF,HLSL文件,不便疾速写Shader代码
- 智能提醒头文件是否被应用,如果没用会加灰色(IWWY,include what you use)提醒能力几乎恐怖
- 提醒所有空幻引擎的各种宏,UBT的代码提醒,疾速跳转,补全逻辑等等
- 疾速在VS中创立Actor,UObject模板类,以前都是从引擎里创立,或者复制一个改很多内容,改错了就一堆奇怪的编译谬误。有了这个性能,极大晋升开发速度
上面展现:
宏谬误或者缺失提醒:
垃圾回收提醒:
疾速生成类模板
疾速创立模板:
等等,性能太多了!
说到这就足够证实这个插件的弱小了,不过它也有问题,就是重大拖慢Visual Studio启动速度,比拟耗性能,所以依据大家的需要决定是否应用吧。下载就不说了,须要装置两个插件resharper C++和resharper C#两个。
Rider for Unreal Engine 2021.3
新工具,提醒同Resharper,而且轻量级,当初引擎曾经反对,然而我还是习惯用Visual Studio,附上网址,有须要自行申请。
Rider for Unreal Engine
十、总结
实现本文耗时还蛮久的,但感觉还是有必要写一下,对初学空幻引擎的开发者们应该或多或少有所帮忙,其实还有很多没有写,包含PCH,还有一些宏介绍,然而我更心愿大家获取的是我学习这些货色的办法,对于学习形式次要还是参考官网、论坛,而后如果有能力翻墙就看看国外的教程,比国内的要好很多,当然B站和知乎上也有很多优良的教程,最初如果哪里有谬误或者有余还请大家指导。
这是侑虎科技第1454篇文章,感激作者雪流星供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:465082844)
作者主页:https://www.zhihu.com/people/xueliuxing
再次感激雪流星的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:465082844)