当专案建立的时候,引擎会自动产生一个同名的Game Module在Source资料夹底下。我们当然可以将所有撰写的C++类别全部放在这个Module中,可是当专案越来越大,若还是将所有的功能都放在同个Module下,不仅仅会造成管理上的混乱,而且编译时间也会增加。

试着想像当我们随便改动一个h档或cpp档的参数就要编译几10分钟的情况?由于UE4在一个Module中的cpp数量到达32个的时候就会自动启动编译档案合并的机制(Unity Build),虽然UE4中有参数可以控制这个机制是否启用,但却是舍弃掉了整体的编译时间。因此,如何将功能适当的切到各别Module中,就变成了一件非常重要的程式架构设计问题。

那么实际上UE4的Module系统是怎么运作的呢?

每一个独立的Module基本上是由${MODULE_NAME}.build.cs与其下的C++程式码所组成,而在UE4中,游戏中的Module主要被分成以下几种实作Macro定义:

  • IMPLEMENT_PRIMARY_GAME_MODULE
  • IMPLEMENT_GAME_MODULE
  • IMPLEMENT_MODULE

到底这些实作彼此之间有什么不同?

其实,这些Macro定义在最后都会被展开成IMPLEMENT_MODULE中的实作,它们之间在本质上并没有不同,主要是用提供Module初始化Function的实作给UE4中的ModuleManager呼叫,参照Code 2.4.1:

  1. #define IMPLEMENT_PRIMARY_GAME_MODULE ( ModuleImplClass, ModuleName , GameName ) \
  2. IMPLEMENT_GAME_MODULE ( ModuleImplClass, ModuleName )
  3. #define IMPLEMENT_GAME_MODULE ( ModuleImplClass, ModuleName ) \
  4. IMPLEMENT_MODULE ( ModuleImplClass, ModuleName )
  5. #define IMPLEMENT_MODULE ( ModuleImplClass, ModuleName ) \
  6. extern “C" DLLEXPORT IModuleInterface * InitializeModule () \
  7. { \
  8. return new ModuleImplClass(); \
  9. } \
  10. PER_MODULE_BOILERPLATE \
  11. PER_MODULE_BOILERPLATE_ANYLINK (ModuleImplClass, ModuleName )

Code 2.4.1 Module的定义Macro,最后都会被展开成IMPLEMENT_MODULE中的实作。其中PER_MODULE_BOILERPLATE让引擎有机会提供一些额外的功能给这个module,例如把原本C++中的New跟delete做Overriding,并将功能导向UE4中的记忆体管理机制。最后的PER_MODULE_BOILERPLATE_ANYLINK其实并没有其他特别的功能,就只是用来做标示,用来说明这个Module相关的定义已经完成。

从上面的程式码中我们可以看出,从Primary Game Module、Game Module到Module总共有三个层级的定义。只是,既然这些Module的定义做的事情都差不多,那为何引擎还要个别提供?

这边虽然没办法完全的掌握设计者的意图,但至少我们可以推敲出区分出这些Module定义名称的好处:若以后想要在Primary Game Module或Game Module层级增加功能的时候,就不会影响到最下层的Module层级的实作。

到这里,或许有人会开始思考这几个层级到底应该用在哪些地方。其实UE4在架构上把程式码拆分成以下几个部份:

  • Engine:位于${UE4_ENGINE_ROOT}/Engine/Source/下面的Developer、Editor、Runtime以及ThirdParty这几个资料夹。
  • Engine Plugin:位于${UE4_ENGINE_ROOT}/Engine/Plugin/。
  • Game:位于${PROJECT_NAME}/Source/。
  • Game Plugin:位于${PROJECT_NAME}/Plugin/。
  • Programs:位于${UE4_ENGINE_ROOT}/Engine/Source/Programs,属于独立运行的工具类程式,里面使用C#或C++分别进行不同的实作。

其中只有位于Game中的Module会使用IMPLEMENT_PRIMARY_GAME_MODULE跟IMPLEMENT_GAME_MODULE这2个实作,从名称中我们可以推测出,作为主要游戏的入口的Primary Game Module只能有一个,其他则都必需设成Game Module。

其他在Engine、Engine Plugin跟Game Plugin底下的Module则都是直接使用IMPLEMENT_MODULE这个定义。

