第14章 使用AddOn Studio和UE5创建基本插件
14.1 开始使用AddOn Studio
AddOn Studio是一个专为开发游戏插件设计的集成开发环境。虽然它最初是为魔兽世界插件开发而创建的,但我们可以借鉴其理念和工作流程,在UE5.2中构建类似的插件开发环境和工具链。
在UE5.2中,我们可以创建一个专门的插件框架,使开发者能够更轻松地创建、测试和部署游戏插件。
创建UE5.2插件开发环境
首先,让我们了解如何在UE5.2中设置插件开发环境:
cpp
// PluginDevEnvironment.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "PluginDevEnvironment.generated.h"
/**
* 插件开发环境子系统 - 提供类似AddOn Studio的功能
*/
UCLASS()
class MYGAME_API UPluginDevEnvironment : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// 初始化和清理
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// 创建新插件
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
bool CreateNewPlugin(const FString& PluginName, const FString& PluginDescription);
// 加载插件
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
bool LoadPlugin(const FString& PluginName);
// 卸载插件
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
bool UnloadPlugin(const FString& PluginName);
// 重新加载插件
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
bool ReloadPlugin(const FString& PluginName);
// 获取已加载插件列表
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
TArray<FString> GetLoadedPlugins() const;
// 获取可用插件列表
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
TArray<FString> GetAvailablePlugins() const;
// 检查插件是否已加载
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
bool IsPluginLoaded(const FString& PluginName) const;
// 打开插件编辑器
UFUNCTION(BlueprintCallable, Category = "Plugin Development")
void OpenPluginEditor(const FString& PluginName);
private:
// 已加载的插件映射
TMap<FString, class UPluginBase*> LoadedPlugins;
// 插件目录路径
FString PluginsDirectory;
// 获取插件路径
FString GetPluginPath(const FString& PluginName) const;
// 创建插件目录结构
bool CreatePluginDirectoryStructure(const FString& PluginName);
// 生成插件模板文件
bool GeneratePluginTemplateFiles(const FString& PluginName, const FString& PluginDescription);
};
cpp
// PluginDevEnvironment.cpp
#include "PluginDevEnvironment.h"
#include "PluginBase.h"
#include "Misc/FileHelper.h"
#include "HAL/PlatformFilemanager.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "Misc/Paths.h"
void UPluginDevEnvironment::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// 设置插件目录
PluginsDirectory = FPaths::ProjectPluginsDir();
// 确保目录存在
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*PluginsDirectory))
{
PlatformFile.CreateDirectory(*PluginsDirectory);
}
UE_LOG(LogTemp, Log, TEXT("Plugin Development Environment initialized. Plugin directory: %s"), *PluginsDirectory);
// 自动加载所有可用插件
TArray<FString> AvailablePlugins = GetAvailablePlugins();
for (const FString& PluginName : AvailablePlugins)
{
LoadPlugin(PluginName);
}
}
void UPluginDevEnvironment::Deinitialize()
{
// 卸载所有已加载的插件
TArray<FString> PluginsToUnload;
LoadedPlugins.GetKeys(PluginsToUnload);
for (const FString& PluginName : PluginsToUnload)
{
UnloadPlugin(PluginName);
}
LoadedPlugins.Empty();
Super::Deinitialize();
}
bool UPluginDevEnvironment::CreateNewPlugin(const FString& PluginName, const FString& PluginDescription)
{
if (PluginName.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("Cannot create plugin with empty name"));
return false;
}
// 检查插件是否已存在
if (GetAvailablePlugins().Contains(PluginName))
{
UE_LOG(LogTemp, Warning, TEXT("Plugin '%s' already exists"), *PluginName);
return false;
}
// 创建插件目录结构
if (!CreatePluginDirectoryStructure(PluginName))
{
UE_LOG(LogTemp, Error, TEXT("Failed to create directory structure for plugin '%s'"), *PluginName);
return false;
}
// 生成插件模板文件
if (!GeneratePluginTemplateFiles(PluginName, PluginDescription))
{
UE_LOG(LogTemp, Error, TEXT("Failed to generate template files for plugin '%s'"), *PluginName);
return false;
}
UE_LOG(LogTemp, Log, TEXT("Successfully created new plugin '%s'"), *PluginName);
return true;
}
bool UPluginDevEnvironment::LoadPlugin(const FString& PluginName)
{
// 检查插件是否已加载
if (IsPluginLoaded(PluginName))
{
UE_LOG(LogTemp, Warning, TEXT("Plugin '%s' is already loaded"), *PluginName);
return true;
}
// 获取插件路径
FString PluginPath = GetPluginPath(PluginName);
// 检查插件是否存在
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*PluginPath))
{
UE_LOG(LogTemp, Error, TEXT("Plugin '%s' not found at path: %s"), *PluginName, *PluginPath);
return false;
}
// 加载插件主类
FString PluginScriptPath = PluginPath / TEXT("Main.lua");
if (!PlatformFile.FileExists(*PluginScriptPath))
{
UE_LOG(LogTemp, Error, TEXT("Plugin '%s' main script not found at path: %s"), *PluginName, *PluginScriptPath);
return false;
}
// 创建插件实例
UPluginBase* NewPlugin = NewObject<UPluginBase>(this);
if (!NewPlugin)
{
UE_LOG(LogTemp, Error, TEXT("Failed to create instance of plugin '%s'"), *PluginName);
return false;
}
// 初始化插件
if (!NewPlugin->Initialize(PluginName, PluginPath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to initialize plugin '%s'"), *PluginName);
return false;
}
// 添加到已加载插件列表
LoadedPlugins.Add(PluginName, NewPlugin);
UE_LOG(LogTemp, Log, TEXT("Successfully loaded plugin '%s'"), *PluginName);
return true;
}
bool UPluginDevEnvironment::UnloadPlugin(const FString& PluginName)
{
// 检查插件是否已加载
UPluginBase** FoundPlugin = LoadedPlugins.Find(PluginName);
if (!FoundPlugin || !*FoundPlugin)
{
UE_LOG(LogTemp, Warning, TEXT("Plugin '%s' is not loaded"), *PluginName);
return false;
}
// 关闭插件
(*FoundPlugin)->Shutdown();
// 从列表中移除
LoadedPlugins.Remove(PluginName);
UE_LOG(LogTemp, Log, TEXT("Successfully unloaded plugin '%s'"), *PluginName);
return true;
}
bool UPluginDevEnvironment::ReloadPlugin(const FString& PluginName)
{
// 先卸载再加载
if (IsPluginLoaded(PluginName))
{
UnloadPlugin(PluginName);
}
return LoadPlugin(PluginName);
}
TArray<FString> UPluginDevEnvironment::GetLoadedPlugins() const
{
TArray<FString> Result;
LoadedPlugins.GetKeys(Result);
return Result;
}
TArray<FString> UPluginDevEnvironment::GetAvailablePlugins() const
{
TArray<FString> Result;
// 列出插件目录中的所有子目录
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
// 确保目录存在
if (!PlatformFile.DirectoryExists(*PluginsDirectory))
{
return Result;
}
// 遍历子目录
class FPluginDirectoryVisitor : public IPlatformFile::FDirectoryVisitor
{
public:
TArray<FString>& PluginNames;
FPluginDirectoryVisitor(TArray<FString>& InPluginNames)
: PluginNames(InPluginNames)
{
}
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
{
if (bIsDirectory)
{
FString DirectoryName = FPaths::GetCleanFilename(FilenameOrDirectory);
PluginNames.Add(DirectoryName);
}
return true;
}
};
FPluginDirectoryVisitor DirectoryVisitor(Result);
PlatformFile.IterateDirectory(*PluginsDirectory, DirectoryVisitor);
return Result;
}
bool UPluginDevEnvironment::IsPluginLoaded(const FString& PluginName) const
{
return LoadedPlugins.Contains(PluginName);
}
void UPluginDevEnvironment::OpenPluginEditor(const FString& PluginName)
{
// 获取插件路径
FString PluginPath = GetPluginPath(PluginName);
// 在编辑器中打开主脚本文件
FString MainScriptPath = PluginPath / TEXT("Main.lua");
// 在这里可以实现打开编辑器的逻辑
// 在实际应用中,可能需要使用平台特定的代码来启动外部编辑器
UE_LOG(LogTemp, Log, TEXT("Opening plugin editor for '%s' at path: %s"), *PluginName, *MainScriptPath);
}
FString UPluginDevEnvironment::GetPluginPath(const FString& PluginName) const
{
return PluginsDirectory / PluginName;
}
bool UPluginDevEnvironment::CreatePluginDirectoryStructure(const FString& PluginName)
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
// 创建主目录
FString PluginPath = GetPluginPath(PluginName);
if (!PlatformFile.CreateDirectoryTree(*PluginPath))
{
return false;
}
// 创建子目录
TArray<FString> Subdirectories = {
TEXT("UI"),
TEXT("Scripts"),
TEXT("Textures"),
TEXT("Sounds"),
TEXT("Localization")
};
for (const FString& Subdir : Subdirectories)
{
FString SubdirPath = PluginPath / Subdir;
if (!PlatformFile.CreateDirectoryTree(*SubdirPath))
{
return false;
}
}
return true;
}
bool UPluginDevEnvironment::GeneratePluginTemplateFiles(const FString& PluginName, const FString& PluginDescription)
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FString PluginPath = GetPluginPath(PluginName);
// 生成插件信息文件
FString InfoContent = FString::Printf(
TEXT("-- %s\n")
TEXT("-- Version: 1.0\n")
TEXT("-- Description: %s\n")
TEXT("-- Author: Your Name\n")
TEXT("-- Date: %s\n"),
*PluginName,
*PluginDescription,
*FDateTime::Now().ToString()
);
if (!FFileHelper::SaveStringToFile(InfoContent, *(PluginPath / TEXT("Info.lua"))))
{
return false;
}
// 生成主脚本模板
FString MainContent = FString::Printf(
TEXT("-- %s Main Script\n\n")
TEXT("-- Plugin initialization\n")
TEXT("function OnLoad(self)\n")
TEXT(" print(\"%s loaded successfully!\")\n")
TEXT(" \n")
TEXT(" -- Register events here\n")
TEXT(" -- Example: RegisterEvent(\"PLAYER_ENTERING_WORLD\")\n")
TEXT("end\n\n")
TEXT("-- Event handling\n")
TEXT("function OnEvent(self, event, ...)\n")
TEXT(" if event == \"PLAYER_ENTERING_WORLD\" then\n")
TEXT(" -- Handle player entering world\n")
TEXT(" end\n")
TEXT("end\n\n")
TEXT("-- UI initialization\n")
TEXT("function InitializeUI()\n")
TEXT(" -- Create and setup UI frames here\n")
TEXT("end\n"),
*PluginName,
*PluginName
);
if (!FFileHelper::SaveStringToFile(MainContent, *(PluginPath / TEXT("Main.lua"))))
{
return false;
}
// 生成UI模板文件
FString UIContent = FString::Printf(
TEXT("<Ui xmlns=\"http://www.blizzard.com/wow/ui/\">\n")
TEXT(" <!-- %s UI Definition -->\n")
TEXT(" <Frame name=\"%sMainFrame\" hidden=\"true\">\n")
TEXT(" <Size x=\"300\" y=\"200\" />\n")
TEXT(" <Anchors>\n")
TEXT(" <Anchor point=\"CENTER\" />\n")
TEXT(" </Anchors>\n")
TEXT(" <Backdrop bgFile=\"Interface\\DialogFrame\\UI-DialogBox-Background\" tile=\"true\">\n")
TEXT(" <BackgroundInsets>\n")
TEXT(" <AbsInset left=\"11\" right=\"12\" top=\"12\" bottom=\"11\" />\n")
TEXT(" </BackgroundInsets>\n")
TEXT(" <TileSize val=\"32\" />\n")
TEXT(" </Backdrop>\n")
TEXT(" <Layers>\n")
TEXT(" <Layer level=\"ARTWORK\">\n")
TEXT(" <FontString name=\"$parentTitle\" inherits=\"GameFontNormal\" text=\"%s\">\n")
TEXT(" <Anchors>\n")
TEXT(" <Anchor point=\"TOP\" relativeTo=\"$parent\" relativePoint=\"TOP\">\n")
TEXT(" <Offset y=\"-15\" />\n")
TEXT(" </Anchor>\n")
TEXT(" </Anchors>\n")
TEXT(" </FontString>\n")
TEXT(" </Layer>\n")
TEXT(" </Layers>\n")
TEXT(" <Frames>\n")
TEXT(" <!-- Add UI elements here -->\n")
TEXT(" <Button name=\"$parentCloseButton\" inherits=\"UIPanelCloseButton\">\n")
TEXT(" <Anchors>\n")
TEXT(" <Anchor point=\"TOPRIGHT\" relativeTo=\"$parent\" relativePoint=\"TOPRIGHT\">\n")
TEXT(" <Offset x=\