实现模组新建/打包

This commit is contained in:
_Redstone_c_ 2020-12-01 15:59:57 +08:00
commit 21a58e4b26
37 changed files with 1297 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
Binaries
DerivedDataCache
Intermediate
Saved
Build
.vscode
.vs
*.VC.db
*.opensdf
*.opendb
*.sdf
*.sln
*.suo
*.xcodeproj
*.xcworkspace

34
ModSupport.uplugin Normal file
View File

@ -0,0 +1,34 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "Mod Support",
"Description": "",
"Category": "Other",
"CreatedBy": "_Redstone_c_",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": true,
"Installed": false,
"Modules": [
{
"Name": "ModSupport",
"Type": "Runtime",
"LoadingPhase": "PreDefault"
},
{
"Name": "ModSupportEditor",
"Type": "Editor",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "PluginBrowser",
"Enabled": true
}
]
}

5
README.md Normal file
View File

@ -0,0 +1,5 @@
## UE4 Plugin: ModSupport
[ModSupport](http://git.myredstone.top/summary/UE4-Plugins!ModSupport.git) 是一个辅助制作 UE4 游戏模块化模组的插件,借鉴 [UGCExample](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 的思路管理和组织模组,配合 [HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 进行打包来实现单 Pak 包模组,可以在发布版的 UE4 上运行而无需源码。
目前支持的 UE4 版本4.25.4
前置插件:[HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
Resources/CreateMod_16x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
Resources/CreateMod_48x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
Resources/CreateMod_64x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
Resources/Icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,61 @@
{
"bByBaseVersion": false,
"baseVersion":
{
"filePath": ""
},
"versionId": "%%%PluginName%%%",
"assetIncludeFilters": [
{
"path": "%%%PluginContentDir%%%"
}
],
"assetIgnoreFilters": [],
"bForceSkipContent": true,
"forceSkipContentRules": [
{
"path": "/Engine/Editor"
},
{
"path": "/Engine/VREditor"
}
],
"forceSkipAssets": [],
"bIncludeHasRefAssetsOnly": false,
"bAnalysisFilterDependencies": false,
"bRecursiveWidgetTree": false,
"assetRegistryDependencyTypes": [],
"includeSpecifyAssets": [],
"bIncludeAssetRegistry": false,
"bIncludeGlobalShaderCache": false,
"bIncludeShaderBytecode": false,
"bIncludeEngineIni": false,
"bIncludePluginIni": false,
"bIncludeProjectIni": false,
"bEnableExternFilesDiff": false,
"ignoreDeletionModulesAsset": [],
"addExternAssetsToPlatform": [],
"bEnableChunk": false,
"chunkInfos": [],
"bCookPatchAssets": false,
"pakCommandOptions": [],
"replacePakCommandTexts": [],
"unrealPakOptions": [
"-compress",
"-compressionformats=Zlib"
],
"pakTargetPlatforms": [
"%%%TargetPlatform%%%"
],
"bCustomPakNameRegular": true,
"pakNameRegular": "{VERSION}",
"bSaveDeletedAssetsToNewReleaseJson": false,
"bSavePakList": false,
"bSaveDiffAnalysis": false,
"bSaveAssetRelatedInfo": false,
"bSavePatchConfig": false,
"savePath":
{
"path": "%%%OutputDirectory%%%"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,53 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class ModSupport : ModuleRules
{
public ModSupport(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,3 @@
#include "ModInfo.h"
#include "Interfaces/IPluginManager.h"

View File

@ -0,0 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModSupport.h"
#define LOCTEXT_NAMESPACE "FModSupportModule"
void FModSupportModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FModSupportModule::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.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FModSupportModule, ModSupport)

View File

@ -0,0 +1,3 @@
#include "ModSupportLog.h"
DEFINE_LOG_CATEGORY(LogModSupport);

View File

@ -0,0 +1,67 @@
#pragma once
#include "CoreMinimal.h"
#include "ModInfo.generated.h"
USTRUCT(BlueprintType, Category = "ModSupport|ModInfo")
struct MODSUPPORT_API FModInfo
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString Name;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString ContentDir;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString VirtualMountPoint;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
int32 Version;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString VersionName;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString FriendlyName;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString Description;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString Category;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString CreatedBy;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString CreatedByURL;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString DocsURL;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString MarketplaceURL;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString SupportURL;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString EngineVersion;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
FString ParentPluginName;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
bool bIsBetaVersion;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
bool bIsExperimentalVersion;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
bool bIsHidden;
UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo")
TArray<FString> PluginsRequire;
};

View File

@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FModSupportModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@ -0,0 +1,6 @@
#pragma once
#include "CoreMinimal.h"
#include "Logging/LogMacros.h"
MODSUPPORT_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupport, Log, All);

View File

@ -0,0 +1,60 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class ModSupportEditor : ModuleRules
{
public ModSupportEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"ModSupport",
"PluginBrowser",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Projects",
"InputCore",
"UnrealEd",
"LevelEditor",
"CoreUObject",
"Engine",
"PluginBrowser",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,64 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModCreator.h"
#include "ModPluginWizardDefinition.h"
#include "Widgets/Docking/SDockTab.h"
// This depends on the Plugin Browser module to work correctly...
#include "IPluginBrowser.h"
#define LOCTEXT_NAMESPACE "FModCreator"
const FName FModCreator::ModSupportEditorPluginCreatorName("ModCreator");
FModCreator::FModCreator()
{
RegisterTabSpawner();
}
FModCreator::~FModCreator()
{
UnregisterTabSpawner();
}
void FModCreator::OpenNewPluginWizard(bool bSuppressErrors) const
{
if (IPluginBrowser::IsAvailable())
{
FGlobalTabmanager::Get()->InvokeTab(ModSupportEditorPluginCreatorName);
}
else if (!bSuppressErrors)
{
FMessageDialog::Open(EAppMsgType::Ok,
LOCTEXT("PluginBrowserDisabled", "Creating a game mod requires the use of the Plugin Browser, but it is currently disabled."));
}
}
void FModCreator::RegisterTabSpawner()
{
FTabSpawnerEntry& Spawner = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ModSupportEditorPluginCreatorName,
FOnSpawnTab::CreateRaw(this, &FModCreator::HandleSpawnPluginTab));
// Set a default size for this tab
FVector2D DefaultSize(800.0f, 500.0f);
FTabManager::RegisterDefaultTabWindowSize(ModSupportEditorPluginCreatorName, DefaultSize);
Spawner.SetDisplayName(LOCTEXT("NewModTabHeader", "Create New Mod Package"));
Spawner.SetMenuType(ETabSpawnerMenuType::Hidden);
}
void FModCreator::UnregisterTabSpawner()
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ModSupportEditorPluginCreatorName);
}
TSharedRef<SDockTab> FModCreator::HandleSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
check(IPluginBrowser::IsAvailable());
return IPluginBrowser::Get().SpawnPluginCreatorTab(SpawnTabArgs, MakeShared<FModPluginWizardDefinition>());
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,221 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModPackager.h"
#include "ModSupportEditor.h"
#include "ModSupportEditorCommands.h"
#include "ModSupportEditorStyle.h"
#include "ModSupportEditorLog.h"
#include "Editor.h"
#include "Widgets/SWindow.h"
#include "Widgets/SWidget.h"
#include "Interfaces/IPluginManager.h"
#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h"
#include "Editor/UATHelper/Public/IUATHelperModule.h"
#include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h"
#include "FileHelpers.h"
#include "Misc/FileHelper.h"
#include "Misc/PackageName.h"
#define LOCTEXT_NAMESPACE "ModPackager"
FModPackager::FModPackager()
{
}
FModPackager::~FModPackager()
{
}
void FModPackager::OpenPluginPackager(TSharedRef<IPlugin> Plugin)
{
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
FString DefaultDirectory = FPaths::ConvertRelativePathToFull(Plugin->GetBaseDir());
FString OutputDirectory;
// Prompt the user to save all dirty packages. We'll ensure that if any packages from the mod that the user wants to
// package are dirty that they will not be able to save them.
if (!IsAllContentSaved(Plugin))
{
FEditorFileUtils::SaveDirtyPackages( true, true, true);
}
if (IsAllContentSaved(Plugin))
{
void* ParentWindowWindowHandle = nullptr;
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid())
{
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
}
if (DesktopPlatform->OpenDirectoryDialog(ParentWindowWindowHandle, LOCTEXT("SelectOutputFolderTitle", "Select Mod output directory:").ToString(), DefaultDirectory, OutputDirectory))
{
PackagePlugin(Plugin, OutputDirectory);
}
}
else
{
FText PackageModError = FText::Format(LOCTEXT("PackageModError_UnsavedContent", "You must save all assets in {0} before you can share it."),
FText::FromString(Plugin->GetName()));
FMessageDialog::Open(EAppMsgType::Ok, PackageModError);
}
}
bool FModPackager::IsAllContentSaved(TSharedRef<IPlugin> Plugin)
{
bool bAllContentSaved = true;
TArray<UPackage*> UnsavedPackages;
FEditorFileUtils::GetDirtyContentPackages(UnsavedPackages);
FEditorFileUtils::GetDirtyWorldPackages(UnsavedPackages);
if (UnsavedPackages.Num() > 0)
{
FString PluginBaseDir = Plugin->GetBaseDir();
for (UPackage* Package : UnsavedPackages)
{
FString PackageFilename;
if (FPackageName::TryConvertLongPackageNameToFilename(Package->GetName(), PackageFilename))
{
if (PackageFilename.Find(PluginBaseDir) == 0)
{
bAllContentSaved = false;
break;
}
}
}
}
return bAllContentSaved;
}
void FModPackager::PackagePlugin(TSharedRef<class IPlugin> Plugin, const FString& OutputDirectory)
{
FString PackageCofnig;
FString PackageCofnigTemplate;
PackageCofnigTemplate = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir() / TEXT("Resources") / TEXT("ModPackageCofnig.json");
if (!FFileHelper::LoadFileToString(PackageCofnig, *PackageCofnigTemplate))
{
UE_LOG(LogModSupportEditor, Error, TEXT("Failed to load configuration template"));
return;
}
PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginName%%%"), *Plugin->GetName());
PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginContentDir%%%"), *Plugin->GetMountedAssetPath());
#if PLATFORM_WINDOWS
PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("WindowsNoEditor"));
#elif PLATFORM_MAC
PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("MacNoEditor"));
#elif PLATFORM_LINUX
PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("LinuxNoEditor"));
#else
PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("AllDesktop"));
#endif
PackageCofnig = PackageCofnig.Replace(TEXT("%%%OutputDirectory%%%"), *OutputDirectory);
FString PackageCofnigSavePath;
PackageCofnigSavePath = FPaths::ProjectSavedDir() / TEXT("ModInfo") / TEXT("ModPackageCofnig.json");
if (!FFileHelper::SaveStringToFile(PackageCofnig, *PackageCofnigSavePath))
{
UE_LOG(LogModSupportEditor, Error, TEXT("Failed to save configuration"));
return;
}
#if PLATFORM_WINDOWS
PackageCofnigSavePath = PackageCofnigSavePath.Replace(TEXT("/"), TEXT("\\"));
#endif
FText OptTitle = LOCTEXT("PackageCofnigDialog", "Saved packaging configuration file");
FText Message = FText::FromString(PackageCofnigSavePath);
FMessageDialog::Open(EAppMsgType::Ok, Message, &OptTitle);
UE_LOG(LogModSupportEditor, Display, TEXT("Saved packaging configuration file to %s"), *PackageCofnigSavePath);
}
void FModPackager::FindAvailableGameMods(TArray<TSharedRef<IPlugin>>& OutAvailableGameMods)
{
OutAvailableGameMods.Empty();
for (TSharedRef<IPlugin> Plugin : IPluginManager::Get().GetDiscoveredPlugins())
{
if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project && Plugin->GetType() == EPluginType::Mod)
{
UE_LOG(LogModSupportEditor, Display, TEXT("Adding %s"), *Plugin->GetName());
OutAvailableGameMods.AddUnique(Plugin);
}
}
}
void FModPackager::GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray<TSharedPtr<FUICommandInfo>>& Commands)
{
for (TSharedPtr<FUICommandInfo> Command : Commands)
{
MenuBuilder.AddMenuEntry(Command, NAME_None, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.Folder"));
}
}
void FModPackager::GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder)
{
TArray<TSharedRef<IPlugin>> AvailableGameMods;
FindAvailableGameMods(AvailableGameMods);
TArray<TSharedPtr<FUICommandInfo>> Commands;
GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands);
}
TSharedRef<SWidget> FModPackager::GeneratePackagerComboButtonContent()
{
// Regenerate the game mod commands
TArray<TSharedRef<IPlugin>> AvailableGameMods;
FindAvailableGameMods(AvailableGameMods);
GetAvailableModCommands(AvailableGameMods);
// Regenerate the action list
TSharedPtr<FUICommandList> GameModActionsList = MakeShareable(new FUICommandList);
for (int32 Index = 0; Index < ModCommands.Num(); ++Index)
{
GameModActionsList->MapAction(
ModCommands[Index],
FExecuteAction::CreateRaw(this, &FModPackager::OpenPluginPackager, AvailableGameMods[Index]),
FCanExecuteAction()
);
}
// Show the drop down menu
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GameModActionsList);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("PackageMod", "Share..."));
{
GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void FModPackager::GetAvailableModCommands(const TArray<TSharedRef<IPlugin>>& AvailableMod)
{
if (ModCommands.Num() > 0)
{
// Unregister UI Commands
FModSupportEditorCommands::Get().UnregisterModCommands(ModCommands);
}
ModCommands.Empty(AvailableMod.Num());
ModCommands = FModSupportEditorCommands::Get().RegisterModCommands(AvailableMod);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,201 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModPluginWizardDefinition.h"
#include "ContentBrowserModule.h"
#include "EngineAnalytics.h"
#include "Interfaces/IPluginManager.h"
#include "IContentBrowserSingleton.h"
#include "Algo/Transform.h"
#include "SlateBasics.h"
#include "SourceCodeNavigation.h"
#define LOCTEXT_NAMESPACE "SimpleModPluginWizard"
FModPluginWizardDefinition::FModPluginWizardDefinition()
{
PluginBaseDir = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir();
BackingTemplate = MakeShareable(new FPluginTemplateDescription(FText(), FText(), TEXT("BaseTemplate"), true, EHostType::Runtime));
BackingTemplatePath = PluginBaseDir / TEXT("Templates") / BackingTemplate->OnDiskPath;
}
const TArray<TSharedRef<FPluginTemplateDescription>>& FModPluginWizardDefinition::GetTemplatesSource() const
{
return TemplateDefinitions;
}
void FModPluginWizardDefinition::OnTemplateSelectionChanged(TArray<TSharedRef<FPluginTemplateDescription>> InSelectedItems, ESelectInfo::Type SelectInfo)
{
SelectedTemplates = InSelectedItems;
}
TArray<TSharedPtr<FPluginTemplateDescription>> FModPluginWizardDefinition::GetSelectedTemplates() const
{
TArray<TSharedPtr<FPluginTemplateDescription>> SelectedTemplatePtrs;
for (TSharedRef<FPluginTemplateDescription> Ref : SelectedTemplates)
{
SelectedTemplatePtrs.Add(Ref);
}
return SelectedTemplatePtrs;
}
void FModPluginWizardDefinition::ClearTemplateSelection()
{
SelectedTemplates.Empty();
}
bool FModPluginWizardDefinition::HasValidTemplateSelection() const
{
// A mod should be created even if no templates are actually selected
return true;
}
bool FModPluginWizardDefinition::CanContainContent() const
{
bool bHasContent = SelectedTemplates.Num() == 0; // if no templates are selected, by default it is a content mod
if (!bHasContent)
{
for (TSharedPtr<FPluginTemplateDescription> Template : SelectedTemplates)
{
// If at least one module can contain content, it's a content mod. Otherwise, it's a pure code mod.
if (Template->bCanContainContent)
{
bHasContent = true;
break;
}
}
}
return bHasContent;
}
bool FModPluginWizardDefinition::HasModules() const
{
return false;
}
bool FModPluginWizardDefinition::IsMod() const
{
return true;
}
void FModPluginWizardDefinition::OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState)
{
}
ECheckBoxState FModPluginWizardDefinition::GetShowOnStartupCheckBoxState() const
{
return ECheckBoxState();
}
FText FModPluginWizardDefinition::GetInstructions() const
{
return LOCTEXT("CreateNewModPanel", "Give your new Mod package a name and Click 'Create Mod' to make a new content only Mod package.");
}
TSharedPtr<SWidget> FModPluginWizardDefinition::GetCustomHeaderWidget()
{
if ( !CustomHeaderWidget.IsValid() )
{
FString IconPath;
GetPluginIconPath(IconPath);
const FName BrushName(*IconPath);
const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName);
if ((Size.X > 0) && (Size.Y > 0))
{
IconBrush = MakeShareable(new FSlateDynamicImageBrush(BrushName, FVector2D(Size.X, Size.Y)));
}
CustomHeaderWidget = SNew(SHorizontalBox)
// Header image
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f)
[
SNew(SBox)
.WidthOverride(80.0f)
.HeightOverride(80.0f)
[
SNew(SImage)
.Image(IconBrush.IsValid() ? IconBrush.Get() : nullptr)
]
];
}
return CustomHeaderWidget;
}
bool FModPluginWizardDefinition::GetPluginIconPath(FString& OutIconPath) const
{
// Replace this file with your own 128x128 image if desired.
OutIconPath = BackingTemplatePath / TEXT("Resources/Icon128.png");
return false;
}
bool FModPluginWizardDefinition::GetTemplateIconPath(TSharedRef<FPluginTemplateDescription> InTemplate, FString& OutIconPath) const
{
FString TemplateName = InTemplate->Name.ToString();
OutIconPath = PluginBaseDir / TEXT("Resources");
if (TemplateToIconMap.Contains(TemplateName))
{
OutIconPath /= TemplateToIconMap[TemplateName];
}
else
{
// Couldn't find a suitable icon to use for this template, so use the default one instead
OutIconPath /= TEXT("Icon128.png");
}
return false;
}
FString FModPluginWizardDefinition::GetPluginFolderPath() const
{
return BackingTemplatePath;
}
EHostType::Type FModPluginWizardDefinition::GetPluginModuleDescriptor() const
{
return BackingTemplate->ModuleDescriptorType;
}
ELoadingPhase::Type FModPluginWizardDefinition::GetPluginLoadingPhase() const
{
return BackingTemplate->LoadingPhase;
}
TArray<FString> FModPluginWizardDefinition::GetFoldersForSelection() const
{
TArray<FString> SelectedFolders;
SelectedFolders.Add(BackingTemplatePath); // This will always be a part of the mod plugin
for (TSharedPtr<FPluginTemplateDescription> Template : SelectedTemplates)
{
SelectedFolders.AddUnique(PluginBaseDir / TEXT("Templates") / Template->OnDiskPath);
}
return SelectedFolders;
}
void FModPluginWizardDefinition::PluginCreated(const FString& PluginName, bool bWasSuccessful) const
{
// Override Category to Mod
if (bWasSuccessful)
{
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(PluginName);
if (Plugin != nullptr)
{
FPluginDescriptor Desc = Plugin->GetDescriptor();
Desc.Category = "Mod";
FText UpdateFailureText;
Plugin->UpdateDescriptor(Desc, UpdateFailureText);
}
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,96 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModSupportEditor.h"
#include "ModCreator.h"
#include "ModPackager.h"
#include "Misc/MessageDialog.h"
#include "ModSupportEditorStyle.h"
#include "ModSupportEditorCommands.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "LevelEditor.h"
#define LOCTEXT_NAMESPACE "FModSupportEditorModule"
void FModSupportEditorModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
ModCreator = MakeShared<FModCreator>();
ModPackager = MakeShared<FModPackager>();
FModSupportEditorStyle::Initialize();
FModSupportEditorStyle::ReloadTextures();
FModSupportEditorCommands::Register();
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(
FModSupportEditorCommands::Get().CreateModAction,
FExecuteAction::CreateRaw(this, &FModSupportEditorModule::CreateModButtonClicked),
FCanExecuteAction()
);
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
// Add commands
{
FName MenuSection = "FileProject";
FName ToolbarSection = "Misc";
// Add creator button to the toolbar
{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModCreatorToolbarExtension));
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
// Add packager button to the toolbar
{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModPackagerToolbarExtension));
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
}
}
void FModSupportEditorModule::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.
FModSupportEditorStyle::Shutdown();
FModSupportEditorCommands::Unregister();
}
void FModSupportEditorModule::CreateModButtonClicked()
{
if (ModCreator.IsValid())
{
ModCreator->OpenNewPluginWizard();
}
}
void FModSupportEditorModule::AddModCreatorToolbarExtension(FToolBarBuilder& Builder)
{
Builder.AddToolBarButton(FModSupportEditorCommands::Get().CreateModAction);
}
void FModSupportEditorModule::AddModPackagerToolbarExtension(FToolBarBuilder& Builder)
{
FModPackager* Packager = ModPackager.Get();
Builder.AddComboButton(FUIAction(),
FOnGetContent::CreateSP(Packager, &FModPackager::GeneratePackagerComboButtonContent),
LOCTEXT("PackageMod_Label", "Package Mod"),
LOCTEXT("PackageMod_Tooltip", "Share and distribute Mod"),
FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.PackageModAction")
);
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FModSupportEditorModule, ModSupportEditor)

