第9章 插件解析
9.1 开发属于您自己的插件
在游戏开发领域,插件系统是一种强大的机制,允许开发者扩展和自定义游戏功能,同时保持核心游戏代码的稳定性。虽然这一章原本针对魔兽世界插件开发,我们将这些概念应用到UE5.2中,探讨如何创建和管理游戏插件系统。
9.1.1 文件夹结构
在UE5.2中,插件拥有清晰的文件夹结构,这有助于组织代码和资源。理解这个结构对于创建自己的插件至关重要。
UE5.2中的插件文件夹结构:
ebnf
MyProject/
Plugins/
MyPlugin/
Source/
MyPlugin/
Private/ # 私有源文件
Public/ # 公共头文件
Resources/ # 图标等资源
MyPlugin.Build.cs # 编译配置
Content/ # 插件内容资源
Config/ # 配置文件
MyPlugin.uplugin # 插件描述文件
创建插件的C++代码:
cpp
// MyPlugin.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FMyPluginModule : public IModuleInterface
{
public:
// IModuleInterface接口实现
virtual void StartupModule() override;
virtual void ShutdownModule() override;
// 插件功能
void ExecutePluginFunction();
// 获取单例实例
static FMyPluginModule& Get()
{
return FModuleManager::LoadModuleChecked<FMyPluginModule>("MyPlugin");
}
};
cpp
// MyPlugin.cpp
#include "MyPlugin.h"
#include "Misc/Paths.h"
#define LOCTEXT_NAMESPACE "FMyPluginModule"
void FMyPluginModule::StartupModule()
{
// 插件启动代码
UE_LOG(LogTemp, Log, TEXT("MyPlugin module has started!"));
// 注册资源路径
FString ContentDir = FPaths::Combine(FPaths::ProjectPluginsDir(), TEXT("MyPlugin"), TEXT("Content"));
FString MountPoint = TEXT("/MyPlugin/");
FPackageName::RegisterMountPoint(MountPoint, ContentDir);
}
void FMyPluginModule::ShutdownModule()
{
// 插件关闭代码
UE_LOG(LogTemp, Log, TEXT("MyPlugin module has shutdown."));
}
void FMyPluginModule::ExecutePluginFunction()
{
// 示例插件功能
UE_LOG(LogTemp, Log, TEXT("Plugin function executed!"));
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)
在蓝图中使用插件功能:
9.1.2 暴雪的插件
暴雪的插件系统提供了许多值得借鉴的设计理念,比如模块化设计、简洁的API和强大的用户界面框架。UE5.2中可以实现类似的功能。
UE5.2中的模块化插件设计:
cpp
// ModularPlugin.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
// 模块接口定义
class IPluginModule : public IModuleInterface
{
public:
virtual ~IPluginModule() {}
// 模块必须实现的核心功能
virtual void Initialize() = 0;
virtual FString GetModuleName() const = 0;
virtual void RegisterCommands() = 0;
};
// 主插件模块
class FModularPluginModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
// 注册子模块
void RegisterSubmodule(TSharedPtr<IPluginModule> Submodule);
// 获取子模块
template<typename T>
TSharedPtr<T> GetSubmodule(const FString& ModuleName);
private:
// 存储子模块
TMap<FString, TSharedPtr<IPluginModule>> Submodules;
};
cpp
// ModularPlugin.cpp
#include "ModularPlugin.h"
void FModularPluginModule::StartupModule()
{
// 初始化所有子模块
for (auto& Pair : Submodules)
{
Pair.Value->Initialize();
Pair.Value->RegisterCommands();
}
}
void FModularPluginModule::ShutdownModule()
{
// 清理子模块
Submodules.Empty();
}
void FModularPluginModule::RegisterSubmodule(TSharedPtr<IPluginModule> Submodule)
{
if (Submodule.IsValid())
{
Submodules.Add(Submodule->GetModuleName(), Submodule);
}
}
template<typename T>
TSharedPtr<T> FModularPluginModule::GetSubmodule(const FString& ModuleName)
{
TSharedPtr<IPluginModule>* FoundModule = Submodules.Find(ModuleName);
if (FoundModule && FoundModule->IsValid())
{
return StaticCastSharedPtr<T>(*FoundModule);
}
return nullptr;
}
IMPLEMENT_MODULE(FModularPluginModule, ModularPlugin)
9.1.3 自定义插件
自定义插件允许开发者创建特定于游戏的功能扩展。UE5.2提供了完整的框架支持自定义插件开发。
UE5.2中的自定义插件示例:
cpp
// CustomGameplayPlugin.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "GameplayPlugin.generated.h"
// 自定义游戏功能结构体
USTRUCT(BlueprintType)
struct FCustomGameplayFeature
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay")
FString FeatureName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay")
float FeatureValue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay")
bool bIsEnabled;
};
// 自定义游戏功能组件
UCLASS(Blueprintable, meta=(BlueprintSpawnableComponent))
class UCustomGameplayComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCustomGameplayComponent();
// 组件功能
UFUNCTION(BlueprintCallable, Category = "Gameplay")
void ActivateFeature(const FString& FeatureName);
UFUNCTION(BlueprintCallable, Category = "Gameplay")
void DeactivateFeature(const FString& FeatureName);
UFUNCTION(BlueprintPure, Category = "Gameplay")
bool IsFeatureActive(const FString& FeatureName) const;
private:
// 存储游戏功能
UPROPERTY()
TMap<FString, FCustomGameplayFeature> GameplayFeatures;
};
// 插件主模块
class FCustomGameplayPluginModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
cpp
// CustomGameplayPlugin.cpp
#include "CustomGameplayPlugin.h"
UCustomGameplayComponent::UCustomGameplayComponent()
{
PrimaryComponentTick.bCanEverTick = true;
// 添加默认功能
FCustomGameplayFeature DoubleJump;
DoubleJump.FeatureName = "DoubleJump";
DoubleJump.FeatureValue = 1.5f; // 跳跃高度倍数
DoubleJump.bIsEnabled = false;
GameplayFeatures.Add(DoubleJump.FeatureName, DoubleJump);
FCustomGameplayFeature SpeedBoost;
SpeedBoost.FeatureName = "SpeedBoost";
SpeedBoost.FeatureValue = 2.0f; // 速度倍数
SpeedBoost.bIsEnabled = false;
GameplayFeatures.Add(SpeedBoost.FeatureName, SpeedBoost);
}
void UCustomGameplayComponent::ActivateFeature(const FString& FeatureName)
{
if (GameplayFeatures.Contains(FeatureName))
{
GameplayFeatures[FeatureName].bIsEnabled = true;
UE_LOG(LogTemp, Log, TEXT("Feature activated: %s"), *FeatureName);
}
}
void UCustomGameplayComponent::DeactivateFeature(const FString& FeatureName)
{
if (GameplayFeatures.Contains(FeatureName))
{
GameplayFeatures[FeatureName].bIsEnabled = false;
UE_LOG(LogTemp, Log, TEXT("Feature deactivated: %s"), *FeatureName);
}
}
bool UCustomGameplayComponent::IsFeatureActive(const FString& FeatureName) const
{
if (GameplayFeatures.Contains(FeatureName))
{
return GameplayFeatures[FeatureName].bIsEnabled;
}
return false;
}
void FCustomGameplayPluginModule::StartupModule()
{
UE_LOG(LogTemp, Log, TEXT("Custom Gameplay Plugin started"));
}
void FCustomGameplayPluginModule::ShutdownModule()
{
UE_LOG(LogTemp, Log, TEXT("Custom Gameplay Plugin shutdown"));
}
IMPLEMENT_MODULE(FCustomGameplayPluginModule, CustomGameplayPlugin)
在蓝图中使用自定义游戏功能:
9.2 插件组件和文件
每个UE5.2插件由多个组件和文件组成,包括插件描述文件、源代码、配置和资源。了解这些组件如何协同工作对于创建有效的插件至关重要。
9.2.1 内容表格文件(.uplugin)
UE5.2中,.uplugin
文件是插件的描述文件,类似于暴雪插件中的.toc
文件。它定义了插件的基本信息和依赖项。
插件描述文件示例:
json
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "My Game Plugin",
"Description": "A plugin that adds custom gameplay features",
"Category": "Gameplay",
"CreatedBy": "Your Name",
"CreatedByURL": "https://example.com",
"DocsURL": "https://example.com/docs",
"MarketplaceURL": "",
"SupportURL": "https://example.com/support",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "MyPlugin",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "MyPluginEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
],
"Plugins": [
{
"Name": "EnhancedInput",
"Enabled": true
}
]
}
C++中处理插件信息:
cpp
// PluginInfoUtility.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "PluginInfoUtility.generated.h"
USTRUCT(BlueprintType)
struct FPluginInfo
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Plugin Info")
FString Name;
UPROPERTY(BlueprintReadOnly, Category = "Plugin Info")
FString Version;
UPROPERTY(BlueprintReadOnly, Category = "Plugin Info")
FString Description;
UPROPERTY(BlueprintReadOnly, Category = "Plugin Info")
FString Author;
UPROPERTY(BlueprintReadOnly, Category = "Plugin Info")
bool bEnabled;
};
UCLASS()
class UPluginInfoUtility : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// 获取插件信息
UFUNCTION(BlueprintCallable, Category = "Plugin Utility")
static FPluginInfo GetPluginInfo(const FString& PluginName);
// 检查插件是否已启用
UFUNCTION(BlueprintPure, Category = "Plugin Utility")
static bool IsPluginEnabled(const FString& PluginName);
// 获取所有已安装的插件
UFUNCTION(BlueprintCallable, Category = "Plugin Utility")
static TArray<FPluginInfo> GetAllInstalledPlugins();
};
cpp
// PluginInfoUtility.cpp
#include "PluginInfoUtility.h"
#include "Interfaces/IPluginManager.h"
FPluginInfo UPluginInfoUtility::GetPluginInfo(const FString& PluginName)
{
FPluginInfo Info;
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(PluginName);
if (Plugin.IsValid())
{
Info.Name = Plugin->GetName();
Info.Version = Plugin->GetDescriptor().VersionName;
Info.Description = Plugin->GetDescriptor().Description;
Info.Author = Plugin->GetDescriptor().CreatedBy;
Info.bEnabled = Plugin->IsEnabled();
}
return Info;
}
bool UPluginInfoUtility::IsPluginEnabled(const FString& PluginName)
{
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(PluginName);
return Plugin.IsValid() && Plugin->IsEnabled();
}
TArray<FPluginInfo> UPluginInfoUtility::GetAllInstalledPlugins()
{
TArray<FPluginInfo> PluginInfos;
TArray<TSharedRef<IPlugin>> Plugins = IPluginManager::Get().GetDiscoveredPlugins();
for (const TSharedRef<IPlugin>& Plugin : Plugins)
{
FPluginInfo Info;
Info.Name = Plugin->GetName();
Info.Version = Plugin->GetDescriptor().VersionName;
Info.Description = Plugin->GetDescriptor().Description;
Info.Author = Plugin->GetDescriptor().CreatedBy;
Info.bEnabled = Plugin->IsEnabled();
PluginInfos.Add(Info);
}
return PluginInfos;
}
在蓝图中获取插件信息:
9.2.2 Lua 脚本文件
虽然UE5.2默认不使用Lua,但我们可以集成Lua作为插件系统的一部分,允许开发者使用Lua脚本扩展游戏功能。
在UE5.2中集成Lua:
cpp
// LuaPlugin.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "LuaState.h"
class FLuaPluginModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
// 获取Lua状态
TSharedPtr<FLuaState> GetLuaState() const { return LuaState; }
// 执行Lua脚本文件
bool ExecuteFile(const FString& FilePath);
// 执行Lua字符串
bool ExecuteString(const FString& LuaCode);
// 注册C++函数到Lua
template<typename FuncType>
void RegisterFunction(const FString& Name, FuncType Function);
private:
// Lua状态
TSharedPtr<FLuaState> LuaState;
// 初始化Lua环境
void InitializeLua();
// 注册核心API
void RegisterAPI();
};
cpp
// LuaState.h
#pragma once
#include "CoreMinimal.h"
// Lua头文件(需要添加到构建系统)
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
class FLuaState
{
public:
FLuaState();
~FLuaState();
// 获取原始Lua状态
lua_State* GetState() const { return L; }
// 执行文件
bool DoFile(const FString& FilePath);
// 执行字符串
bool DoString(const FString& Code);
// 调用Lua函数
bool CallFunction(const FString& FunctionName, int NumArgs = 0, int NumResults = 0);
// 压入值到栈
void PushString(const FString& String);
void PushNumber(double Number);
void PushBoolean(bool Value);
// 从栈读取值
FString ToString(int Index);
double ToNumber(int Index);
bool ToBoolean(int Index);
private:
lua_State* L;
};
cpp
// LuaState.cpp
#include "LuaState.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
FLuaState::FLuaState()
{
// 创建Lua状态
L = luaL_newstate();
// 打开标准库
luaL_openlibs(L);
}
FLuaState::~FLuaState()
{
// 关闭Lua状态
if (L)
{
lua_close(L);
L = nullptr;
}
}
bool FLuaState::DoFile(const FString& FilePath)
{
if (!L) return false;
// 读取文件内容
FString Content;
if (!FFileHelper::LoadFileToString(Content, *FilePath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to load Lua file: %s"), *FilePath);
return false;
}
// 执行内容
return DoString(Content);
}
bool FLuaState::DoString(const FString& Code)
{
if (!L) return false;
// 执行Lua代码
if (luaL_dostring(L, TCHAR_TO_UTF8(*Code)) != 0)
{
FString Error = FString(UTF8_TO_TCHAR(lua_tostring(L, -1)));
UE_LOG(LogTemp, Error, TEXT("Lua error: %s"), *Error);
lua_pop(L, 1);
return false;
}
return true;
}
bool FLuaState::CallFunction(const FString& FunctionName, int NumArgs, int NumResults)
{
if (!L) return false;
// 获取函数
lua_getglobal(L, TCHAR_TO_UTF8(*FunctionName));
// 检查是否为函数
if (!lua_isfunction(L, -1))
{
UE_LOG(LogTemp, Error, TEXT("Lua function not found: %s"), *FunctionName);
lua_pop(L, 1);
return false;
}
// 移动函数到参数之前
lua_insert(L, -(NumArgs + 1));
// 调用函数
if (lua_pcall(L, NumArgs, NumResults, 0) != 0)
{
FString Error = FString(UTF8_TO_TCHAR(lua_tostring(L, -1)));
UE_LOG(LogTemp, Error, TEXT("Lua error: %s"), *Error);
lua_pop(L, 1);
return false;
}
return true;
}
void FLuaState::PushString(const FString& String)
{
lua_pushstring(L, TCHAR_TO_UTF8(*String));
}
void FLuaState::PushNumber(double Number)
{
lua_pushnumber(L, Number);
}
void FLuaState::PushBoolean(bool Value)
{
lua_pushboolean(L, Value ? 1 : 0);
}
FString FLuaState::ToString(int Index)
{
const char* Str = lua_tostring(L, Index);
return Str ? FString(UTF8_TO_TCHAR(Str)) : FString();
}
double FLuaState::ToNumber(int Index)
{
return lua_tonumber(L, Index);
}
bool FLuaState::ToBoolean(int Index)
{
return lua_toboolean(L, Index) != 0;
}
cpp
// LuaPlugin.cpp
#include "LuaPlugin.h"
#include "Misc/Paths.h"
void FLuaPluginModule::StartupModule()
{
// 初始化Lua
InitializeLua();
// 注册API
RegisterAPI();
UE_LOG(LogTemp, Log, TEXT("Lua Plugin module started"));
}
void FLuaPluginModule::ShutdownModule()
{
// 清理Lua状态
LuaState.Reset();
UE_LOG(LogTemp, Log, TEXT("Lua