EM_Task/UnrealEd/Public/GameModeInfoCustomizer.h
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

309 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UnrealType.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Engine/Blueprint.h"
#include "Editor.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "Widgets/Text/STextBlock.h"
#include "IDetailGroup.h"
#include "IDetailPropertyRow.h"
#include "UObject/ConstructorHelpers.h"
#include "Widgets/Layout/SBox.h"
#include "GameFramework/GameModeBase.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "PropertyCustomizationHelpers.h"
#include "IDocumentation.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "EditorClassUtils.h"
#include "Subsystems/AssetEditorSubsystem.h"
#define LOCTEXT_NAMESPACE "FGameModeInfoCustomizer"
static FString GameModeCategory(LOCTEXT("GameModeCategory", "GameMode").ToString());
/** Class to help customize a GameMode class picker, to show settings 'withing' GameMode. */
class FGameModeInfoCustomizer: public TSharedFromThis<FGameModeInfoCustomizer>
{
public:
FGameModeInfoCustomizer(UObject* InOwningObject, FName InGameModePropertyName)
{
OwningObject = InOwningObject;
GameModePropertyName = InGameModePropertyName;
CachedGameModeClass = nullptr;
}
/** Create widget for the name of a default class property */
TSharedRef<SWidget> CreateGameModePropertyLabelWidget(FName PropertyName)
{
FProperty* Prop = FindFieldChecked<FProperty>(AGameModeBase::StaticClass(), PropertyName);
FString DisplayName = Prop->GetDisplayNameText().ToString();
if (DisplayName.Len() == 0)
{
DisplayName = Prop->GetName();
}
DisplayName = FName::NameToDisplayString(DisplayName, false);
return SNew(STextBlock)
.Text(FText::FromString(DisplayName))
.ToolTip(IDocumentation::Get()->CreateToolTip(Prop->GetToolTipText(), NULL, TEXT("Shared/Types/AGameMode"), Prop->GetName()))
.Font(IDetailLayoutBuilder::GetDetailFont());
}
/** Create widget fo modifying a default class within the current GameMode */
void CustomizeGameModeDefaultClass(IDetailGroup& Group, FName DefaultClassPropertyName)
{
// Find the metaclass of this property
FClassProperty* ClassProp = FindFieldChecked<FClassProperty>(AGameModeBase::StaticClass(), DefaultClassPropertyName);
UClass* MetaClass = ClassProp->MetaClass;
const bool bAllowNone = !(ClassProp->PropertyFlags & CPF_NoClear);
// This is inconsistent with all the other browsers, so disabling it for now
// TAttribute<bool> CanBrowseAtrribute = TAttribute<bool>::Create( TAttribute<bool>::FGetter::CreateSP( this, &FGameModeInfoCustomizer::CanBrowseDefaultClass, DefaultClassPropertyName) ) ;
// Add a row for choosing a new default class
Group.AddWidgetRow()
.NameContent()
[CreateGameModePropertyLabelWidget(DefaultClassPropertyName)]
.ValueContent()
.MaxDesiredWidth(0)
[SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth()[SNew(SBox).WidthOverride(125)[SNew(SClassPropertyEntryBox).AllowNone(bAllowNone).MetaClass(MetaClass).IsEnabled(this, &FGameModeInfoCustomizer::AllowModifyGameMode).SelectedClass(this, &FGameModeInfoCustomizer::OnGetDefaultClass, DefaultClassPropertyName).OnSetClass(FOnSetClass::CreateSP(this, &FGameModeInfoCustomizer::OnSetDefaultClass, DefaultClassPropertyName))]] + SHorizontalBox::Slot().AutoWidth()[PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnMakeSelectedDefaultClassClicked, DefaultClassPropertyName))] + SHorizontalBox::Slot().AutoWidth()[PropertyCustomizationHelpers::MakeBrowseButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnBrowseDefaultClassClicked, DefaultClassPropertyName))] + SHorizontalBox::Slot().AutoWidth()[PropertyCustomizationHelpers::MakeNewBlueprintButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnMakeNewDefaultClassClicked, DefaultClassPropertyName))]];
}
/** Add special customization for the GameMode setting */
void CustomizeGameModeSetting(IDetailLayoutBuilder& LayoutBuilder, IDetailCategoryBuilder& CategoryBuilder)
{
// Add GameMode picker widget
DefaultGameModeClassHandle = LayoutBuilder.GetProperty(GameModePropertyName);
check(DefaultGameModeClassHandle.IsValid());
IDetailPropertyRow& DefaultGameModeRow = CategoryBuilder.AddProperty(DefaultGameModeClassHandle);
// SEe if we are allowed to choose 'no' GameMode
const bool bAllowNone = !(DefaultGameModeClassHandle->GetProperty()->PropertyFlags & CPF_NoClear);
// This is inconsistent with other property
// TAttribute<bool> CanBrowseAtrribute = TAttribute<bool>(this, &FGameModeInfoCustomizer::CanBrowseGameMode);
DefaultGameModeRow
.ShowPropertyButtons(false)
.CustomWidget()
.NameContent()
[DefaultGameModeClassHandle->CreatePropertyNameWidget()]
.ValueContent()
.MaxDesiredWidth(0)
[SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth()[SNew(SBox).WidthOverride(125)[SNew(SClassPropertyEntryBox).AllowNone(bAllowNone).MetaClass(AGameModeBase::StaticClass()).SelectedClass(this, &FGameModeInfoCustomizer::GetCurrentGameModeClass).OnSetClass(FOnSetClass::CreateSP(this, &FGameModeInfoCustomizer::SetCurrentGameModeClass))]]
+ SHorizontalBox::Slot()
.AutoWidth()
[PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnUseSelectedGameModeClicked))] +
SHorizontalBox::Slot()
.AutoWidth()
[PropertyCustomizationHelpers::MakeBrowseButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnBrowseGameModeClicked))] +
SHorizontalBox::Slot()
.AutoWidth()
[PropertyCustomizationHelpers::MakeNewBlueprintButton(FSimpleDelegate::CreateSP(this, &FGameModeInfoCustomizer::OnClickNewGameMode))]];
static FName SelectedGameModeDetailsName(TEXT("SelectedGameModeDetails"));
IDetailGroup& Group = CategoryBuilder.AddGroup(SelectedGameModeDetailsName, LOCTEXT("SelectedGameModeDetails", "Selected GameMode"));
// Then add rows to show key properties and let you edit them
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, DefaultPawnClass));
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, HUDClass));
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, PlayerControllerClass));
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, GameStateClass));
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, PlayerStateClass));
CustomizeGameModeDefaultClass(Group, GET_MEMBER_NAME_CHECKED(AGameModeBase, SpectatorClass));
}
/** Get the currently set GameMode class */
const UClass* GetCurrentGameModeClass() const
{
FString ClassName;
DefaultGameModeClassHandle->GetValueAsFormattedString(ClassName);
// Blueprints may have type information before the class name, so make sure and strip that off now
ConstructorHelpers::StripObjectClass(ClassName);
// Do we have a valid cached class pointer? (Note: We can't search for the class while a save is happening)
const UClass* GameModeClass = CachedGameModeClass.Get();
if ((!GameModeClass || GameModeClass->GetPathName() != ClassName) && !GIsSavingPackage)
{
GameModeClass = FEditorClassUtils::GetClassFromString(ClassName);
CachedGameModeClass = MakeWeakObjectPtr(const_cast<UClass*>(GameModeClass));
}
return GameModeClass;
}
void SetCurrentGameModeClass(const UClass* NewGameModeClass)
{
if (DefaultGameModeClassHandle->SetValueFromFormattedString((NewGameModeClass) ? NewGameModeClass->GetPathName() : TEXT("None")) == FPropertyAccess::Success)
{
CachedGameModeClass = MakeWeakObjectPtr(const_cast<UClass*>(NewGameModeClass));
}
}
/** Get the CDO from the currently set GameMode class */
AGameModeBase* GetCurrentGameModeCDO() const
{
UClass* GameModeClass = const_cast<UClass*>(GetCurrentGameModeClass());
if (GameModeClass != NULL)
{
return GameModeClass->GetDefaultObject<AGameModeBase>();
}
else
{
return NULL;
}
}
/** Find the current default class by property name */
const UClass* OnGetDefaultClass(FName ClassPropertyName) const
{
UClass* CurrentDefaultClass = NULL;
const UClass* GameModeClass = GetCurrentGameModeClass();
if (GameModeClass != NULL)
{
FClassProperty* ClassProp = FindFieldChecked<FClassProperty>(GameModeClass, ClassPropertyName);
CurrentDefaultClass = (UClass*)ClassProp->GetObjectPropertyValue(ClassProp->ContainerPtrToValuePtr<void>(GetCurrentGameModeCDO()));
}
return CurrentDefaultClass;
}
/** Set a new default class by property name */
void OnSetDefaultClass(const UClass* NewDefaultClass, FName ClassPropertyName)
{
const UClass* GameModeClass = GetCurrentGameModeClass();
if (GameModeClass != NULL && AllowModifyGameMode())
{
FClassProperty* ClassProp = FindFieldChecked<FClassProperty>(GameModeClass, ClassPropertyName);
const UClass** DefaultClassPtr = ClassProp->ContainerPtrToValuePtr<const UClass*>(GetCurrentGameModeCDO());
*DefaultClassPtr = NewDefaultClass;
UBlueprint* Blueprint = Cast<UBlueprint>(GameModeClass->ClassGeneratedBy);
if (Blueprint)
{
// Indicate that the BP has changed and would need to be saved.
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
bool CanBrowseDefaultClass(FName ClassPropertyName) const
{
return CanSyncToClass(OnGetDefaultClass(ClassPropertyName));
}
void OnBrowseDefaultClassClicked(FName ClassPropertyName)
{
SyncBrowserToClass(OnGetDefaultClass(ClassPropertyName));
}
void OnMakeNewDefaultClassClicked(FName ClassPropertyName)
{
FClassProperty* ClassProp = FindFieldChecked<FClassProperty>(AGameModeBase::StaticClass(), ClassPropertyName);
UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprintFromClass(LOCTEXT("CreateNewBlueprint", "Create New Blueprint"), ClassProp->MetaClass, FString::Printf(TEXT("New%s"), *ClassProp->MetaClass->GetName()));
if (Blueprint != NULL && Blueprint->GeneratedClass)
{
OnSetDefaultClass(Blueprint->GeneratedClass, ClassPropertyName);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Blueprint);
}
}
void OnMakeSelectedDefaultClassClicked(FName ClassPropertyName)
{
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
FClassProperty* ClassProp = FindFieldChecked<FClassProperty>(AGameModeBase::StaticClass(), ClassPropertyName);
const UClass* SelectedClass = GEditor->GetFirstSelectedClass(ClassProp->MetaClass);
if (SelectedClass)
{
OnSetDefaultClass(SelectedClass, ClassPropertyName);
}
}
bool CanBrowseGameMode() const
{
return CanSyncToClass(GetCurrentGameModeClass());
}
void OnBrowseGameModeClicked()
{
SyncBrowserToClass(GetCurrentGameModeClass());
}
bool CanSyncToClass(const UClass* Class) const
{
return (Class != NULL && Class->ClassGeneratedBy != NULL);
}
void SyncBrowserToClass(const UClass* Class)
{
if (CanSyncToClass(Class))
{
UBlueprint* Blueprint = Cast<UBlueprint>(Class->ClassGeneratedBy);
if (ensure(Blueprint != NULL))
{
TArray<UObject*> SyncObjects;
SyncObjects.Add(Blueprint);
GEditor->SyncBrowserToObjects(SyncObjects);
}
}
}
void OnUseSelectedGameModeClicked()
{
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
const UClass* SelectedClass = GEditor->GetFirstSelectedClass(AGameModeBase::StaticClass());
if (SelectedClass)
{
DefaultGameModeClassHandle->SetValueFromFormattedString(SelectedClass->GetPathName());
}
}
void OnClickNewGameMode()
{
// Create a new GameMode BP
UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprintFromClass(LOCTEXT("CreateNewGameMode", "Create New GameMode"), AGameModeBase::StaticClass(), TEXT("NewGameMode"));
// if that worked, assign it
if (Blueprint != NULL && Blueprint->GeneratedClass)
{
DefaultGameModeClassHandle->SetValueFromFormattedString(Blueprint->GeneratedClass->GetPathName());
}
}
/** Are we allowed to modify the currently selected GameMode */
bool AllowModifyGameMode() const
{
// Only allow editing GameMode BP, not native class!
const UBlueprintGeneratedClass* GameModeBPClass = Cast<UBlueprintGeneratedClass>(GetCurrentGameModeClass());
return (GameModeBPClass != NULL);
}
private:
/** Object that owns the pointer to the GameMode we want to customize */
TWeakObjectPtr<UObject> OwningObject;
/** Name of GameMode property inside OwningObject */
FName GameModePropertyName;
/** Handle to the DefaultGameMode property */
TSharedPtr<IPropertyHandle> DefaultGameModeClassHandle;
/** Cached class pointer from the DefaultGameModeClassHandle */
mutable TWeakObjectPtr<UClass> CachedGameModeClass;
};
#undef LOCTEXT_NAMESPACE