当然,就目前的引擎版本我们是可以全部都用IMPLEMENT_MODULE来实作出上面Module所有效果,但是为了往后更新引擎版本时的兼容性,建议还是照着UE4所定义出来的流程实作。

最后的Programs跟其他的Module不同,里面的C++ Module会使用IMPLEMENT_APPLICATION这个定义,参见Code 2.4.2。

  1. #if IS_MONOLITHIC
  2. #define IMPLEMENT_APPLICATION ( ModuleName , GameName ) \
  3. /* For monolithic builds, we must statically define the game's name string (See Core.h) */ \
  4. TCHAR GInternalGameName [64] = TEXT ( GameName ); \
  5. IMPLEMENT_DEBUGGAME () \
  6. IMPLEMENT_FOREIGN_ENGINE_DIR () \
  7. IMPLEMENT_GAME_MODULE ( FDefaultGameModuleImpl , ModuleName ) \
  8. PER_MODULE_BOILERPLATE \
  9. FEngineLoop GEngineLoop ;
  10. #else
  11. #define IMPLEMENT_APPLICATION ( ModuleName , GameName ) \
  12. /* For non-monolithic programs, we must set the game's name string before main starts (See Core.h) */ \
  13. struct FAutoSet##ModuleName \
  14. { \
  15. FAutoSet##ModuleName() \
  16. { \
  17. FCString :: Strncpy ( GInternalGameName , TEXT ( GameName ), ARRAY_COUNT ( GInternalGameName )); \
  18. } \
  19. } AutoSet##ModuleName; \
  20. PER_MODULE_BOILERPLATE \
  21. PER_MODULE_BOILERPLATE_ANYLINK ( FDefaultGameModuleImpl , ModuleName ) \
  22. FEngineLoop GEngineLoop ;
  23. #endif

Code 2.4.2 在UE4中,Module的Link分成Modular跟Monolithic二种方式:Modular指的是将Module编译成各别的dynamic library再做连结;Monolithic则是指将所有的Module全部编译到同一份Library中。其中Editor预设是Modular,而Game预设是Monolithic。

而要不要把该Module放到Plugin或者是当成Game Module,则要看这个Module需不需要用在不同的专案上使用。若是这个Module中的功能需要让其他专案使用的话,则将该Module制作成Plugin会比单纯的当成Game Module会更有弹性。

  • 在UE项目添加一个新的Module

本文只简单介绍下创建自定义模块的方法

1. 新建项目并在Source文件夹下建立我们要添加的新模块 
 
2. 并在新建的NewModule文件夹下创建.h .cpp .Build.cs 后缀的3个文件 
 
3进入VS建立NewModule(筛选器)并把刚才文件夹里新创建的3个文件拖入NewModule下 
 
4.为NewModule.Build.cs添加内容(直接从我们项目里的TestModule.Build.cs拷贝即可)

using UnrealBuildTool;

public class NewModule: ModuleRules
{
    public NewModule(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

PrivateDependencyModuleNames.AddRange(new string[] { });

// Uncomment if you are using Slate UI
        // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

// Uncomment if you are using online features
        // PrivateDependencyModuleNames.Add("OnlineSubsystem");

// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
    }
}

5. 修改NewModule.h为

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"
#include "ModuleManager.h"

class FNewModuleModule : public IModuleInterface
{
public:

  /** IModuleInterface implementation */
  virtual void StartupModule() override;
  virtual void ShutdownModule() override;
};

6.修改NewModule.cpp为

// Copyright Sigurdur Gunnarsson. All Rights Reserved. 
// Licensed under the MIT License. See LICENSE file in the project root for full license information. 

#include "NewModule.h"

void FNewModuleModule::StartupModule()
{
  // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}

void FNewModuleModule::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.
}

  
IMPLEMENT_MODULE(FNewModuleModule, NewModule)

6. 进入项目的uproject内添加我们的新模块 
 
7. 最后在项目里的.Target.cs里添加新的模块名 

2个.Target.cs后缀的都需要添加模块名

8. 使用Editor在NewModule下创建Actor,重新编译成功后,在C++ Class下就能看到新建的NewModule