View File

@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModSupportEditorCommands.h"
#include "Interfaces/IPluginManager.h"
#define LOCTEXT_NAMESPACE "FModSupportEditorModule"
void FModSupportEditorCommands::RegisterCommands()
{
UI_COMMAND(CreateModAction, "Create Mod", "Create a new Mod package in a mod plugin", EUserInterfaceActionType::Button, FInputGesture());
UI_COMMAND(PackageModAction, "Package Mod", "Share and distribute your Mod", EUserInterfaceActionType::Button, FInputGesture());
}
TArray<TSharedPtr<FUICommandInfo>> FModSupportEditorCommands::RegisterModCommands(const TArray<TSharedRef<class IPlugin>>& ModList) const
{
TArray<TSharedPtr<FUICommandInfo>> AvailableModActions;
AvailableModActions.Reserve(ModList.Num());
FModSupportEditorCommands* MutableThis = const_cast<FModSupportEditorCommands*>(this);
for (int32 Index = 0; Index < ModList.Num(); ++Index)
{
AvailableModActions.Add(TSharedPtr<FUICommandInfo>());
TSharedRef<IPlugin> Mod = ModList[Index];
FString CommandName = "ModEditor_" + Mod->GetName();
FUICommandInfo::MakeCommandInfo(MutableThis->AsShared(),
AvailableModActions[Index],
FName(*CommandName),
FText::FromString(Mod->GetName()),
FText::FromString(Mod->GetBaseDir()),
FSlateIcon(),
EUserInterfaceActionType::Button,
FInputGesture());
}
return AvailableModActions;
}
void FModSupportEditorCommands::UnregisterModCommands(TArray<TSharedPtr<FUICommandInfo>>& UICommands) const
{
FModSupportEditorCommands* MutableThis = const_cast<FModSupportEditorCommands*>(this);
for (TSharedPtr<FUICommandInfo> Command : UICommands)
{
FUICommandInfo::UnregisterCommandInfo(MutableThis->AsShared(), Command.ToSharedRef());
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,4 @@
#include "ModSupportEditorLog.h"
DEFINE_LOG_CATEGORY(LogModSupportEditor);

View File

@ -0,0 +1,72 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ModSupportEditorStyle.h"
#include "ModSupportEditor.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyleRegistry.h"
#include "Slate/SlateGameResources.h"
#include "Interfaces/IPluginManager.h"
TSharedPtr< FSlateStyleSet > FModSupportEditorStyle::StyleInstance = NULL;
void FModSupportEditorStyle::Initialize()
{
if (!StyleInstance.IsValid())
{
StyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
}
}
void FModSupportEditorStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
ensure(StyleInstance.IsUnique());
StyleInstance.Reset();
}
FName FModSupportEditorStyle::GetStyleSetName()
{
static FName StyleSetName(TEXT("ModSupportEditorStyle"));
return StyleSetName;
}
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon20x20(20.0f, 20.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
TSharedRef< FSlateStyleSet > FModSupportEditorStyle::Create()
{
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ModSupportEditorStyle"));
Style->SetContentRoot(IPluginManager::Get().FindPlugin("ModSupport")->GetBaseDir() / TEXT("Resources"));
Style->Set("ModSupportEditor.PackageModAction", new IMAGE_BRUSH(TEXT("PackageMod_64x"), Icon40x40));
Style->Set("ModSupportEditor.CreateModAction", new IMAGE_BRUSH(TEXT("CreateMod_64x"), Icon40x40));
return Style;
}
#undef IMAGE_BRUSH
#undef BOX_BRUSH
#undef BORDER_BRUSH
#undef TTF_FONT
#undef OTF_FONT
void FModSupportEditorStyle::ReloadTextures()
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
}
const ISlateStyle& FModSupportEditorStyle::Get()
{
return *StyleInstance;
}

View File

@ -0,0 +1,33 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
class FModSupportPluginWizardDefinition;
class SDockTab;
class FModCreator : public TSharedFromThis<FModCreator>
{
public:
FModCreator();
~FModCreator();
/**
* Opens the mod creator wizard.
* @param bSuppressErrors If false, a dialog will be shown if the wizard cannot be opened for whatever reason
*/
void OpenNewPluginWizard(bool bSuppressErrors = false) const;
/** The name to use when creating the tab for the tab spawner */
static const FName ModSupportEditorPluginCreatorName;
private:
/** Registers a nomad tab spawner that will create the mod wizard */
void RegisterTabSpawner();
/** Unregisters the nomad tab spawner */
void UnregisterTabSpawner();
/** Spawns the tab that hosts the mod creator wizard widget */
TSharedRef<SDockTab> HandleSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs);
};

