[UE4C++程序]GameModule与Plugin

本文介绍了UE4引擎中模块系统的运作原理及应用方法,包括不同模块类型的定义与区别,以及如何创建自定义模块来提高项目管理和编译效率。

当专案建立的时候,引擎会自动产生一个同名的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.  
  4.  
  5.  
  6. #define IMPLEMENT_GAME_MODULE ( ModuleImplClass, ModuleName ) \
  7.  
  8. IMPLEMENT_MODULE ( ModuleImplClass, ModuleName )
  9.  
  10.  
  11.  
  12. #define IMPLEMENT_MODULE ( ModuleImplClass, ModuleName ) \
  13.  
  14. extern “C" DLLEXPORT IModuleInterface * InitializeModule () \
  15.  
  16. { \
  17.  
  18. return new ModuleImplClass(); \
  19.  
  20. } \
  21.  
  22. PER_MODULE_BOILERPLATE \
  23.  
  24. 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.  
  3.  
  4. #define IMPLEMENT_APPLICATION ( ModuleName , GameName ) \
  5.  
  6. /* For monolithic builds, we must statically define the game's name string (See Core.h) */ \
  7.  
  8. TCHAR GInternalGameName [64] = TEXT ( GameName ); \
  9.  
  10. IMPLEMENT_DEBUGGAME () \
  11.  
  12. IMPLEMENT_FOREIGN_ENGINE_DIR () \
  13.  
  14. IMPLEMENT_GAME_MODULE ( FDefaultGameModuleImpl , ModuleName ) \
  15.  
  16. PER_MODULE_BOILERPLATE \
  17.  
  18. FEngineLoop GEngineLoop ;
  19.  
  20.  
  21.  
  22. #else
  23.  
  24.  
  25.  
  26. #define IMPLEMENT_APPLICATION ( ModuleName , GameName ) \
  27.  
  28. /* For non-monolithic programs, we must set the game's name string before main starts (See Core.h) */ \
  29.  
  30. struct FAutoSet##ModuleName \
  31.  
  32. { \
  33.  
  34. FAutoSet##ModuleName() \
  35.  
  36. { \
  37.  
  38. FCString :: Strncpy ( GInternalGameName , TEXT ( GameName ), ARRAY_COUNT ( GInternalGameName )); \
  39.  
  40. } \
  41.  
  42. } AutoSet##ModuleName; \
  43.  
  44. PER_MODULE_BOILERPLATE \
  45.  
  46. PER_MODULE_BOILERPLATE_ANYLINK ( FDefaultGameModuleImpl , ModuleName ) \
  47.  
  48. FEngineLoop GEngineLoop ;
  49.  
  50.  
  51.  
  52. #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
这里写图片描述 
 

