Files
2025-05-18 13:04:45 +08:00

142 lines
5.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameFeaturePluginTemplate.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetToolsModule.h"
#include "Dom/JsonValue.h"
#include "Editor.h"
#include "GameFeaturesSubsystem.h"
#include "GameFeaturesSubsystemSettings.h"
#include "HAL/FileManager.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "Subsystems/AssetEditorSubsystem.h"
#define LOCTEXT_NAMESPACE "GameFeatures"
FGameFeaturePluginTemplateDescription::FGameFeaturePluginTemplateDescription(FText InName, FText InDescription, FString InOnDiskPath, FString InDefaultSubfolder, FString InDefaultPluginName, TSubclassOf<UGameFeatureData> GameFeatureDataClassOverride, FString GameFeatureDataNameOverride, EPluginEnabledByDefault InEnabledByDefault)
: FPluginTemplateDescription(InName, InDescription, InOnDiskPath, /*bCanContainContent=*/ true, EHostType::Runtime)
{
SortPriority = 10;
bCanBePlacedInEngine = false;
DefaultSubfolder = InDefaultSubfolder;
DefaultPluginName = InDefaultPluginName;
GameFeatureDataName = !GameFeatureDataNameOverride.IsEmpty() ? GameFeatureDataNameOverride : FString();
GameFeatureDataClass = GameFeatureDataClassOverride != nullptr ? GameFeatureDataClassOverride : TSubclassOf<UGameFeatureData>(UGameFeatureData::StaticClass());
PluginEnabledByDefault = InEnabledByDefault;
}
bool FGameFeaturePluginTemplateDescription::ValidatePathForPlugin(const FString& ProposedAbsolutePluginPath, FText& OutErrorMessage)
{
if (!IsRootedInGameFeaturesRoot(ProposedAbsolutePluginPath))
{
OutErrorMessage = LOCTEXT("InvalidPathForGameFeaturePlugin", "Game features must be inside the Plugins/GameFeatures folder");
return false;
}
OutErrorMessage = FText::GetEmpty();
return true;
}
void FGameFeaturePluginTemplateDescription::UpdatePathWhenTemplateSelected(FString& InOutPath)
{
if (!IsRootedInGameFeaturesRoot(InOutPath))
{
InOutPath = GetGameFeatureRoot();
}
}
void FGameFeaturePluginTemplateDescription::UpdatePathWhenTemplateUnselected(FString& InOutPath)
{
InOutPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FPaths::ProjectPluginsDir());
FPaths::MakePlatformFilename(InOutPath);
}
void FGameFeaturePluginTemplateDescription::UpdatePluginNameTextWhenTemplateSelected(FText& OutPluginNameText)
{
OutPluginNameText = FText::FromString(DefaultPluginName);
}
void FGameFeaturePluginTemplateDescription::UpdatePluginNameTextWhenTemplateUnselected(FText& OutPluginNameText)
{
OutPluginNameText = FText::GetEmpty();
}
void FGameFeaturePluginTemplateDescription::CustomizeDescriptorBeforeCreation(FPluginDescriptor& Descriptor)
{
Descriptor.bExplicitlyLoaded = true;
Descriptor.AdditionalFieldsToWrite.FindOrAdd(TEXT("BuiltInInitialFeatureState")) = MakeShared<FJsonValueString>(TEXT("Active"));
Descriptor.Category = TEXT("Game Features");
// Game features should not be enabled by default if the game wants to strictly manage default settings in the target settings
Descriptor.EnabledByDefault = PluginEnabledByDefault;
if (Descriptor.Modules.Num() > 0)
{
Descriptor.Modules[0].Name = FName(*(Descriptor.Modules[0].Name.ToString() + TEXT("Runtime")));
}
}
void FGameFeaturePluginTemplateDescription::OnPluginCreated(TSharedPtr<IPlugin> NewPlugin)
{
// If the template includes an existing game feature data, do not create a new one.
TArray<FAssetData> ObjectList;
FARFilter AssetFilter;
AssetFilter.ClassPaths.Add(UGameFeatureData::StaticClass()->GetClassPathName());
AssetFilter.PackagePaths.Add(FName(NewPlugin->GetMountedAssetPath()));
AssetFilter.bRecursiveClasses = true;
AssetFilter.bRecursivePaths = true;
IAssetRegistry::GetChecked().GetAssets(AssetFilter, ObjectList);
UObject* GameFeatureDataAsset = nullptr;
if (ObjectList.Num() <= 0)
{
// Create the game feature data asset
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
FString const& AssetName = !GameFeatureDataName.IsEmpty() ? GameFeatureDataName : NewPlugin->GetName();
GameFeatureDataAsset = AssetToolsModule.Get().CreateAsset(AssetName, NewPlugin->GetMountedAssetPath(), GameFeatureDataClass, /*Factory=*/ nullptr);
}
else
{
GameFeatureDataAsset = ObjectList[0].GetAsset();
}
// Activate the new game feature plugin
auto AdditionalFilter = [](const FString&, const FGameFeaturePluginDetails&, FBuiltInGameFeaturePluginBehaviorOptions&) -> bool { return true; };
UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugin(NewPlugin.ToSharedRef(), AdditionalFilter,
FGameFeaturePluginLoadComplete::CreateLambda([GameFeatureDataAsset](const UE::GameFeatures::FResult&)
{
// Edit the new game feature data
if (GameFeatureDataAsset != nullptr)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(GameFeatureDataAsset);
}
}));
}
FString FGameFeaturePluginTemplateDescription::GetGameFeatureRoot() const
{
FString Result = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*(FPaths::ProjectPluginsDir() / TEXT("GameFeatures/")));
// Append the optional subfolder if specified.
if (!DefaultSubfolder.IsEmpty())
{
Result /= DefaultSubfolder + TEXT("/");
}
FPaths::MakePlatformFilename(Result);
return Result;
}
bool FGameFeaturePluginTemplateDescription::IsRootedInGameFeaturesRoot(const FString& InStr) const
{
const FString ConvertedPath = FPaths::ConvertRelativePathToFull(FPaths::CreateStandardFilename(InStr / TEXT("test.uplugin")));
return GetDefault<UGameFeaturesSubsystemSettings>()->IsValidGameFeaturePlugin(ConvertedPath);
}
#undef LOCTEXT_NAMESPACE