关于unreal:虚幻引擎编译系统总结

49次阅读

共计 17353 个字符,预计需要花费 44 分钟才能阅读完成。

【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 off
setlocal enabledelayedexpansion

REM 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 to
REM directory relative to where we know the .bat is stored.
pushd "%~dp0\..\..\Source"

REM %1 is the game name
REM %2 is the platform name
REM %3 is the configuration name

IF 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 生成解决方案文件,有时候也不必,如果出问题了就点一下,上面这三种形式一样成果:

  1. 运行 GenerateProjectFiles.bat。
  2. 右键点击我的项目的.uproject 文件,而后 点击 Generate Project Files
  3. 在空幻编辑器中,点击 文件(File)> 刷新(Refresh)Visual Studio 我的项目(Project)

另外说一下 Generate Visual Studio Project 的实质也是执行 UnrealBuildTool.exe 的带参命令,所以当你在 Visual Studio 中点 Build 的同时点 Generate Visual Studio Project,就会提醒你正在运行中,当然这两个命令的参数不同。

总结 UBT 工作流程
接下来说一下空幻我的项目点击 Build 过程的工作:

  1. UBT 读取每个模块的 Build.cs 文件获取各模块之前的依赖关系(如果代码没有更改,则会跳过该模块)
  2. UBT 调用 UHT(头文件解析)依据反射属性标签(UCLASS,UFUNCTION,UPROPERTY 等)生成 generated.h 和 gen.cpp 文件,用于裸露给蓝图该类的信息
  3. 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()等,当然不止这些,还有很多其余不罕用的。同时还能够看到有点属性和办法没有被宏标记,他们次要区别如下:

  1. 下面提到过这些宏的作用是为了服务反射零碎,将类的信息裸露给反射零碎,也是蓝图能拜访的根底条件,如果不加宏就无奈被反射零碎辨认,更不用说在蓝图中调用了。当然如果须要蓝图对属性能进行更改等操作,还须要在宏括号内加标签进一步润饰。
  2. 当类属性有宏润饰时,空幻的垃圾回收零碎能够对该属性进行治理,没有被 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.h
voidA(){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_NAMESPACE
IMPLEMENT_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 it
FString 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)

正文完
 0