第14章 使用AddOn Studio和UE5创建基本插件

第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=\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值