View File

@ -0,0 +1,49 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
struct FModSupportCommand
{
TSharedPtr<class IPlugin> PluginInfo;
TSharedPtr<class FUICommandInfo> CommandInfo;
};
class FModPackager : public TSharedFromThis<FModPackager>
{
public:
FModPackager();
~FModPackager();
void OpenPluginPackager(TSharedRef<class IPlugin> Plugin);
void PackagePlugin(TSharedRef<class IPlugin> Plugin, const FString& OutputDirectory);
/** Generates submenu content for the plugin packager command */
void GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder);
/** Generates the menu content for the plugin packager toolbar button */
TSharedRef<class SWidget> GeneratePackagerComboButtonContent();
private:
/** Gets all available game mod plugin packages */
void FindAvailableGameMods(TArray<TSharedRef<class IPlugin>>& OutAvailableGameMods);
/** Gets all available game mod plugins and registers command info for them */
void GetAvailableModCommands(const TArray<TSharedRef<class IPlugin>>& AvailableMod);
/** Generates menu content for the supplied set of commands */
void GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray<TSharedPtr<FUICommandInfo>>& Commands);
/**
* Checks if a plugin has any unsaved content
*
* @param Plugin The plugin to check for unsaved content
* @return True if all mod content has been saved, false otherwise
*/
bool IsAllContentSaved(TSharedRef<class IPlugin> Plugin);
private:
TArray<TSharedPtr<class FUICommandInfo>> ModCommands;
};