<think>我们正在处理的是在Unreal Engine 4C++环境中调用Python函数并获取返回值的问题。根据用户提供的引用,特别是引用[1]和引用[3]中展示了如何在UE4中通过Python调用C++函数,但用户的需求是反向的:在C++中调用Python函数并获取返回值。 在UE4中,提供了对Python的集成支持(通过PythonScriptPlugin),我们可以利用UE4提供的Python API来实现。UE4的Python API是对标准Python C API的封装,并添加了一些UE4特有的功能。 步骤: 1. 确保启用了Python插件:在UE4编辑器中,需要启用“Python Editor Script Plugin”和“Editor Scripting Utilities”插件。 2. 在C++代码中,我们可以使用UE4提供的FPythonScriptPlugin来执行Python代码或调用Python函数。 具体步骤: - 初始化Python环境(通常UE4编辑器启动时会自动初始化)。 - 导入Python模块。 - 获取Python函数并调用。 - 处理返回值。 注意:UE4的Python集成可能有一些限制,比如在非编辑器环境下(如打包后的游戏)可能不可用。 示例代码: 假设我们有一个Python模块`mymodule.py`,其中包含一个函数`add(a, b)`,我们想在C++中调用它并获取返回值。 ```cpp #include "PythonScriptPlugin.h" #include "PyUtil.h" #include "PyConversion.h" // 调用Python函数并获取返回值的函数 int32 CallPythonFunction(int32 a, int32 b) { // 确保Python插件已加载 FPythonScriptPlugin* PythonPlugin = FPythonScriptPlugin::Get(); if (!PythonPlugin || !PythonPlugin->IsPythonAvailable()) { UE_LOG(LogTemp, Error, TEXT("Python is not available.")); return -1; } // 导入模块 PyObject* pModule = PyImport_ImportModule("mymodule"); if (!pModule) { UE_LOG(LogTemp, Error, TEXT("Failed to import module.")); PyUtil::PrintPythonErrors(); return -1; } // 获取函数 PyObject* pFunc = PyObject_GetAttrString(pModule, "add"); if (!pFunc || !PyCallable_Check(pFunc)) { UE_LOG(LogTemp, Error, TEXT("Function add not found or not callable.")); Py_XDECREF(pModule); PyUtil::PrintPythonErrors(); return -1; } // 准备参数:这里我们有两个整数参数 PyObject* pArgs = PyTuple_New(2); PyTuple_SetItem(pArgs, 0, PyLong_FromLong(a)); PyTuple_SetItem(pArgs, 1, PyLong_FromLong(b)); // 调用函数 PyObject* pResult = PyObject_CallObject(pFunc, pArgs); Py_XDECREF(pArgs); Py_XDECREF(pFunc); Py_XDECREF(pModule); if (!pResult) { UE_LOG(LogTemp, Error, TEXT("Function call failed.")); PyUtil::PrintPythonErrors(); return -1; } // 处理返回值:假设返回整数 if (!PyLong_Check(pResult)) { UE_LOG(LogTemp, Error, TEXT("Return value is not integer.")); Py_XDECREF(pResult); return -1; } int32 Result = PyLong_AsLong(pResult); Py_XDECREF(pResult); return Result; } ``` 注意事项: 1. 引用计数管理:每次使用PyObject后,需要正确管理引用计数,使用`Py_XDECREF`来释放。 2. 错误处理:使用`PyUtil::PrintPythonErrors()`(UE4提供的工具函数)来打印Python错误信息。 3. 模块路径:确保Python模块位于UE4可以找到的路径中(例如,项目目录下的`Content/Script`文件夹)。 4. 线程安全:在游戏线程中调用,因为Python集成可能不是线程安全的。 在UE4中调用Python的另一种方式是使用`FPythonScriptPlugin`的`ExecPythonCommand`或`ExecPythonString`,但这些方法主要用于执行字符串形式的Python代码,并不直接方便获取返回值。因此,直接使用Python C API更灵活。 关于返回值类型:上述示例假设返回整数。如果返回其他类型(如字符串、列表、字典等),需要使用相应的转换函数。UE4提供了一些辅助函数,例如`PyConversion::PyObjectToUE4`(但注意,这些函数可能不是对所有类型都支持)。对于复杂类型,可能需要手动转换。 例如,如果返回字符串,可以这样处理: ```cpp if (PyUnicode_Check(pResult)) { const char* ResultStr = PyUnicode_AsUTF8(pResult); FString Result = UTF8_TO_TCHAR(ResultStr); // 使用Result... } ``` 总结: 在UE4C++中调用Python函数并获取返回值,主要步骤标准Python C API类似,但需要依赖UE4的Python插件,并注意UE4提供的工具函数和错误处理方式。 §§相关问题§§ 1. 在UE4中如何设置Python模块的搜索路径? 2. 如何处理Python函数返回的复杂类型(如UE4的FVector)? 3. 在UE4的非编辑器环境下(如打包的游戏)能否使用Python调用?</think>在 Unreal Engine 4C++ 环境中调用 Python 函数并获取返回值,可以通过 UE4 提供的 Python 集成功能实现。以下是详细步骤和最佳实践: ### 核心步骤(基于 UE4 Python 集成) 1. **启用 Python 支持** - 在编辑器中启用插件:`Python Editor Script Plugin` - 在项目设置中开启开发者模式:`项目设置 > Python > 启用开发者模式` 2. **创建 Python 脚本** 在 `Content/Python` 目录下创建 `mymodule.py`: ```python import unreal def calculate(a, b): return a * b # 示例计算函数 ``` 3. **C++ 调用 Python 并获取返回值** ```cpp #include "PyUtil.h" // UE4 Python工具头文件 #include "PythonScriptTypes.h" void CallPythonFunction() { // 初始化Python环境 FPythonScriptPlugin::Get()->Initialize(); // 导入Python模块 PyObject* pModule = PyImport_ImportModule("mymodule"); if (!pModule) { UE_LOG(LogTemp, Error, TEXT("Failed to import module")); return; } // 获取函数对象 PyObject* pFunc = PyObject_GetAttrString(pModule, "calculate"); if (!pFunc || !PyCallable_Check(pFunc)) { UE_LOG(LogTemp, Error, TEXT("Function not found")); Py_XDECREF(pModule); return; } // 准备参数 (两个整数) PyObject* pArgs = PyTuple_New(2); PyTuple_SetItem(pArgs, 0, PyLong_FromLong(5)); PyTuple_SetItem(pArgs, 1, PyLong_FromLong(3)); // 调用函数并获取返回值 PyObject* pResult = PyObject_CallObject(pFunc, pArgs); Py_XDECREF(pArgs); Py_XDECREF(pFunc); Py_XDECREF(pModule); if (!pResult) { UE_LOG(LogTemp, Error, TEXT("Function call failed")); return; } // 处理返回值 if (PyLong_Check(pResult)) { int32 Result = PyLong_AsLong(pResult); UE_LOG(LogTemp, Display, TEXT("Result: %d"), Result); // 输出: Result: 15 } Py_XDECREF(pResult); } ``` ### 关键注意事项 1. **函数暴露要求** - 确保 Python 函数是顶级函数(非类方法) - 使用 `import unreal` 保证 UE4 Python 环境初始化[^2] 2. **返回值类型处理** 根据返回类型使用不同的转换方法: ```cpp // 整数 if (PyLong_Check(pResult)) { int32 Value = PyLong_AsLong(pResult); } // 浮点数 if (PyFloat_Check(pResult)) { float Value = PyFloat_AsDouble(pResult); } // 字符串 if (PyUnicode_Check(pResult)) { const char* StrValue = PyUnicode_AsUTF8(pResult); FString Result = UTF8_TO_TCHAR(StrValue); } ``` 3. **蓝图可调用限制** 如果需要从蓝图调用,需额外声明: ```cpp UFUNCTION(BlueprintCallable, Category="Python") static int32 ExecutePythonCalculation(int32 A, int32 B); ``` 注意某些元数据限制可能阻止 Python 访问[^2] ### 最佳实践 1. **错误处理** ```cpp if (PyErr_Occurred()) { PyErr_Print(); // 打印Python异常 PyErr_Clear(); // 清除错误状态 } ``` 2. **路径配置** 在 `DefaultEngine.ini` 中添加: ```ini [/Script/PythonScriptPlugin.PythonScriptPluginSettings] DeveloperMode=true AdditionalPaths=/Game/Python ``` 3. **异步调用(避免阻塞游戏线程)** ```cpp Async(EAsyncExecution::Thread, [](){ FPythonScriptPlugin::Get()->ExecutePythonCommand(TEXT("import mymodule; result = mymodule.calculate(5, 3)")); }); ``` ### 完整示例(C++ 类封装) ```cpp // MyPythonWrapper.h #pragma once #include "Kismet/BlueprintFunctionLibrary.h" #include "MyPythonWrapper.generated.h" UCLASS() class UMyPythonWrapper : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category="Python") static int32 PythonCalculate(int32 A, int32 B); }; // MyPythonWrapper.cpp #include "MyPythonWrapper.h" #include "PyUtil.h" int32 UMyPythonWrapper::PythonCalculate(int32 A, int32 B) { FPythonScriptPlugin::Get()->Initialize(); PyObject* pModule = PyImport_ImportModule("mymodule"); // ... 完整调用逻辑(参考前文)... return Result; // 返回整数结果 } ``` ### 常见问题解决 1. **模块导入失败** - 确认脚本在 `Content/Python` 目录 - 检查文件编码为 UTF-8 无 BOM 2. **返回值类型错误** - 使用 `PyObject_TypeCheck(pResult, &PyFloat_Type)` 精确检查类型 - 复杂类型建议返回 JSON 字符串 3. **内存泄漏预防** - 所有 `PyObject*` 必须用 `Py_XDECREF` 释放 - 使用 `TSharedPtr<PyObject>` 自定义智能指针管理 > 提示:在 UE4.26+ 中可直接使用 `unreal.log` 在 Python 中输出日志到 UE4 控制台[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值