[UE4C++程序]GameModule与Plugin相关推荐

  1. 一文搞懂Go语言的plugin

    要历数Go语言中还有哪些我还没用过的特性,在Go 1.8版本[1]中引入的go plugin[2]算一个.近期想给一个网关类平台设计一个插件系统,于是想起了go plugin^_^. Go plugi ...

  2. 程序员的自我修养--链接、装载与库笔记:动态链接

    1. 为什么要动态链接 静态链接诸多缺点,比如浪费内存和磁盘空间.模块更新困难等. 内存和磁盘空间:静态链接的方式对于计算机内存和磁盘的空间浪费非常严重,特别是在多进程操作系统情况下. 程序开发和发布 ...

  3. VR开发基础(一)一文理清unity xr plugin架构与openxr标准

    一,VR开发中的几个概念:从openVR到openXR 1. OpenVR OpenVR是Valve公司开发的一套包含一系列SDK和API的工具集,旨在从驱动层级为硬件厂商提供软硬件开发支持.硬件设备 ...

  4. 程序员的自我修养--链接、装载与库笔记:总结

    <程序员的自我修养----链接.装载与库>这本书是2009年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C/C++开发,而且书中展示的结果均是在32位机上进行的操作,这里全部 ...

  5. 编程精粹 --Microsoft编写优质无错C程序秘诀

    献给我的妻子Beth, 以及我的双亲Joseph和Julia Maguire ────为了他们的爱和支持 序 1986年,在为几家小公司咨询和工作了10年之后为了获得编写Macintosh应用程序的经 ...

  6. Quartz.Net—配置化

    Schedule配置 线程数量 如果一个Schedule中有很多任务,这样默认的10个线程就不够用了. 有很多种方法配置线程的个数. 工厂构造函数 webfonfig quartzconfig 环境变 ...

  7. -Objc 、 -all_load 、 -force_load

    标志 -Objc . -all_load . -force_load 笔记. 有时候经常会遇到在导入第三方库的时候需要在 Other Linker Flags 中添加 -Objc 标志.-all_lo ...

  8. Gradle 教程:第一部分,安装【翻译】

    原文地址:http://rominirani.com/2014/07/28/gradle-tutorial-part-1-installation-setup/ 在这篇教程里,我们将主要讲解如何在我们 ...

  9. JSP简单练习-JSP动作指令

    JSP动作指令在JSP程序设计中经常会用到,与JSP指令不同,它将影响JSP运行是的功能! 1.include动作指令: include动作指令用来在JSP页面中动态包含一个文件,这样包含页面程序与被 ...

最新文章

  1. 【python】向上取整 向下取整
  2. 正則表達式截取字符串两字符间的内容
  3. Bitcoin 中的挖矿算法(2) 难度值说明
  4. 三次握手和四次挥手之间的关系
  5. aspen二元体系共沸组分_超详细 | 手把手教你组分结构预测
  6. oracle 在线表分析报告,Oracle Statspack分析报告详解(一)
  7. 基于UML的面向对象分析与设计
  8. Python 常见的内置模块
  9. micropython webrepl_4-5 MicroPython WebREPL 命令行交互环境设置-2 接入点模式
  10. python页面切换_Python+Selenium学习--窗口切换及操作元素
  11. deepfake 资源总结
  12. vb写数据到mysql数据库_VB2010写入数据到access 2003数据库
  13. psd导出jpg太大_Adobe Photoshop CC2017保存技巧 (PS)导出文件过大问题解决
  14. 产品的思维与技术的思维差异
  15. 苹果大中华区营收同比增48% iPhone销量翻番
  16. STM32F7 硬件IIC驱动
  17. 2022年黄石市高企申报奖励补贴以及认定奖励补贴汇总!
  18. [电脑桌面右击新建没有excel、ppt、word]
  19. win7家庭版桌面没有计算机图标,Win7 home basic家庭普通版显示桌面图标的方法
  20. ctr 平滑_广告计算——平滑CTR

热门文章

  1. CanOpen协议栈学习笔记1-帧格式,SYNC和NMT报文介绍
  2. Python 使用 PyOTP 实现二步验证
  3. ORACLE EBS常用表查询语句
  4. 机动战士高达观影顺序
  5. uos网页服务器安装,安装uos
  6. 论文专利博客写作总结
  7. 实时填报推送微信消息及审核(帆软报表)
  8. Python实现统一社会信用代码校验(GB32100-2015)
  9. 转载-GNS3安装和使用教程(超详细)
  10. python画五角星-Python的画五角星