什么时引擎独立应用程序?让我们首先看一下虚化引擎的Launcher运行器。不知道大家是否曾经好奇,虚幻引擎的Luancher运行器时用什么写的呢?如果你曾经仔细观察过虚幻引擎的运行器的文件结构,你会惊讶的发现,这个文件结构非常类似于虚幻引擎本身的文件结构,而非虚幻引擎编译打包形成的游戏文件结构。这意味着,虚幻引擎的运行器是一个微型虚幻引擎,而不是一个打包形成的游戏!
如何开发这样的东西呢?如果我们希望在一个小型的、不是游戏的应用程序中继续使用虚幻引擎的一部分API,那么我们就需要学习UE4 Luancher(虚幻运行器)这样的技术。
关于如何撰写这样的应用程序的介绍,虚幻官方文档语焉不详,几乎没有说明什么。但是虚幻引擎提供了几个案例性的程序,如BlankProgram 和SlateViewer。我们可以从几个应用程序着手进行分析和学习。
由于没有了虚幻引擎提供的打包机制,我们需要自己完成应用程序的剥离和发布。这个发布过程并不是一个简单地做加法的过程,而是一个做减法的过程。我们不是选择出哪些文件时我们需要的,并添加到程序中,而是选择出哪些文件时我们不需要的从程序中删除。
那么,我们首先把目光投向虚幻引擎的源码目录ixa的一个小文件夹,就是Source\Programs\BlankProgram。
这个文件夹包含了以下的文件结构:
BlankProgram
| BlankProgarm.Build.cs
| BlankProgram.Target.cs
|
|_Private
BlankProgram.cpp
BlankProgram.h
如何编译呢?你只需要打开源码版本的虚幻引擎的工程文件,然后运行解决方案窗口中的BlankProgram项目,你会看到弹出一个命令行窗口,显示出一行 “Hellow World”。
在对源码进行分析前,我们必须先关注如何配置这个工程。怎么告诉UHT(类似API库)和UBT(类似编译器),我们需要编译出一个可以运行的.exe文件,而不时虚幻引擎的插件、模块或者游戏?
答案是:我们通过.Target.cs 指定。由于虚幻引擎对源码引用的限制,笔者将会逐函数地分析.Target.cs的内容。你可以队长对应的文件,然后阅读比记者的讲解。比较重要的函数主要有SetupBinares和SetupGlobalEnvironment。
Public ouverride void SetupBinaries(
TargetInfo Target,
ref List
OutBuildBinaryConfigurations,
ref List OutExtraModuleNames
)
{
OutBuildBinaryConfigurations.Add(
new UEBuildBinaryConfiguration( InType:
UEBuildBinaryType.Eecuteable,
InmoduleNames:new List(){
"BlankProram"
}
)
);
}
在SetupBinaries中,我们指定了两样信息:
需要被引入的模块名,在这里叫BlankProgram,指向的是.build.cs中定义的BlankProgram模块。
需要指定引入模块的编译类型,这里编译的类型是UEBuildBinaryType.Executable。
第二个编译类型指定非常重要,意味着我们的模块将不再被编译为二进制数据模块,用于编译链接,而是编译为可执行的exe应用程序。换句话说,对SetupBinaries函数的重载,完成了对编译为exe应用程序的指定。
public onverride void SetupGlobaleEnvionment(
TargetInfo Target,
ref LinkEnvironmentConfiguration
OutLinkEnvironmentConfiguration,
ref CPPEnvironmentConfiguration
OutCPPEnvironmentCOnfiguration
)
{
UEBuildConfiguration.bCompileLeanAndMeanUE = true;
BuildCOnfiguration.bUseMallcProfiler = false
UEBuildConfiguration.bBuildEditor = false
UEBuildConfiguration.bBuildWithEditorOnlyData = true;
UEBuildConfiguration.bCompileAgainstEngine = false;
UEBuildConfiguration.bCompileAgainsetCoreUOBject = false
OutLinkEnvironmentConfiguration.bIsBuildingCOnsoleApplication = true;
}
笔者删除了注释以符合虚幻引擎的源码应用协议。这里我们能够看到7个控制变量的设定,具体含义如下:
在此基础上,我们的Hello World程序就会显得比较简单,核心代码如下:
#include "BlankProgram.h"
#include "RequiredProgramMainCPPInclude,h"
DEFINE_LOG_CATEGORY_STATIC(LogBlankProgram,Log,All);
IMPLEMENT_APPLICATION(BlankProgram,"BlankProgram") ;
INT32_MAIN_INT32_ARGC_TCHAR_ARGV()
{
GEnginloop.PreInit(ArgC,ArgV);
UE_LOG(LogBlankProgram,Display,TEXT("Hello World"));
return 0;
}
整个代码中包含的内容不多,相对来说,Main函数中的内容理解起来比较简单,即预初始化引擎来完成日志系统初始化,从而支持UE_LOG宏,输出Hello World。为了配和UE_LOG宏,我们需要定义log category,所以需要在最前方书写DEFINE_LOG_CATEGORY_STATIC宏。
此时相对来说陌生的只有两个部分:
对于前者,这个头我呢见包含了:
ModuleManager.h 模块管理器头文件,提供注册模块需要的辅助类,这是提供给IMPLEMENT_APPLICAION宏使用的。
LaunchEngineLoop.h 提供我们用的GEngineLoop定义。
LaunchEngineLoop.cpp 头文件包含cpp显得非常奇怪,根据官方注释,cpp提供了一些需要的定义。
对于后者,实际上可以理解为和之前介绍的模块实现宏IMPLEMENT_MODULE一样,提供了模块的注册实现。
整个程序就打印了一行日志输出,逗我玩?莫慌,先看看我们已经拥有了什么:
在这个基础上,我们能走的更远:我们可以开发出一个引擎独立的Slate应用程序。若读者有兴趣,请按照以下步骤操作。
请读者尽可能准备一个源码版的。可以在github上获取。
在.Build.cs文件的PrivateDependencyModuleNames中,添加到Slate\SlateCore和StandaloneRenderer模块的引用。前两者时为了提供Slate访问的接口,很好理解,后者是为了让Slate能够独立于整个引擎渲染(可以看作一个轻量级的Slate窗口渲染器)。当你修改完之后的代码应该是这样:
PrivateDependencyModuleNames.AddRange(
new string[]{
"Core"
"Slate",
"SlateCore",
"StandaloneRenderer"
}
)
在BlankProgram.h头文件中加入:
#include "SlateBasics.h"
#include "StandaloneRenderer.h"
#include "SlateApplication.h"
为了能够创建一个Windows窗口,我们必须要让引擎的入口点设置为WinMain,修改Main函数定义为:
int
WINAPI WinMain(
_In_ HINSTANCE hInIstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR,
_In_ int
nCmdShow
)
在BlankProgram.cpp 最开头添加上一行LOCTEXT_NAMESPACE定义,内容随意,这是为了适配虚幻引擎的本地化机制。
此时GEngineLoop的初始化会缺少参数,因此需要略微修改,之后添加以下代码以启动SlateApplication,并进入消息循环:
GEngineLoop.PreInit(GetCommandLineW());
FSlateApplication::InitializAsStandlongApplication(
GetStandaloneRenderer()
);
while(!GIsRequestingExit)
{
FSlateApplication::Get().Tick();
FSlateApplication::Get().PumpMessages();
}
由于Slate需要CoreUObject模块中的一些定义,因此需要在.Target.cs文件中把链接选项设置为true,同时顺便也把编译为命令行应用的设置为false,即:
UEBuildConfiguration.bCompileAgainstCoreUObject = true;
OutLinkEnvironmentConfiguration.bIsBuildingConsoleApplication = false;
为了让我们的Slate系统打开一个窗口,我们需要手动增加一个Window。在FSlateApplication初始化后,添加一下代码:
TSharedPtrMainWindow=
SNew(SWindow)
.ClientSize(FVector2D(800,200));
FSlateApplication::Get().AddWindow(MainWindow,ToShareRef());
如果一切顺利,最终完成的代码应该是这样的:
#pragma once
#inlcude "Core.h"
#include "SlateBasics.h"
#include "StandaloneRenderer.h"
#include "SlateApplication.h"
#include "BlankProgram.h"
#include "RequiredProgramMainCPPInclude.h"
DEFINE_LOG_CATEGORY_STATIC(LogBlankProgram,Log,All);
IMPLEMENT_APPLICATION(BlankProgram,"BlankProgram");
int
WINAPI WinMain(_In_ HINSTANCE hInIstance,_In_opt_ HINSTANCE hPrevInstance,_in_ LPSTR,In_ int nCmdShow)
{
GEngineLoop.PreInit(GetCommandLineW());
FSlateApplication::InitializeAstandaloneApplication(
GetStandardStandaloneRenderer()
);
TSharedPtr MainWindow=SNew(SWindow)
.ClientSize(FVector2D(800,200));
FSlateApplication::Get().AddWindow(MainWindow.ToSharedRef());
while(!GIsRequestingExit)
{
FSlateApplication::Get().Tick();
FSlateApplication::Get().PumpMessages();
}
return 0;
}
退出VisualStudio,然后进入你的源码版引擎目录,运行里面的GenerateProjectFiles批处理文件,以调用UBT更新工程文件的设置与依赖。笔者目前不确定这一步是否必须执行,但笔者每次都用这样的方式刷新了工程文件。
在BlankProgram(或者你自己复制出来的项目)上单击鼠标右键,设置为启动项目,然后单击鼠标右键,构建当前项目。构建完成后,直接运行。不出意外,你会看到一个弹出的Slate窗口。
此时就有读者想得更远,希望能够不依赖虚幻引擎运行自己写好的引擎独立应用程序。同时也为了验证笔者刚刚说的是不是真的——真的不需要引擎来运行吗?
一个最简单的方式就是去\Engine\Binaries\Win64 文件夹下找到BlankProgram.exe,拷贝到你的Epic运行器文件夹对应的目录。没错,就是虚幻引擎哪个登录账号的运行器的目录。在笔者电脑上,它位于Epic Games\Launcher\Engine\Binaries\Win64。双击一下,你依然会发现程序能正常工作,验证了笔者的说法:你的程序不需要依赖一个引擎也能运行。
需要注意的是,这种应用程序依赖特定文件结构和Shader着色器,所以如果你真的希望把你的程序交给没有虚幻引擎的人使用,笔者由衷建议直接打包运行器,作为已经配置完成的一栏,会大幅度简化你的工作