View File

@ -0,0 +1,70 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// Depends on code from the plugin browser to work correctly
#include "IPluginWizardDefinition.h"
class FModPluginWizardDefinition : public IPluginWizardDefinition
{
public:
FModPluginWizardDefinition();
// Begin IPluginWizardDefinition interface
virtual const TArray<TSharedRef<FPluginTemplateDescription>>& GetTemplatesSource() const override;
virtual void OnTemplateSelectionChanged(TArray<TSharedRef<FPluginTemplateDescription>> InSelectedItems, ESelectInfo::Type SelectInfo) override;
virtual TArray<TSharedPtr<FPluginTemplateDescription>> GetSelectedTemplates() const override;
virtual void ClearTemplateSelection() override;
virtual bool HasValidTemplateSelection() const override;
virtual ESelectionMode::Type GetSelectionMode() const override { return ESelectionMode::Multi; }
virtual bool AllowsEnginePlugins() const override { return false; }
virtual bool CanShowOnStartup() const override { return true; }
virtual bool CanContainContent() const override;
virtual bool HasModules() const override;
virtual bool IsMod() const override;
virtual void OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) override;
virtual ECheckBoxState GetShowOnStartupCheckBoxState() const override;
virtual TSharedPtr<class SWidget> GetCustomHeaderWidget() override;
virtual FText GetInstructions() const override;
virtual bool GetPluginIconPath(FString& OutIconPath) const override;
virtual EHostType::Type GetPluginModuleDescriptor() const override;
virtual ELoadingPhase::Type GetPluginLoadingPhase() const override;
virtual bool GetTemplateIconPath(TSharedRef<FPluginTemplateDescription> InTemplate, FString& OutIconPath) const override;
virtual FString GetPluginFolderPath() const override;
virtual TArray<FString> GetFoldersForSelection() const override;
virtual void PluginCreated(const FString& PluginName, bool bWasSuccessful) const override;
// End IPluginWizardDefinition interface
private:
/** The available templates for the mod. They should function as mixins to the backing template */
TArray<TSharedRef<FPluginTemplateDescription>> TemplateDefinitions;
/** The content that will be used when creating the mod */
TArray<TSharedRef<FPluginTemplateDescription>> SelectedTemplates;
/** The base directory of this plugin. Used for accessing the templates used to create mods */
FString PluginBaseDir;
/**
* The path to the template that ultimately serves as the template that the mod will be based on. It's not intended to be
* selected directly, but rather other templates will act as mixins to define what content will exist in the plugin.
*/
FString BackingTemplatePath;
/** The backing template definition for the mod. This should never be directly selectable */
TSharedPtr<FPluginTemplateDescription> BackingTemplate;
/** The base code template definition. Can be directly selectable to create an "empty" code mod, but should be included with any code mod selection */
TSharedPtr<FPluginTemplateDescription> BaseCodeTemplate;
/** Maps a specific template to a specific icon file */
TMap<FString, FString> TemplateToIconMap;
/** Brush used for drawing the custom header widget */
TSharedPtr<struct FSlateDynamicImageBrush> IconBrush;
/** Custom header widget */
TSharedPtr<class SWidget> CustomHeaderWidget;
};

View File

@ -0,0 +1,30 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FModSupportEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
// When the Create Button is clicked
void CreateModButtonClicked();
/** Adds the plugin creator as a new toolbar button */
void AddModCreatorToolbarExtension(FToolBarBuilder& Builder);
/** Adds the plugin packager as a new toolbar button */
void AddModPackagerToolbarExtension(FToolBarBuilder& Builder);
private:
TSharedPtr<class FModCreator> ModCreator;
TSharedPtr<class FModPackager> ModPackager;
TSharedPtr<class FUICommandList> PluginCommands;
};

View File

@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"
#include "ModSupportEditorStyle.h"
class FModSupportEditorCommands : public TCommands<FModSupportEditorCommands>
{
public:
FModSupportEditorCommands()
: TCommands<FModSupportEditorCommands>(TEXT("ModSupportEditor"), NSLOCTEXT("Contexts", "ModSupportEditor", "ModSupportEditor Plugin"), NAME_None, FModSupportEditorStyle::GetStyleSetName())
{
}
// TCommands<> interface
virtual void RegisterCommands() override;
TArray<TSharedPtr<FUICommandInfo>> RegisterModCommands(const TArray<TSharedRef<class IPlugin>>& ModList) const;
void UnregisterModCommands(TArray<TSharedPtr<FUICommandInfo>>& UICommands) const;
public:
TSharedPtr< FUICommandInfo > CreateModAction;
TSharedPtr< FUICommandInfo > PackageModAction;
};

View File

@ -0,0 +1,6 @@
#pragma once
#include "CoreMinimal.h"
#include "Logging/LogMacros.h"
MODSUPPORTEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupportEditor, Log, All);

View File

@ -0,0 +1,31 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
class FModSupportEditorStyle
{
public:
static void Initialize();
static void Shutdown();
/** reloads textures used by slate renderer */
static void ReloadTextures();
/** @return The Slate style set for the Shooter game */
static const ISlateStyle& Get();
static FName GetStyleSetName();
private:
static TSharedRef< class FSlateStyleSet > Create();
private:
static TSharedPtr< class FSlateStyleSet > StyleInstance;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB