搬了lyra的hudlayout

This commit is contained in:
2023-08-28 03:38:23 +08:00
parent b9666b7fe7
commit c90b2d2956
343 changed files with 10665 additions and 2 deletions

View File

@@ -0,0 +1,42 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "CommonGame",
"Description": "Generic gameplay classes that use the other Common plugins.",
"Category": "Gameplay",
"CreatedBy": "Epic Games, Inc.",
"CreatedByURL": "https://www.epicgames.com",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": false,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
{
"Name": "CommonGame",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "CommonUI",
"Enabled": true
},
{
"Name": "CommonUser",
"Enabled": true
},
{
"Name": "ModularGameplayActors",
"Enabled": true
},
{
"Name": "OnlineFramework",
"Enabled": true
}
]
}

View File

@@ -0,0 +1,43 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class CommonGame : ModuleRules
{
public CommonGame(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"InputCore",
"Engine",
"Slate",
"SlateCore",
"UMG",
"CommonInput",
"CommonUI",
"CommonUser",
"GameplayTags",
"ModularGameplayActors",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
}
);
}
}

View File

@@ -0,0 +1,97 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Actions/AsyncAction_CreateWidgetAsync.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "CommonUIExtensions.h"
#include "Engine/AssetManager.h"
#include "Engine/Engine.h"
#include "Engine/GameInstance.h"
#include "Engine/StreamableManager.h"
#include "UObject/Stack.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncAction_CreateWidgetAsync)
class UUserWidget;
static const FName InputFilterReason_Template = FName(TEXT("CreatingWidgetAsync"));
UAsyncAction_CreateWidgetAsync::UAsyncAction_CreateWidgetAsync(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bSuspendInputUntilComplete(true)
{
}
UAsyncAction_CreateWidgetAsync* UAsyncAction_CreateWidgetAsync::CreateWidgetAsync(UObject* InWorldContextObject, TSoftClassPtr<UUserWidget> InUserWidgetSoftClass, APlayerController* InOwningPlayer, bool bSuspendInputUntilComplete)
{
if (InUserWidgetSoftClass.IsNull())
{
FFrame::KismetExecutionMessage(TEXT("CreateWidgetAsync was passed a null UserWidgetSoftClass"), ELogVerbosity::Error);
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(InWorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UAsyncAction_CreateWidgetAsync* Action = NewObject<UAsyncAction_CreateWidgetAsync>();
Action->UserWidgetSoftClass = InUserWidgetSoftClass;
Action->OwningPlayer = InOwningPlayer;
Action->World = World;
Action->GameInstance = World->GetGameInstance();
Action->bSuspendInputUntilComplete = bSuspendInputUntilComplete;
Action->RegisterWithGameInstance(World);
return Action;
}
void UAsyncAction_CreateWidgetAsync::Activate()
{
SuspendInputToken = bSuspendInputUntilComplete ? UCommonUIExtensions::SuspendInputForPlayer(OwningPlayer.Get(), InputFilterReason_Template) : NAME_None;
TWeakObjectPtr<UAsyncAction_CreateWidgetAsync> LocalWeakThis(this);
StreamingHandle = UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(
UserWidgetSoftClass.ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(this, &ThisClass::OnWidgetLoaded),
FStreamableManager::AsyncLoadHighPriority
);
// Setup a cancel delegate so that we can resume input if this handler is canceled.
StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
[this]()
{
UCommonUIExtensions::ResumeInputForPlayer(OwningPlayer.Get(), SuspendInputToken);
})
);
}
void UAsyncAction_CreateWidgetAsync::Cancel()
{
Super::Cancel();
if (StreamingHandle.IsValid())
{
StreamingHandle->CancelHandle();
StreamingHandle.Reset();
}
}
void UAsyncAction_CreateWidgetAsync::OnWidgetLoaded()
{
if (bSuspendInputUntilComplete)
{
UCommonUIExtensions::ResumeInputForPlayer(OwningPlayer.Get(), SuspendInputToken);
}
// If the load as successful, create it, otherwise don't complete this.
TSubclassOf<UUserWidget> UserWidgetClass = UserWidgetSoftClass.Get();
if (UserWidgetClass)
{
UUserWidget* UserWidget = UWidgetBlueprintLibrary::Create(World.Get(), UserWidgetClass, OwningPlayer.Get());
OnComplete.Broadcast(UserWidget);
}
StreamingHandle.Reset();
SetReadyToDestroy();
}

View File

@@ -0,0 +1,81 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Actions/AsyncAction_PushContentToLayerForPlayer.h"
#include "Engine/Engine.h"
#include "PrimaryGameLayout.h"
#include "UObject/Stack.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncAction_PushContentToLayerForPlayer)
UAsyncAction_PushContentToLayerForPlayer::UAsyncAction_PushContentToLayerForPlayer(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UAsyncAction_PushContentToLayerForPlayer* UAsyncAction_PushContentToLayerForPlayer::PushContentToLayerForPlayer(APlayerController* InOwningPlayer, TSoftClassPtr<UCommonActivatableWidget> InWidgetClass, FGameplayTag InLayerName, bool bSuspendInputUntilComplete)
{
if (InWidgetClass.IsNull())
{
FFrame::KismetExecutionMessage(TEXT("PushContentToLayerForPlayer was passed a null WidgetClass"), ELogVerbosity::Error);
return nullptr;
}
if (UWorld* World = GEngine->GetWorldFromContextObject(InOwningPlayer, EGetWorldErrorMode::LogAndReturnNull))
{
UAsyncAction_PushContentToLayerForPlayer* Action = NewObject<UAsyncAction_PushContentToLayerForPlayer>();
Action->WidgetClass = InWidgetClass;
Action->OwningPlayerPtr = InOwningPlayer;
Action->LayerName = InLayerName;
Action->bSuspendInputUntilComplete = bSuspendInputUntilComplete;
Action->RegisterWithGameInstance(World);
return Action;
}
return nullptr;
}
void UAsyncAction_PushContentToLayerForPlayer::Cancel()
{
Super::Cancel();
if (StreamingHandle.IsValid())
{
StreamingHandle->CancelHandle();
StreamingHandle.Reset();
}
}
void UAsyncAction_PushContentToLayerForPlayer::Activate()
{
if (UPrimaryGameLayout* RootLayout = UPrimaryGameLayout::GetPrimaryGameLayout(OwningPlayerPtr.Get()))
{
TWeakObjectPtr<UAsyncAction_PushContentToLayerForPlayer> WeakThis = this;
StreamingHandle = RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(LayerName, bSuspendInputUntilComplete, WidgetClass, [this, WeakThis](EAsyncWidgetLayerState State, UCommonActivatableWidget* Widget) {
if (WeakThis.IsValid())
{
switch (State)
{
case EAsyncWidgetLayerState::Initialize:
BeforePush.Broadcast(Widget);
break;
case EAsyncWidgetLayerState::AfterPush:
AfterPush.Broadcast(Widget);
SetReadyToDestroy();
break;
case EAsyncWidgetLayerState::Canceled:
SetReadyToDestroy();
break;
}
}
SetReadyToDestroy();
});
}
else
{
SetReadyToDestroy();
}
}

View File

@@ -0,0 +1,88 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Actions/AsyncAction_ShowConfirmation.h"
#include "Engine/GameInstance.h"
#include "Messaging/CommonGameDialog.h"
#include "Messaging/CommonMessagingSubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncAction_ShowConfirmation)
UAsyncAction_ShowConfirmation::UAsyncAction_ShowConfirmation(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UAsyncAction_ShowConfirmation* UAsyncAction_ShowConfirmation::ShowConfirmationYesNo(UObject* InWorldContextObject, FText Title, FText Message)
{
UAsyncAction_ShowConfirmation* Action = NewObject<UAsyncAction_ShowConfirmation>();
Action->WorldContextObject = InWorldContextObject;
Action->Descriptor = UCommonGameDialogDescriptor::CreateConfirmationYesNo(Title, Message);
Action->RegisterWithGameInstance(InWorldContextObject);
return Action;
}
UAsyncAction_ShowConfirmation* UAsyncAction_ShowConfirmation::ShowConfirmationOkCancel(UObject* InWorldContextObject, FText Title, FText Message)
{
UAsyncAction_ShowConfirmation* Action = NewObject<UAsyncAction_ShowConfirmation>();
Action->WorldContextObject = InWorldContextObject;
Action->Descriptor = UCommonGameDialogDescriptor::CreateConfirmationOkCancel(Title, Message);
Action->RegisterWithGameInstance(InWorldContextObject);
return Action;
}
UAsyncAction_ShowConfirmation* UAsyncAction_ShowConfirmation::ShowConfirmationCustom(UObject* InWorldContextObject, UCommonGameDialogDescriptor* Descriptor)
{
UAsyncAction_ShowConfirmation* Action = NewObject<UAsyncAction_ShowConfirmation>();
Action->WorldContextObject = InWorldContextObject;
Action->Descriptor = Descriptor;
Action->RegisterWithGameInstance(InWorldContextObject);
return Action;
}
void UAsyncAction_ShowConfirmation::Activate()
{
if (WorldContextObject && !TargetLocalPlayer)
{
if (UUserWidget* UserWidget = Cast<UUserWidget>(WorldContextObject))
{
TargetLocalPlayer = UserWidget->GetOwningLocalPlayer<ULocalPlayer>();
}
else if (APlayerController* PC = Cast<APlayerController>(WorldContextObject))
{
TargetLocalPlayer = PC->GetLocalPlayer();
}
else if (UWorld* World = WorldContextObject->GetWorld())
{
if (UGameInstance* GameInstance = World->GetGameInstance<UGameInstance>())
{
TargetLocalPlayer = GameInstance->GetPrimaryPlayerController(false)->GetLocalPlayer();
}
}
}
if (TargetLocalPlayer)
{
if (UCommonMessagingSubsystem* Messaging = TargetLocalPlayer->GetSubsystem<UCommonMessagingSubsystem>())
{
FCommonMessagingResultDelegate ResultCallback = FCommonMessagingResultDelegate::CreateUObject(this, &UAsyncAction_ShowConfirmation::HandleConfirmationResult);
Messaging->ShowConfirmation(Descriptor, ResultCallback);
return;
}
}
// If we couldn't make the confirmation, just handle an unknown result and broadcast nothing
HandleConfirmationResult(ECommonMessagingResult::Unknown);
}
void UAsyncAction_ShowConfirmation::HandleConfirmationResult(ECommonMessagingResult ConfirmationResult)
{
OnResult.Broadcast(ConfirmationResult);
SetReadyToDestroy();
}

View File

@@ -0,0 +1,194 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonGameInstance.h"
#include "CommonLocalPlayer.h"
#include "CommonSessionSubsystem.h"
#include "CommonUISettings.h"
#include "CommonUserSubsystem.h"
#include "GameUIManagerSubsystem.h"
#include "ICommonUIModule.h"
#include "LogCommonGame.h"
#include "Messaging/CommonGameDialog.h"
#include "Messaging/CommonMessagingSubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonGameInstance)
UCommonGameInstance::UCommonGameInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UCommonGameInstance::HandleSystemMessage(FGameplayTag MessageType, FText Title, FText Message)
{
ULocalPlayer* FirstPlayer = GetFirstGamePlayer();
// Forward severe ones to the error dialog for the first player
if (FirstPlayer && MessageType.MatchesTag(FCommonUserTags::SystemMessage_Error))
{
if (UCommonMessagingSubsystem* Messaging = FirstPlayer->GetSubsystem<UCommonMessagingSubsystem>())
{
Messaging->ShowError(UCommonGameDialogDescriptor::CreateConfirmationOk(Title, Message));
}
}
}
void UCommonGameInstance::HandlePrivilegeChanged(const UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserAvailability OldAvailability, ECommonUserAvailability NewAvailability)
{
// By default show errors and disconnect if play privilege for first player is lost
if (Privilege == ECommonUserPrivilege::CanPlay && OldAvailability == ECommonUserAvailability::NowAvailable && NewAvailability != ECommonUserAvailability::NowAvailable)
{
UE_LOG(LogCommonGame, Error, TEXT("HandlePrivilegeChanged: Player %d no longer has permission to play the game!"), UserInfo->LocalPlayerIndex);
// TODO: Games can do something specific in subclass
// ReturnToMainMenu();
}
}
int32 UCommonGameInstance::AddLocalPlayer(ULocalPlayer* NewPlayer, FPlatformUserId UserId)
{
int32 ReturnVal = Super::AddLocalPlayer(NewPlayer, UserId);
if (ReturnVal != INDEX_NONE)
{
if (!PrimaryPlayer.IsValid())
{
UE_LOG(LogCommonGame, Log, TEXT("AddLocalPlayer: Set %s to Primary Player"), *NewPlayer->GetName());
PrimaryPlayer = NewPlayer;
}
GetSubsystem<UGameUIManagerSubsystem>()->NotifyPlayerAdded(Cast<UCommonLocalPlayer>(NewPlayer));
}
return ReturnVal;
}
bool UCommonGameInstance::RemoveLocalPlayer(ULocalPlayer* ExistingPlayer)
{
if (PrimaryPlayer == ExistingPlayer)
{
//TODO: do we want to fall back to another player?
PrimaryPlayer.Reset();
UE_LOG(LogCommonGame, Log, TEXT("RemoveLocalPlayer: Unsetting Primary Player from %s"), *ExistingPlayer->GetName());
}
GetSubsystem<UGameUIManagerSubsystem>()->NotifyPlayerDestroyed(Cast<UCommonLocalPlayer>(ExistingPlayer));
return Super::RemoveLocalPlayer(ExistingPlayer);
}
void UCommonGameInstance::Init()
{
Super::Init();
// After subsystems are initialized, hook them together
FGameplayTagContainer PlatformTraits = ICommonUIModule::GetSettings().GetPlatformTraits();
UCommonUserSubsystem* UserSubsystem = GetSubsystem<UCommonUserSubsystem>();
if (ensure(UserSubsystem))
{
UserSubsystem->SetTraitTags(PlatformTraits);
UserSubsystem->OnHandleSystemMessage.AddDynamic(this, &UCommonGameInstance::HandleSystemMessage);
UserSubsystem->OnUserPrivilegeChanged.AddDynamic(this, &UCommonGameInstance::HandlePrivilegeChanged);
}
UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>();
if (ensure(SessionSubsystem))
{
SessionSubsystem->OnUserRequestedSessionEvent.AddUObject(this, &UCommonGameInstance::OnUserRequestedSession);
}
}
void UCommonGameInstance::ResetUserAndSessionState()
{
UCommonUserSubsystem* UserSubsystem = GetSubsystem<UCommonUserSubsystem>();
if (ensure(UserSubsystem))
{
UserSubsystem->ResetUserState();
}
UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>();
if (ensure(SessionSubsystem))
{
SessionSubsystem->CleanUpSessions();
}
}
void UCommonGameInstance::ReturnToMainMenu()
{
// By default when returning to main menu we should reset everything
ResetUserAndSessionState();
Super::ReturnToMainMenu();
}
void UCommonGameInstance::OnUserRequestedSession(const FPlatformUserId& PlatformUserId, UCommonSession_SearchResult* InRequestedSession, const FOnlineResultInformation& RequestedSessionResult)
{
if (InRequestedSession)
{
SetRequestedSession(InRequestedSession);
}
else
{
HandleSystemMessage(FCommonUserTags::SystemMessage_Error, NSLOCTEXT("CommonGame", "Warning_RequestedSessionFailed", "Requested Session Failed"), RequestedSessionResult.ErrorText);
}
}
void UCommonGameInstance::SetRequestedSession(UCommonSession_SearchResult* InRequestedSession)
{
RequestedSession = InRequestedSession;
if (RequestedSession)
{
if (CanJoinRequestedSession())
{
JoinRequestedSession();
}
else
{
ResetGameAndJoinRequestedSession();
}
}
}
bool UCommonGameInstance::CanJoinRequestedSession() const
{
// Default behavior is always allow joining the requested session
return true;
}
void UCommonGameInstance::JoinRequestedSession()
{
if (RequestedSession)
{
if (ULocalPlayer* const FirstPlayer = GetFirstGamePlayer())
{
UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>();
if (ensure(SessionSubsystem))
{
// Clear our current requested session since we are now acting on it.
UCommonSession_SearchResult* LocalRequestedSession = RequestedSession;
RequestedSession = nullptr;
SessionSubsystem->JoinSession(FirstPlayer->PlayerController, LocalRequestedSession);
}
}
}
}
void UCommonGameInstance::ResetGameAndJoinRequestedSession()
{
// Default behavior is to return to the main menu. The game must call JoinRequestedSession when the game is in a ready state.
ReturnToMainMenu();
}
//void UCommonGameInstance::OnPreLoadMap(const FString& MapName)
//{
// if (!IsDedicatedServerInstance())
// {
// if (!bWasInLoadMap)
// {
// UGameUIManagerSubsystem* UIManager = GetSubsystem<UGameUIManagerSubsystem>();
// for (ULocalPlayer* LocalPlayer : LocalPlayers)
// {
// UIManager->NotifyPlayerAdded(Cast<UCommonLocalPlayer>(LocalPlayer));
// }
// }
// }
//}

View File

@@ -0,0 +1,32 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Modules/ModuleManager.h"
/**
* Implements the FCommonGameModule module.
*/
class FCommonGameModule : public IModuleInterface
{
public:
FCommonGameModule();
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
};
FCommonGameModule::FCommonGameModule()
{
}
void FCommonGameModule::StartupModule()
{
}
void FCommonGameModule::ShutdownModule()
{
}
IMPLEMENT_MODULE(FCommonGameModule, CommonGame);

View File

@@ -0,0 +1,81 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonLocalPlayer.h"
#include "Engine/GameInstance.h"
#include "GameFramework/PlayerController.h"
#include "GameUIManagerSubsystem.h"
#include "GameUIPolicy.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonLocalPlayer)
class APawn;
class APlayerState;
class FViewport;
struct FSceneViewProjectionData;
UCommonLocalPlayer::UCommonLocalPlayer()
: Super(FObjectInitializer::Get())
{
}
FDelegateHandle UCommonLocalPlayer::CallAndRegister_OnPlayerControllerSet(FPlayerControllerSetDelegate::FDelegate Delegate)
{
APlayerController* PC = GetPlayerController(GetWorld());
if (PC)
{
Delegate.Execute(this, PC);
}
return OnPlayerControllerSet.Add(Delegate);
}
FDelegateHandle UCommonLocalPlayer::CallAndRegister_OnPlayerStateSet(FPlayerStateSetDelegate::FDelegate Delegate)
{
APlayerController* PC = GetPlayerController(GetWorld());
APlayerState* PlayerState = PC ? PC->PlayerState : nullptr;
if (PlayerState)
{
Delegate.Execute(this, PlayerState);
}
return OnPlayerStateSet.Add(Delegate);
}
FDelegateHandle UCommonLocalPlayer::CallAndRegister_OnPlayerPawnSet(FPlayerPawnSetDelegate::FDelegate Delegate)
{
APlayerController* PC = GetPlayerController(GetWorld());
APawn* Pawn = PC ? PC->GetPawn() : nullptr;
if (Pawn)
{
Delegate.Execute(this, Pawn);
}
return OnPlayerPawnSet.Add(Delegate);
}
bool UCommonLocalPlayer::GetProjectionData(FViewport* Viewport, FSceneViewProjectionData& ProjectionData, int32 StereoViewIndex) const
{
if (!bIsPlayerViewEnabled)
{
return false;
}
return Super::GetProjectionData(Viewport, ProjectionData, StereoViewIndex);
}
UPrimaryGameLayout* UCommonLocalPlayer::GetRootUILayout() const
{
if (UGameUIManagerSubsystem* UIManager = GetGameInstance()->GetSubsystem<UGameUIManagerSubsystem>())
{
if (UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
{
return Policy->GetRootLayout(this);
}
}
return nullptr;
}

View File

@@ -0,0 +1,72 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonPlayerController.h"
#include "CommonLocalPlayer.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonPlayerController)
class APawn;
ACommonPlayerController::ACommonPlayerController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void ACommonPlayerController::ReceivedPlayer()
{
Super::ReceivedPlayer();
if (UCommonLocalPlayer* LocalPlayer = Cast<UCommonLocalPlayer>(Player))
{
LocalPlayer->OnPlayerControllerSet.Broadcast(LocalPlayer, this);
if (PlayerState)
{
LocalPlayer->OnPlayerStateSet.Broadcast(LocalPlayer, PlayerState);
}
}
}
void ACommonPlayerController::SetPawn(APawn* InPawn)
{
Super::SetPawn(InPawn);
if (UCommonLocalPlayer* LocalPlayer = Cast<UCommonLocalPlayer>(Player))
{
LocalPlayer->OnPlayerPawnSet.Broadcast(LocalPlayer, InPawn);
}
}
void ACommonPlayerController::OnPossess(APawn* APawn)
{
Super::OnPossess(APawn);
if (UCommonLocalPlayer* LocalPlayer = Cast<UCommonLocalPlayer>(Player))
{
LocalPlayer->OnPlayerPawnSet.Broadcast(LocalPlayer, APawn);
}
}
void ACommonPlayerController::OnUnPossess()
{
Super::OnUnPossess();
if (UCommonLocalPlayer* LocalPlayer = Cast<UCommonLocalPlayer>(Player))
{
LocalPlayer->OnPlayerPawnSet.Broadcast(LocalPlayer, nullptr);
}
}
void ACommonPlayerController::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
if (PlayerState)
{
if (UCommonLocalPlayer* LocalPlayer = Cast<UCommonLocalPlayer>(Player))
{
LocalPlayer->OnPlayerStateSet.Broadcast(LocalPlayer, PlayerState);
}
}
}

View File

@@ -0,0 +1,543 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonPlayerInputKey.h"
#include "CommonInputSubsystem.h"
#include "CommonInputTypeEnum.h"
#include "CommonLocalPlayer.h"
#include "CommonPlayerController.h"
#include "Fonts/FontMeasure.h"
#include "Framework/Application/SlateApplication.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Rendering/SlateRenderer.h"
#include "TimerManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonPlayerInputKey)
class FPaintArgs;
class FSlateRect;
#define LOCTEXT_NAMESPACE "CommonKeybindWidget"
DECLARE_LOG_CATEGORY_EXTERN(LogCommonPlayerInput, Log, All);
DEFINE_LOG_CATEGORY(LogCommonPlayerInput);
struct FSlateDrawUtil
{
static void DrawBrushCenterFit(
FSlateWindowElementList& ElementList,
uint32 InLayer,
const FGeometry& InAllottedGeometry,
const FSlateBrush* InBrush,
const FLinearColor& InTint = FLinearColor::White)
{
DrawBrushCenterFitWithOffset
(
ElementList,
InLayer,
InAllottedGeometry,
InBrush,
InTint,
FVector2D(0, 0)
);
}
static void DrawBrushCenterFitWithOffset(
FSlateWindowElementList& ElementList,
uint32 InLayer,
const FGeometry& InAllottedGeometry,
const FSlateBrush* InBrush,
const FLinearColor& InTint,
const FVector2D InOffset)
{
if (!InBrush)
{
return;
}
const FVector2D AreaSize = InAllottedGeometry.GetLocalSize();
const FVector2D ProgressSize = InBrush->GetImageSize();
const float FitScale = FMath::Min(FMath::Min(AreaSize.X / ProgressSize.X, AreaSize.Y / ProgressSize.Y), 1.0f);
const FVector2D FinalSize = FitScale * ProgressSize;
const FVector2D Offset = (InAllottedGeometry.GetLocalSize() * 0.5f) - (FinalSize * 0.5f) + InOffset;
FSlateDrawElement::MakeBox
(
ElementList,
InLayer,
InAllottedGeometry.ToPaintGeometry(FinalSize, FSlateLayoutTransform(Offset)),
InBrush,
ESlateDrawEffect::None,
InTint
);
}
};
void FMeasuredText::SetText(const FText& InText)
{
CachedText = InText;
bTextDirty = true;
}
FVector2D FMeasuredText::UpdateTextSize(const FSlateFontInfo &InFontInfo, float FontScale) const
{
if (bTextDirty)
{
bTextDirty = false;
CachedTextSize = FSlateApplication::Get().GetRenderer()->GetFontMeasureService()->Measure(CachedText, InFontInfo, FontScale);
}
return CachedTextSize;
}
UCommonPlayerInputKey::UCommonPlayerInputKey(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, BoundKeyFallback(EKeys::Invalid)
, InputTypeOverride(ECommonInputType::Count)
{
FrameSize = FVector2D(0, 0);
}
void UCommonPlayerInputKey::NativePreConstruct()
{
Super::NativePreConstruct();
UpdateKeybindWidget();
if (IsDesignTime())
{
ShowHoldBackPlate();
RecalculateDesiredSize();
}
}
void UCommonPlayerInputKey::NativeConstruct()
{
Super::NativeConstruct();
}
void UCommonPlayerInputKey::NativeDestruct()
{
if (ProgressPercentageMID)
{
// Need to restore the material on the brush before we kill off the MID.
HoldProgressBrush.SetResourceObject(ProgressPercentageMID->GetMaterial());
ProgressPercentageMID->MarkAsGarbage();
ProgressPercentageMID = nullptr;
}
Super::NativeDestruct();
}
int32 UCommonPlayerInputKey::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
int32 MaxLayer = Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
if (bDrawProgress)
{
FSlateDrawUtil::DrawBrushCenterFit
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&HoldProgressBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * HoldProgressBrush.GetTint(InWidgetStyle))
);
}
if (bDrawCountdownText)
{
const FVector2D CountdownTextOffset = (AllottedGeometry.GetLocalSize() - CountdownText.GetTextSize()) * 0.5f;
FSlateDrawElement::MakeText
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToOffsetPaintGeometry(CountdownTextOffset),
CountdownText.GetText(),
CountdownTextFont,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint())
);
}
else if (bDrawBrushForKey)
{
// Draw Shadow
FSlateDrawUtil::DrawBrushCenterFitWithOffset
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&CachedKeyBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * FLinearColor::Black),
FVector2D(1, 1)
);
FSlateDrawUtil::DrawBrushCenterFit
(
OutDrawElements,
++MaxLayer,
AllottedGeometry,
&CachedKeyBrush,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * CachedKeyBrush.GetTint(InWidgetStyle))
);
}
else if (KeybindText.GetTextSize().X > 0)
{
const FVector2D FrameOffset = (AllottedGeometry.GetLocalSize() - FrameSize) * 0.5f;
FSlateDrawElement::MakeBox
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToPaintGeometry(FrameSize, FSlateLayoutTransform(FrameOffset)),
&KeyBindTextBorder,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint() * KeyBindTextBorder.GetTint(InWidgetStyle))
);
const FVector2D ActionTextOffset = (AllottedGeometry.GetLocalSize() - KeybindText.GetTextSize()) * 0.5f;
FSlateDrawElement::MakeText
(
OutDrawElements,
++MaxLayer,
AllottedGeometry.ToOffsetPaintGeometry(ActionTextOffset),
KeybindText.GetText(),
KeyBindTextFont,
ESlateDrawEffect::None,
FLinearColor(InWidgetStyle.GetColorAndOpacityTint())
);
}
return MaxLayer;
}
void UCommonPlayerInputKey::StartHoldProgress(FName HoldActionName, float HoldDuration)
{
if (HoldActionName == BoundAction && ensureMsgf(HoldDuration > 0.0f, TEXT("Trying to perform hold action \"%s\" with no HoldDuration"), *BoundAction.ToString()))
{
HoldKeybindDuration = HoldDuration;
HoldKeybindStartTime = GetWorld()->GetRealTimeSeconds();
UpdateHoldProgress();
}
}
void UCommonPlayerInputKey::StopHoldProgress(FName HoldActionName, bool bCompletedSuccessfully)
{
if (HoldActionName == BoundAction)
{
HoldKeybindStartTime = 0.f;
HoldKeybindDuration = 0.f;
if (ensure(ProgressPercentageMID))
{
ProgressPercentageMID->SetScalarParameterValue(PercentageMaterialParameterName, 0.f);
}
if (bDrawCountdownText)
{
bDrawCountdownText = false;
Invalidate(EInvalidateWidget::Paint);
RecalculateDesiredSize();
}
}
}
void UCommonPlayerInputKey::SyncHoldProgress()
{
// If we had an active hold action, stop it
if (HoldKeybindStartTime > 0.f)
{
StopHoldProgress(BoundAction, false);
}
}
void UCommonPlayerInputKey::UpdateHoldProgress()
{
if (HoldKeybindStartTime != 0.f && HoldKeybindDuration > 0.f)
{
const float CurrentTime = GetWorld()->GetRealTimeSeconds();
const float ElapsedTime = FMath::Min(CurrentTime - HoldKeybindStartTime, HoldKeybindDuration);
const float RemainingTime = FMath::Max(0.0f, HoldKeybindDuration - ElapsedTime);
if (ElapsedTime < HoldKeybindDuration && ensure(ProgressPercentageMID))
{
const float HoldKeybindPercentage = ElapsedTime / HoldKeybindDuration;
ProgressPercentageMID->SetScalarParameterValue(PercentageMaterialParameterName, HoldKeybindPercentage);
// Schedule a callback for next tick to update the hold progress again.
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::UpdateHoldProgress);
}
if (bShowTimeCountDown)
{
FNumberFormattingOptions Options;
Options.MinimumFractionalDigits = 1;
Options.MaximumFractionalDigits = 1;
CountdownText.SetText(FText::AsNumber(RemainingTime, &Options));
bDrawCountdownText = true;
Invalidate(EInvalidateWidget::Paint);
RecalculateDesiredSize();
}
}
}
void UCommonPlayerInputKey::UpdateKeybindWidget()
{
if (!GetOwningPlayer<ACommonPlayerController>())
{
bWaitingForPlayerController = true;
return;
}
UCommonInputSubsystem* CommonInputSubsystem = GetInputSubsystem();
if (CommonInputSubsystem && !CommonInputSubsystem->ShouldShowInputKeys())
{
SetVisibility(ESlateVisibility::Collapsed);
return;
}
const bool bIsUsingGamepad = (InputTypeOverride == ECommonInputType::Gamepad) || ((CommonInputSubsystem != nullptr) && (CommonInputSubsystem->GetCurrentInputType() == ECommonInputType::Gamepad)) ;
if (!BoundKey.IsValid())
{
BoundKey = BoundKeyFallback;
}
UE_LOG(LogCommonPlayerInput, Verbose, TEXT("UCommonKeybindWidget::UpdateKeybindWidget: Action: %s Key: %s"), *(BoundAction.ToString()), *(BoundKey.ToString()));
// Must be called before Update, due to the creation of ProgressPercentageMID which will be used in Update
SetupHoldKeybind();
bool NewDrawBrushForKey = false;
bool NeedToRecalcSize = false;
if (BoundKey.IsValid())
{
SetVisibility(ESlateVisibility::HitTestInvisible);
ShowHoldBackPlate();
NeedToRecalcSize = true;
}
else
{
if (bShowUnboundStatus)
{
SetVisibility(ESlateVisibility::HitTestInvisible);
NewDrawBrushForKey = false;
KeybindText.SetText(LOCTEXT("Unbound", "Unbound"));
NeedToRecalcSize = true;
}
else
{
SetVisibility(ESlateVisibility::Collapsed);
}
}
if (bDrawBrushForKey != NewDrawBrushForKey)
{
bDrawBrushForKey = NewDrawBrushForKey;
Invalidate(EInvalidateWidget::Paint);
}
// As RecalculateDesiredSize relies on the bDrawBrushForKey
// we shouldn't call it until that value has been finalized
// for the update
if (NeedToRecalcSize)
{
RecalculateDesiredSize();
}
}
void UCommonPlayerInputKey::SetBoundKey(FKey NewKey)
{
if (NewKey != BoundKey)
{
BoundKeyFallback = NewKey;
BoundAction = NAME_None;
UpdateKeybindWidget();
}
}
void UCommonPlayerInputKey::SetBoundAction(FName NewBoundAction)
{
bool bUpdateWidget = true;
if (BoundAction != NewBoundAction)
{
BoundAction = NewBoundAction;
}
if (bUpdateWidget)
{
UpdateKeybindWidget();
}
}
void UCommonPlayerInputKey::NativeOnInitialized()
{
Super::NativeOnInitialized();
if (UCommonLocalPlayer* CommonLocalPlayer = GetOwningLocalPlayer<UCommonLocalPlayer>())
{
CommonLocalPlayer->OnPlayerControllerSet.AddUObject(this, &ThisClass::HandlePlayerControllerSet);
}
}
void UCommonPlayerInputKey::SetForcedHoldKeybind(bool InForcedHoldKeybind)
{
if (InForcedHoldKeybind)
{
SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus::ForcedHold);
}
else
{
SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus::NoForcedHold);
}
}
void UCommonPlayerInputKey::SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus InForcedHoldKeybindStatus)
{
ForcedHoldKeybindStatus = InForcedHoldKeybindStatus;
UpdateKeybindWidget();
}
void UCommonPlayerInputKey::SetShowProgressCountDown(bool bShow)
{
bShowTimeCountDown = bShow;
}
void UCommonPlayerInputKey::SetupHoldKeybind()
{
ACommonPlayerController* OwningCommonPlayer = Cast<ACommonPlayerController>(GetOwningPlayer());
// Setup the hold
if (ForcedHoldKeybindStatus == ECommonKeybindForcedHoldStatus::ForcedHold)
{
bIsHoldKeybind = true;
}
else if (ForcedHoldKeybindStatus == ECommonKeybindForcedHoldStatus::NeverShowHold)
{
bIsHoldKeybind = false;
}
if (ensure(OwningCommonPlayer))
{
if (bIsHoldKeybind)
{
// Setup the ProgressPercentageMID
if (ProgressPercentageMID == nullptr)
{
if (UMaterialInterface* Material = Cast<UMaterialInterface>(HoldProgressBrush.GetResourceObject()))
{
ProgressPercentageMID = UMaterialInstanceDynamic::Create(Material, this);
HoldProgressBrush.SetResourceObject(ProgressPercentageMID);
}
}
SyncHoldProgress();
}
}
}
void UCommonPlayerInputKey::ShowHoldBackPlate()
{
bool bDirty = false;
if (IsHoldKeybind())
{
float BrushSizeAsValue = 32.0f;
float DesiredBoxSize = BrushSizeAsValue + 10.0f;
if (!bDrawBrushForKey)
{
DesiredBoxSize += 14.0f;
}
const FVector2D NewDesiredBrushSize(DesiredBoxSize, DesiredBoxSize);
if (HoldProgressBrush.GetImageSize() != NewDesiredBrushSize)
{
HoldProgressBrush.SetImageSize(NewDesiredBrushSize);
bDirty = true;
}
if (!bDrawProgress)
{
bDrawProgress = true;
bDirty = true;
}
static const FName BackAlphaName = TEXT("BackAlpha");
static const FName OutlineAlphaName = TEXT("OutlineAlpha");
if (ProgressPercentageMID)
{
ProgressPercentageMID->SetScalarParameterValue(BackAlphaName, 0.2f);
ProgressPercentageMID->SetScalarParameterValue(OutlineAlphaName, 0.4f);
}
}
else
{
if (bDrawProgress)
{
bDrawProgress = false;
bDirty = true;
}
}
if (bDirty)
{
Invalidate(EInvalidateWidget::Paint);
}
}
void UCommonPlayerInputKey::HandlePlayerControllerSet(UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController)
{
if (bWaitingForPlayerController && GetOwningPlayer<ACommonPlayerController>())
{
UpdateKeybindWidget();
bWaitingForPlayerController = false;
}
}
void UCommonPlayerInputKey::RecalculateDesiredSize()
{
FVector2D MaximumDesiredSize(0, 0);
float LayoutScale = 1;
if (bDrawProgress)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, HoldProgressBrush.GetImageSize());
}
if (bDrawCountdownText)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, CountdownText.UpdateTextSize(CountdownTextFont, LayoutScale));
}
else if (bDrawBrushForKey)
{
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, CachedKeyBrush.GetImageSize());
}
else
{
const FVector2D KeybindTextSize = KeybindText.UpdateTextSize(KeyBindTextFont, LayoutScale);
FrameSize = FVector2D::Max(KeybindTextSize, KeybindFrameMinimumSize) + KeybindTextPadding.GetDesiredSize();
MaximumDesiredSize = FVector2D::Max(MaximumDesiredSize, FrameSize);
}
SetMinimumDesiredSize(MaximumDesiredSize);
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,171 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonUIExtensions.h"
#include "CommonInputSubsystem.h"
#include "CommonInputTypeEnum.h"
#include "CommonLocalPlayer.h"
#include "Engine/GameInstance.h"
#include "GameUIManagerSubsystem.h"
#include "GameUIPolicy.h"
#include "PrimaryGameLayout.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonUIExtensions)
int32 UCommonUIExtensions::InputSuspensions = 0;
ECommonInputType UCommonUIExtensions::GetOwningPlayerInputType(const UUserWidget* WidgetContextObject)
{
if (WidgetContextObject)
{
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
{
return InputSubsystem->GetCurrentInputType();
}
}
return ECommonInputType::Count;
}
bool UCommonUIExtensions::IsOwningPlayerUsingTouch(const UUserWidget* WidgetContextObject)
{
if (WidgetContextObject)
{
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
{
return InputSubsystem->GetCurrentInputType() == ECommonInputType::Touch;
}
}
return false;
}
bool UCommonUIExtensions::IsOwningPlayerUsingGamepad(const UUserWidget* WidgetContextObject)
{
if (WidgetContextObject)
{
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
{
return InputSubsystem->GetCurrentInputType() == ECommonInputType::Gamepad;
}
}
return false;
}
UCommonActivatableWidget* UCommonUIExtensions::PushContentToLayer_ForPlayer(const ULocalPlayer* LocalPlayer, FGameplayTag LayerName, TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!ensure(LocalPlayer) || !ensure(WidgetClass != nullptr))
{
return nullptr;
}
if (UGameUIManagerSubsystem* UIManager = LocalPlayer->GetGameInstance()->GetSubsystem<UGameUIManagerSubsystem>())
{
if (UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
{
if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CastChecked<UCommonLocalPlayer>(LocalPlayer)))
{
return RootLayout->PushWidgetToLayerStack(LayerName, WidgetClass);
}
}
}
return nullptr;
}
void UCommonUIExtensions::PushStreamedContentToLayer_ForPlayer(const ULocalPlayer* LocalPlayer, FGameplayTag LayerName, TSoftClassPtr<UCommonActivatableWidget> WidgetClass)
{
if (!ensure(LocalPlayer) || !ensure(!WidgetClass.IsNull()))
{
return;
}
if (UGameUIManagerSubsystem* UIManager = LocalPlayer->GetGameInstance()->GetSubsystem<UGameUIManagerSubsystem>())
{
if (UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
{
if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CastChecked<UCommonLocalPlayer>(LocalPlayer)))
{
const bool bSuspendInputUntilComplete = true;
RootLayout->PushWidgetToLayerStackAsync(LayerName, bSuspendInputUntilComplete, WidgetClass);
}
}
}
}
void UCommonUIExtensions::PopContentFromLayer(UCommonActivatableWidget* ActivatableWidget)
{
if (!ActivatableWidget)
{
// Ignore request to pop an already deleted widget
return;
}
if (const ULocalPlayer* LocalPlayer = ActivatableWidget->GetOwningLocalPlayer())
{
if (const UGameUIManagerSubsystem* UIManager = LocalPlayer->GetGameInstance()->GetSubsystem<UGameUIManagerSubsystem>())
{
if (const UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
{
if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CastChecked<UCommonLocalPlayer>(LocalPlayer)))
{
RootLayout->FindAndRemoveWidgetFromLayer(ActivatableWidget);
}
}
}
}
}
ULocalPlayer* UCommonUIExtensions::GetLocalPlayerFromController(APlayerController* PlayerController)
{
if (PlayerController)
{
return Cast<ULocalPlayer>(PlayerController->Player);
}
return nullptr;
}
FName UCommonUIExtensions::SuspendInputForPlayer(APlayerController* PlayerController, FName SuspendReason)
{
return SuspendInputForPlayer(PlayerController ? PlayerController->GetLocalPlayer() : nullptr, SuspendReason);
}
FName UCommonUIExtensions::SuspendInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendReason)
{
if (UCommonInputSubsystem* CommonInputSubsystem = UCommonInputSubsystem::Get(LocalPlayer))
{
InputSuspensions++;
FName SuspendToken = SuspendReason;
SuspendToken.SetNumber(InputSuspensions);
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::MouseAndKeyboard, SuspendToken, true);
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Gamepad, SuspendToken, true);
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Touch, SuspendToken, true);
return SuspendToken;
}
return NAME_None;
}
void UCommonUIExtensions::ResumeInputForPlayer(APlayerController* PlayerController, FName SuspendToken)
{
ResumeInputForPlayer(PlayerController ? PlayerController->GetLocalPlayer() : nullptr, SuspendToken);
}
void UCommonUIExtensions::ResumeInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendToken)
{
if (SuspendToken == NAME_None)
{
return;
}
if (UCommonInputSubsystem* CommonInputSubsystem = UCommonInputSubsystem::Get(LocalPlayer))
{
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::MouseAndKeyboard, SuspendToken, false);
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Gamepad, SuspendToken, false);
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Touch, SuspendToken, false);
}
}

View File

@@ -0,0 +1,76 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameUIManagerSubsystem.h"
#include "Engine/GameInstance.h"
#include "GameUIPolicy.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameUIManagerSubsystem)
class FSubsystemCollectionBase;
class UClass;
void UGameUIManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (!CurrentPolicy && !DefaultUIPolicyClass.IsNull())
{
TSubclassOf<UGameUIPolicy> PolicyClass = DefaultUIPolicyClass.LoadSynchronous();
SwitchToPolicy(NewObject<UGameUIPolicy>(this, PolicyClass));
}
}
void UGameUIManagerSubsystem::Deinitialize()
{
Super::Deinitialize();
SwitchToPolicy(nullptr);
}
bool UGameUIManagerSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
if (!CastChecked<UGameInstance>(Outer)->IsDedicatedServerInstance())
{
TArray<UClass*> ChildClasses;
GetDerivedClasses(GetClass(), ChildClasses, false);
// Only create an instance if there is no override implementation defined elsewhere
return ChildClasses.Num() == 0;
}
return false;
}
void UGameUIManagerSubsystem::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer)
{
if (ensure(LocalPlayer) && CurrentPolicy)
{
CurrentPolicy->NotifyPlayerAdded(LocalPlayer);
}
}
void UGameUIManagerSubsystem::NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer)
{
if (LocalPlayer && CurrentPolicy)
{
CurrentPolicy->NotifyPlayerRemoved(LocalPlayer);
}
}
void UGameUIManagerSubsystem::NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer)
{
if (LocalPlayer && CurrentPolicy)
{
CurrentPolicy->NotifyPlayerDestroyed(LocalPlayer);
}
}
void UGameUIManagerSubsystem::SwitchToPolicy(UGameUIPolicy* InPolicy)
{
if (CurrentPolicy != InPolicy)
{
CurrentPolicy = InPolicy;
}
}

View File

@@ -0,0 +1,203 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameUIPolicy.h"
#include "Engine/GameInstance.h"
#include "Framework/Application/SlateApplication.h"
#include "GameUIManagerSubsystem.h"
#include "CommonLocalPlayer.h"
#include "PrimaryGameLayout.h"
#include "Engine/Engine.h"
#include "LogCommonGame.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameUIPolicy)
// Static
UGameUIPolicy* UGameUIPolicy::GetGameUIPolicy(const UObject* WorldContextObject)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
if (UGameInstance* GameInstance = World->GetGameInstance())
{
if (UGameUIManagerSubsystem* UIManager = UGameInstance::GetSubsystem<UGameUIManagerSubsystem>(GameInstance))
{
return UIManager->GetCurrentUIPolicy();
}
}
}
return nullptr;
}
UGameUIManagerSubsystem* UGameUIPolicy::GetOwningUIManager() const
{
return CastChecked<UGameUIManagerSubsystem>(GetOuter());
}
UWorld* UGameUIPolicy::GetWorld() const
{
return GetOwningUIManager()->GetGameInstance()->GetWorld();
}
UPrimaryGameLayout* UGameUIPolicy::GetRootLayout(const UCommonLocalPlayer* LocalPlayer) const
{
const FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer);
return LayoutInfo ? LayoutInfo->RootLayout : nullptr;
}
void UGameUIPolicy::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer)
{
LocalPlayer->OnPlayerControllerSet.AddWeakLambda(this, [this](UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController)
{
NotifyPlayerRemoved(LocalPlayer);
if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
{
AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);
LayoutInfo->bAddedToViewport = true;
}
else
{
CreateLayoutWidget(LocalPlayer);
}
});
if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
{
AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);
LayoutInfo->bAddedToViewport = true;
}
else
{
CreateLayoutWidget(LocalPlayer);
}
}
void UGameUIPolicy::NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer)
{
if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
{
RemoveLayoutFromViewport(LocalPlayer, LayoutInfo->RootLayout);
LayoutInfo->bAddedToViewport = false;
if (LocalMultiplayerInteractionMode == ELocalMultiplayerInteractionMode::SingleToggle && !LocalPlayer->IsPrimaryPlayer())
{
UPrimaryGameLayout* RootLayout = LayoutInfo->RootLayout;
if (RootLayout && !RootLayout->IsDormant())
{
// We're removing a secondary player's root while it's in control - transfer control back to the primary player's root
RootLayout->SetIsDormant(true);
for (const FRootViewportLayoutInfo& RootLayoutInfo : RootViewportLayouts)
{
if (RootLayoutInfo.LocalPlayer->IsPrimaryPlayer())
{
if (UPrimaryGameLayout* PrimaryRootLayout = RootLayoutInfo.RootLayout)
{
PrimaryRootLayout->SetIsDormant(false);
}
}
}
}
}
}
}
void UGameUIPolicy::NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer)
{
NotifyPlayerRemoved(LocalPlayer);
LocalPlayer->OnPlayerControllerSet.RemoveAll(this);
const int32 LayoutInfoIdx = RootViewportLayouts.IndexOfByKey(LocalPlayer);
if (LayoutInfoIdx != INDEX_NONE)
{
UPrimaryGameLayout* Layout = RootViewportLayouts[LayoutInfoIdx].RootLayout;
RootViewportLayouts.RemoveAt(LayoutInfoIdx);
RemoveLayoutFromViewport(LocalPlayer, Layout);
OnRootLayoutReleased(LocalPlayer, Layout);
}
}
void UGameUIPolicy::AddLayoutToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
{
UE_LOG(LogCommonGame, Log, TEXT("[%s] is adding player [%s]'s root layout [%s] to the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
Layout->SetPlayerContext(FLocalPlayerContext(LocalPlayer));
Layout->AddToPlayerScreen(1000);
OnRootLayoutAddedToViewport(LocalPlayer, Layout);
}
void UGameUIPolicy::RemoveLayoutFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
{
TWeakPtr<SWidget> LayoutSlateWidget = Layout->GetCachedWidget();
if (LayoutSlateWidget.IsValid())
{
UE_LOG(LogCommonGame, Log, TEXT("[%s] is removing player [%s]'s root layout [%s] from the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
Layout->RemoveFromParent();
if (LayoutSlateWidget.IsValid())
{
UE_LOG(LogCommonGame, Log, TEXT("Player [%s]'s root layout [%s] has been removed from the viewport, but other references to its underlying Slate widget still exist. Noting in case we leak it."), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
}
OnRootLayoutRemovedFromViewport(LocalPlayer, Layout);
}
}
void UGameUIPolicy::OnRootLayoutAddedToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
{
#if WITH_EDITOR
if (GIsEditor && LocalPlayer->IsPrimaryPlayer())
{
// So our controller will work in PIE without needing to click in the viewport
FSlateApplication::Get().SetUserFocusToGameViewport(0);
}
#endif
}
void UGameUIPolicy::OnRootLayoutRemovedFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
{
}
void UGameUIPolicy::OnRootLayoutReleased(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
{
}
void UGameUIPolicy::RequestPrimaryControl(UPrimaryGameLayout* Layout)
{
if (LocalMultiplayerInteractionMode == ELocalMultiplayerInteractionMode::SingleToggle && Layout->IsDormant())
{
for (const FRootViewportLayoutInfo& LayoutInfo : RootViewportLayouts)
{
UPrimaryGameLayout* RootLayout = LayoutInfo.RootLayout;
if (RootLayout && !RootLayout->IsDormant())
{
RootLayout->SetIsDormant(true);
break;
}
}
Layout->SetIsDormant(false);
}
}
void UGameUIPolicy::CreateLayoutWidget(UCommonLocalPlayer* LocalPlayer)
{
if (APlayerController* PlayerController = LocalPlayer->GetPlayerController(GetWorld()))
{
TSubclassOf<UPrimaryGameLayout> LayoutWidgetClass = GetLayoutWidgetClass(LocalPlayer);
if (ensure(LayoutWidgetClass && !LayoutWidgetClass->HasAnyClassFlags(CLASS_Abstract)))
{
UPrimaryGameLayout* NewLayoutObject = CreateWidget<UPrimaryGameLayout>(PlayerController, LayoutWidgetClass);
RootViewportLayouts.Emplace(LocalPlayer, NewLayoutObject, true);
AddLayoutToViewport(LocalPlayer, NewLayoutObject);
}
}
}
TSubclassOf<UPrimaryGameLayout> UGameUIPolicy::GetLayoutWidgetClass(UCommonLocalPlayer* LocalPlayer)
{
return LayoutClass.LoadSynchronous();
}

View File

@@ -0,0 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LogCommonGame.h"
DEFINE_LOG_CATEGORY(LogCommonGame);

View File

@@ -0,0 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Logging/LogMacros.h"
DECLARE_LOG_CATEGORY_EXTERN(LogCommonGame, Log, All);

View File

@@ -0,0 +1,106 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Messaging/CommonGameDialog.h"
#include "Messaging/CommonMessagingSubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonGameDialog)
#define LOCTEXT_NAMESPACE "Messaging"
UCommonGameDialogDescriptor* UCommonGameDialogDescriptor::CreateConfirmationOk(const FText& Header, const FText& Body)
{
UCommonGameDialogDescriptor* Descriptor = NewObject<UCommonGameDialogDescriptor>();
Descriptor->Header = Header;
Descriptor->Body = Body;
FConfirmationDialogAction ConfirmAction;
ConfirmAction.Result = ECommonMessagingResult::Confirmed;
ConfirmAction.OptionalDisplayText = LOCTEXT("Ok", "Ok");
Descriptor->ButtonActions.Add(ConfirmAction);
return Descriptor;
}
UCommonGameDialogDescriptor* UCommonGameDialogDescriptor::CreateConfirmationOkCancel(const FText& Header, const FText& Body)
{
UCommonGameDialogDescriptor* Descriptor = NewObject<UCommonGameDialogDescriptor>();
Descriptor->Header = Header;
Descriptor->Body = Body;
FConfirmationDialogAction ConfirmAction;
ConfirmAction.Result = ECommonMessagingResult::Confirmed;
ConfirmAction.OptionalDisplayText = LOCTEXT("Ok", "Ok");
FConfirmationDialogAction CancelAction;
CancelAction.Result = ECommonMessagingResult::Cancelled;
CancelAction.OptionalDisplayText = LOCTEXT("Cancel", "Cancel");
Descriptor->ButtonActions.Add(ConfirmAction);
Descriptor->ButtonActions.Add(CancelAction);
return Descriptor;
}
UCommonGameDialogDescriptor* UCommonGameDialogDescriptor::CreateConfirmationYesNo(const FText& Header, const FText& Body)
{
UCommonGameDialogDescriptor* Descriptor = NewObject<UCommonGameDialogDescriptor>();
Descriptor->Header = Header;
Descriptor->Body = Body;
FConfirmationDialogAction ConfirmAction;
ConfirmAction.Result = ECommonMessagingResult::Confirmed;
ConfirmAction.OptionalDisplayText = LOCTEXT("Yes", "Yes");
FConfirmationDialogAction DeclineAction;
DeclineAction.Result = ECommonMessagingResult::Declined;
DeclineAction.OptionalDisplayText = LOCTEXT("No", "No");
Descriptor->ButtonActions.Add(ConfirmAction);
Descriptor->ButtonActions.Add(DeclineAction);
return Descriptor;
}
UCommonGameDialogDescriptor* UCommonGameDialogDescriptor::CreateConfirmationYesNoCancel(const FText& Header, const FText& Body)
{
UCommonGameDialogDescriptor* Descriptor = NewObject<UCommonGameDialogDescriptor>();
Descriptor->Header = Header;
Descriptor->Body = Body;
FConfirmationDialogAction ConfirmAction;
ConfirmAction.Result = ECommonMessagingResult::Confirmed;
ConfirmAction.OptionalDisplayText = LOCTEXT("Yes", "Yes");
FConfirmationDialogAction DeclineAction;
DeclineAction.Result = ECommonMessagingResult::Declined;
DeclineAction.OptionalDisplayText = LOCTEXT("No", "No");
FConfirmationDialogAction CancelAction;
CancelAction.Result = ECommonMessagingResult::Cancelled;
CancelAction.OptionalDisplayText = LOCTEXT("Cancel", "Cancel");
Descriptor->ButtonActions.Add(ConfirmAction);
Descriptor->ButtonActions.Add(DeclineAction);
Descriptor->ButtonActions.Add(CancelAction);
return Descriptor;
}
UCommonGameDialog::UCommonGameDialog()
{
}
void UCommonGameDialog::SetupDialog(UCommonGameDialogDescriptor* Descriptor, FCommonMessagingResultDelegate ResultCallback)
{
}
void UCommonGameDialog::KillDialog()
{
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,46 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Messaging/CommonMessagingSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "UObject/UObjectHash.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonMessagingSubsystem)
class FSubsystemCollectionBase;
class UClass;
void UCommonMessagingSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void UCommonMessagingSubsystem::Deinitialize()
{
Super::Deinitialize();
}
bool UCommonMessagingSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
if (!CastChecked<ULocalPlayer>(Outer)->GetGameInstance()->IsDedicatedServerInstance())
{
TArray<UClass*> ChildClasses;
GetDerivedClasses(GetClass(), ChildClasses, false);
// Only create an instance if there is no override implementation defined elsewhere
return ChildClasses.Num() == 0;
}
return false;
}
void UCommonMessagingSubsystem::ShowConfirmation(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback)
{
}
void UCommonMessagingSubsystem::ShowError(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback)
{
}

View File

@@ -0,0 +1,132 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PrimaryGameLayout.h"
#include "CommonLocalPlayer.h"
#include "Engine/GameInstance.h"
#include "GameUIManagerSubsystem.h"
#include "GameUIPolicy.h"
#include "Kismet/GameplayStatics.h"
#include "LogCommonGame.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PrimaryGameLayout)
class UObject;
/*static*/ UPrimaryGameLayout* UPrimaryGameLayout::GetPrimaryGameLayoutForPrimaryPlayer(const UObject* WorldContextObject)
{
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldContextObject);
APlayerController* PlayerController = GameInstance->GetPrimaryPlayerController(false);
return GetPrimaryGameLayout(PlayerController);
}
/*static*/ UPrimaryGameLayout* UPrimaryGameLayout::GetPrimaryGameLayout(APlayerController* PlayerController)
{
return PlayerController ? GetPrimaryGameLayout(Cast<UCommonLocalPlayer>(PlayerController->Player)) : nullptr;
}
/*static*/ UPrimaryGameLayout* UPrimaryGameLayout::GetPrimaryGameLayout(ULocalPlayer* LocalPlayer)
{
if (LocalPlayer)
{
const UCommonLocalPlayer* CommonLocalPlayer = CastChecked<UCommonLocalPlayer>(LocalPlayer);
if (const UGameInstance* GameInstance = CommonLocalPlayer->GetGameInstance())
{
if (UGameUIManagerSubsystem* UIManager = GameInstance->GetSubsystem<UGameUIManagerSubsystem>())
{
if (const UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
{
if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CommonLocalPlayer))
{
return RootLayout;
}
}
}
}
}
return nullptr;
}
UPrimaryGameLayout::UPrimaryGameLayout(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UPrimaryGameLayout::SetIsDormant(bool InDormant)
{
if (bIsDormant != InDormant)
{
const ULocalPlayer* LP = GetOwningLocalPlayer();
const int32 PlayerId = LP ? LP->GetControllerId() : -1;
const TCHAR* OldDormancyStr = bIsDormant ? TEXT("Dormant") : TEXT("Not-Dormant");
const TCHAR* NewDormancyStr = InDormant ? TEXT("Dormant") : TEXT("Not-Dormant");
const TCHAR* PrimaryPlayerStr = LP && LP->IsPrimaryPlayer() ? TEXT("[Primary]") : TEXT("[Non-Primary]");
UE_LOG(LogCommonGame, Display, TEXT("%s PrimaryGameLayout Dormancy changed for [%d] from [%s] to [%s]"), PrimaryPlayerStr, PlayerId, OldDormancyStr, NewDormancyStr);
bIsDormant = InDormant;
OnIsDormantChanged();
}
}
void UPrimaryGameLayout::OnIsDormantChanged()
{
//@TODO NDarnell Determine what to do with dormancy, in the past we treated dormancy as a way to shutoff rendering
//and the view for the other local players when we force multiple players to use the player view of a single player.
//if (UCommonLocalPlayer* LocalPlayer = GetOwningLocalPlayer<UCommonLocalPlayer>())
//{
// // When the root layout is dormant, we don't want to render anything from the owner's view either
// LocalPlayer->SetIsPlayerViewEnabled(!bIsDormant);
//}
//SetVisibility(bIsDormant ? ESlateVisibility::Collapsed : ESlateVisibility::SelfHitTestInvisible);
//OnLayoutDormancyChanged().Broadcast(bIsDormant);
}
void UPrimaryGameLayout::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget)
{
if (!IsDesignTime())
{
LayerWidget->OnTransitioningChanged.AddUObject(this, &UPrimaryGameLayout::OnWidgetStackTransitioning);
// TODO: Consider allowing a transition duration, we currently set it to 0, because if it's not 0, the
// transition effect will cause focus to not transition properly to the new widgets when using
// gamepad always.
LayerWidget->SetTransitionDuration(0.0);
Layers.Add(LayerTag, LayerWidget);
}
}
void UPrimaryGameLayout::OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning)
{
if (bIsTransitioning)
{
const FName SuspendToken = UCommonUIExtensions::SuspendInputForPlayer(GetOwningLocalPlayer(), TEXT("GlobalStackTransion"));
SuspendInputTokens.Add(SuspendToken);
}
else
{
if (ensure(SuspendInputTokens.Num() > 0))
{
const FName SuspendToken = SuspendInputTokens.Pop();
UCommonUIExtensions::ResumeInputForPlayer(GetOwningLocalPlayer(), SuspendToken);
}
}
}
void UPrimaryGameLayout::FindAndRemoveWidgetFromLayer(UCommonActivatableWidget* ActivatableWidget)
{
// We're not sure what layer the widget is on so go searching.
for (const auto& LayerKVP : Layers)
{
LayerKVP.Value->RemoveWidget(*ActivatableWidget);
}
}
UCommonActivatableWidgetContainerBase* UPrimaryGameLayout::GetLayerWidget(FGameplayTag LayerName)
{
return Layers.FindRef(LayerName);
}

View File

@@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/CancellableAsyncAction.h"
#include "UObject/SoftObjectPtr.h"
#include "AsyncAction_CreateWidgetAsync.generated.h"
class APlayerController;
class UGameInstance;
class UUserWidget;
class UWorld;
struct FFrame;
struct FStreamableHandle;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCreateWidgetAsyncDelegate, UUserWidget*, UserWidget);
/**
* Load the widget class asynchronously, the instance the widget after the loading completes, and return it on OnComplete.
*/
UCLASS(BlueprintType)
class COMMONGAME_API UAsyncAction_CreateWidgetAsync : public UCancellableAsyncAction
{
GENERATED_UCLASS_BODY()
public:
virtual void Cancel() override;
UFUNCTION(BlueprintCallable, BlueprintCosmetic, meta=(WorldContext = "WorldContextObject", BlueprintInternalUseOnly="true"))
static UAsyncAction_CreateWidgetAsync* CreateWidgetAsync(UObject* WorldContextObject, TSoftClassPtr<UUserWidget> UserWidgetSoftClass, APlayerController* OwningPlayer, bool bSuspendInputUntilComplete = true);
virtual void Activate() override;
public:
UPROPERTY(BlueprintAssignable)
FCreateWidgetAsyncDelegate OnComplete;
private:
void OnWidgetLoaded();
FName SuspendInputToken;
TWeakObjectPtr<APlayerController> OwningPlayer;
TWeakObjectPtr<UWorld> World;
TWeakObjectPtr<UGameInstance> GameInstance;
bool bSuspendInputUntilComplete;
TSoftClassPtr<UUserWidget> UserWidgetSoftClass;
TSharedPtr<FStreamableHandle> StreamingHandle;
};

View File

@@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/CancellableAsyncAction.h"
#include "GameplayTagContainer.h"
#include "UObject/SoftObjectPtr.h"
#include "AsyncAction_PushContentToLayerForPlayer.generated.h"
class APlayerController;
class UCommonActivatableWidget;
class UObject;
struct FFrame;
struct FStreamableHandle;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPushContentToLayerForPlayerAsyncDelegate, UCommonActivatableWidget*, UserWidget);
/**
*
*/
UCLASS(BlueprintType)
class COMMONGAME_API UAsyncAction_PushContentToLayerForPlayer : public UCancellableAsyncAction
{
GENERATED_UCLASS_BODY()
public:
virtual void Cancel() override;
UFUNCTION(BlueprintCallable, BlueprintCosmetic, meta=(WorldContext = "WorldContextObject", BlueprintInternalUseOnly="true"))
static UAsyncAction_PushContentToLayerForPlayer* PushContentToLayerForPlayer(APlayerController* OwningPlayer, UPARAM(meta = (AllowAbstract=false)) TSoftClassPtr<UCommonActivatableWidget> WidgetClass, UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerName, bool bSuspendInputUntilComplete = true);
virtual void Activate() override;
public:
UPROPERTY(BlueprintAssignable)
FPushContentToLayerForPlayerAsyncDelegate BeforePush;
UPROPERTY(BlueprintAssignable)
FPushContentToLayerForPlayerAsyncDelegate AfterPush;
private:
FGameplayTag LayerName;
bool bSuspendInputUntilComplete = false;
TWeakObjectPtr<APlayerController> OwningPlayerPtr;
TSoftClassPtr<UCommonActivatableWidget> WidgetClass;
TSharedPtr<FStreamableHandle> StreamingHandle;
};

View File

@@ -0,0 +1,60 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Kismet/BlueprintAsyncActionBase.h"
#include "UObject/ObjectPtr.h"
#include "AsyncAction_ShowConfirmation.generated.h"
enum class ECommonMessagingResult : uint8;
class FText;
class UCommonGameDialogDescriptor;
class ULocalPlayer;
struct FFrame;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommonMessagingResultMCDelegate, ECommonMessagingResult, Result);
/**
* Allows easily triggering an async confirmation dialog in blueprints that you can then wait on the result.
*/
UCLASS()
class UAsyncAction_ShowConfirmation : public UBlueprintAsyncActionBase
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintCosmetic, meta = (BlueprintInternalUseOnly = "true", WorldContext = "InWorldContextObject"))
static UAsyncAction_ShowConfirmation* ShowConfirmationYesNo(
UObject* InWorldContextObject, FText Title, FText Message
);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, meta = (BlueprintInternalUseOnly = "true", WorldContext = "InWorldContextObject"))
static UAsyncAction_ShowConfirmation* ShowConfirmationOkCancel(
UObject* InWorldContextObject, FText Title, FText Message
);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, meta = (BlueprintInternalUseOnly = "true", WorldContext = "InWorldContextObject"))
static UAsyncAction_ShowConfirmation* ShowConfirmationCustom(
UObject* InWorldContextObject, UCommonGameDialogDescriptor* Descriptor
);
virtual void Activate() override;
public:
UPROPERTY(BlueprintAssignable)
FCommonMessagingResultMCDelegate OnResult;
private:
void HandleConfirmationResult(ECommonMessagingResult ConfirmationResult);
UPROPERTY(Transient)
TObjectPtr<UObject> WorldContextObject;
UPROPERTY(Transient)
TObjectPtr<ULocalPlayer> TargetLocalPlayer;
UPROPERTY(Transient)
TObjectPtr<UCommonGameDialogDescriptor> Descriptor;
};

View File

@@ -0,0 +1,72 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/GameInstance.h"
#include "CommonGameInstance.generated.h"
enum class ECommonUserAvailability : uint8;
enum class ECommonUserPrivilege : uint8;
class FText;
class UCommonUserInfo;
class UCommonSession_SearchResult;
struct FOnlineResultInformation;
class ULocalPlayer;
class USocialManager;
class UObject;
struct FFrame;
struct FGameplayTag;
UCLASS(Abstract, Config = Game)
class COMMONGAME_API UCommonGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UCommonGameInstance(const FObjectInitializer& ObjectInitializer);
/** Handles errors/warnings from CommonUser, can be overridden per game */
UFUNCTION()
virtual void HandleSystemMessage(FGameplayTag MessageType, FText Title, FText Message);
UFUNCTION()
virtual void HandlePrivilegeChanged(const UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserAvailability OldAvailability, ECommonUserAvailability NewAvailability);
/** Call to reset user and session state, usually because a player has been disconnected */
virtual void ResetUserAndSessionState();
/**
* Requested Session Flow
* Something requests the user to join a specific session (for example, a platform overlay via OnUserRequestedSession).
* This request is handled in SetRequestedSession.
* Check if we can join the requested session immediately (CanJoinRequestedSession). If we can, join the requested session (JoinRequestedSession)
* If not, cache the requested session and instruct the game to get into a state where the session can be joined (ResetGameAndJoinRequestedSession)
*/
/** Handles user accepting a session invite from an external source (for example, a platform overlay). Intended to be overridden per game. */
virtual void OnUserRequestedSession(const FPlatformUserId& PlatformUserId, UCommonSession_SearchResult* InRequestedSession, const FOnlineResultInformation& RequestedSessionResult);
/** Get the requested session */
UCommonSession_SearchResult* GetRequestedSession() const { return RequestedSession; }
/** Set (or clear) the requested session. When this is set, the requested session flow begins. */
virtual void SetRequestedSession(UCommonSession_SearchResult* InRequestedSession);
/** Checks if the requested session can be joined. Can be overridden per game. */
virtual bool CanJoinRequestedSession() const;
/** Join the requested session */
virtual void JoinRequestedSession();
/** Get the game into a state to join the requested session */
virtual void ResetGameAndJoinRequestedSession();
virtual int32 AddLocalPlayer(ULocalPlayer* NewPlayer, FPlatformUserId UserId) override;
virtual bool RemoveLocalPlayer(ULocalPlayer* ExistingPlayer) override;
virtual void Init() override;
virtual void ReturnToMainMenu() override;
private:
/** This is the primary player*/
TWeakObjectPtr<ULocalPlayer> PrimaryPlayer;
/** Session the player has requested to join */
UPROPERTY()
TObjectPtr<UCommonSession_SearchResult> RequestedSession;
};

View File

@@ -0,0 +1,51 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/LocalPlayer.h"
#include "CommonLocalPlayer.generated.h"
class APawn;
class APlayerController;
class APlayerState;
class FViewport;
class UObject;
class UPrimaryGameLayout;
struct FSceneViewProjectionData;
UCLASS(config=Engine, transient)
class COMMONGAME_API UCommonLocalPlayer : public ULocalPlayer
{
GENERATED_BODY()
public:
UCommonLocalPlayer();
/** Called when the local player is assigned a player controller */
DECLARE_MULTICAST_DELEGATE_TwoParams(FPlayerControllerSetDelegate, UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController);
FPlayerControllerSetDelegate OnPlayerControllerSet;
/** Called when the local player is assigned a player state */
DECLARE_MULTICAST_DELEGATE_TwoParams(FPlayerStateSetDelegate, UCommonLocalPlayer* LocalPlayer, APlayerState* PlayerState);
FPlayerStateSetDelegate OnPlayerStateSet;
/** Called when the local player is assigned a player pawn */
DECLARE_MULTICAST_DELEGATE_TwoParams(FPlayerPawnSetDelegate, UCommonLocalPlayer* LocalPlayer, APawn* Pawn);
FPlayerPawnSetDelegate OnPlayerPawnSet;
FDelegateHandle CallAndRegister_OnPlayerControllerSet(FPlayerControllerSetDelegate::FDelegate Delegate);
FDelegateHandle CallAndRegister_OnPlayerStateSet(FPlayerStateSetDelegate::FDelegate Delegate);
FDelegateHandle CallAndRegister_OnPlayerPawnSet(FPlayerPawnSetDelegate::FDelegate Delegate);
public:
virtual bool GetProjectionData(FViewport* Viewport, FSceneViewProjectionData& ProjectionData, int32 StereoViewIndex) const override;
bool IsPlayerViewEnabled() const { return bIsPlayerViewEnabled; }
void SetIsPlayerViewEnabled(bool bInIsPlayerViewEnabled) { bIsPlayerViewEnabled = bInIsPlayerViewEnabled; }
UPrimaryGameLayout* GetRootUILayout() const;
private:
bool bIsPlayerViewEnabled = true;
};

View File

@@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ModularPlayerController.h"
#include "CommonPlayerController.generated.h"
class APawn;
class UObject;
UCLASS(config=Game)
class COMMONGAME_API ACommonPlayerController : public AModularPlayerController
{
GENERATED_BODY()
public:
ACommonPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
virtual void ReceivedPlayer() override;
virtual void SetPawn(APawn* InPawn) override;
virtual void OnPossess(class APawn* APawn) override;
virtual void OnUnPossess() override;
protected:
virtual void OnRep_PlayerState() override;
};

View File

@@ -0,0 +1,228 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonUserWidget.h"
#include "Fonts/SlateFontInfo.h"
#include "CommonPlayerInputKey.generated.h"
enum class ECommonInputType : uint8;
class APlayerController;
class FPaintArgs;
class FSlateRect;
class FSlateWindowElementList;
class FWidgetStyle;
class UCommonLocalPlayer;
class UMaterialInstanceDynamic;
class UObject;
struct FFrame;
struct FGeometry;
UENUM(BlueprintType)
enum class ECommonKeybindForcedHoldStatus : uint8
{
NoForcedHold,
ForcedHold,
NeverShowHold
};
USTRUCT()
struct FMeasuredText
{
GENERATED_BODY()
public:
FText GetText() const { return CachedText; }
void SetText(const FText& InText);
FVector2D GetTextSize() const { return CachedTextSize; }
FVector2D UpdateTextSize(const FSlateFontInfo &InFontInfo, float FontScale = 1.0f) const;
private:
FText CachedText;
mutable FVector2D CachedTextSize;
mutable bool bTextDirty = true;
};
UCLASS(Abstract, BlueprintType, Blueprintable, meta = (DisableNativeTick))
class COMMONGAME_API UCommonPlayerInputKey : public UCommonUserWidget
{
GENERATED_BODY()
public:
UCommonPlayerInputKey(const FObjectInitializer& ObjectInitializer);
/** Update the key and associated display based on our current Boundaction */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void UpdateKeybindWidget();
/** Set the bound key for our keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetBoundKey(FKey NewBoundAction);
/** Set the bound action for our keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetBoundAction(FName NewBoundAction);
/** Force this keybind to be a hold keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget", meta=(DeprecatedFunction, DeprecationMessage = "Use SetForcedHoldKeybindStatus instead"))
void SetForcedHoldKeybind(bool InForcedHoldKeybind);
/** Force this keybind to be a hold keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetForcedHoldKeybindStatus(ECommonKeybindForcedHoldStatus InForcedHoldKeybindStatus);
/** Force this keybind to be a hold keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetShowProgressCountDown(bool bShow);
/** Set the axis scale value for this keybind */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetAxisScale(const float NewValue) { AxisScale = NewValue; }
/** Set the preset name override value for this keybind. */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
void SetPresetNameOverride(const FName NewValue) { PresetNameOverride = NewValue; }
/** Our current BoundAction */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Keybind Widget")
FName BoundAction;
/** Scale to read when using an axis Mapping */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Keybind Widget")
float AxisScale;
/** Key this widget is bound to set directly in blueprint. Used when we want to reference a specific key instead of an action. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Keybind Widget")
FKey BoundKeyFallback;
/** Allows us to set the input type explicitly for the keybind widget. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Keybind Widget")
ECommonInputType InputTypeOverride;
/** Allows us to set the preset name explicitly for the keybind widget. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Keybind Widget")
FName PresetNameOverride;
/** Setting that can show this keybind as a hold or never show it as a hold (even if it is) */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Keybind Widget")
ECommonKeybindForcedHoldStatus ForcedHoldKeybindStatus;
/** Called through a delegate when we start hold progress */
UFUNCTION()
void StartHoldProgress(FName HoldActionName, float HoldDuration);
/** Called through a delegate when we stop hold progress */
UFUNCTION()
void StopHoldProgress(FName HoldActionName, bool bCompletedSuccessfully);
/** Get whether this keybind is a hold action. */
UFUNCTION(BlueprintCallable, Category = "Keybind Widget")
bool IsHoldKeybind() const { return bIsHoldKeybind; }
UFUNCTION()
bool IsBoundKeyValid() const { return BoundKey.IsValid(); }
protected:
virtual void NativePreConstruct() override;
virtual void NativeConstruct() override;
virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
void RecalculateDesiredSize();
/** Overridden to destroy our MID */
virtual void NativeDestruct() override;
/** Whether or not this keybind widget is currently set to be a hold keybind */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Keybind Widget", meta=(ScriptName = "IsHoldKeybindValue"))
bool bIsHoldKeybind;
/** */
UPROPERTY(Transient)
bool bShowKeybindBorder;
UPROPERTY(Transient)
FVector2D FrameSize;
UPROPERTY(BlueprintReadOnly, Category = "Keybind Widget")
bool bShowTimeCountDown;
/** Derived Key this widget is bound to */
UPROPERTY(BlueprintReadOnly, Category = "Keybind Widget")
FKey BoundKey;
/** Material for showing Progress */
UPROPERTY(EditDefaultsOnly, Category = "Keybind Widget")
FSlateBrush HoldProgressBrush;
/** The key bind text border. */
UPROPERTY(EditDefaultsOnly, Category = "Keybind Widget")
FSlateBrush KeyBindTextBorder;
/** Should this keybinding widget display information that it is currently unbound? */
UPROPERTY(EditAnywhere, Category = "Keybind Widget")
bool bShowUnboundStatus = false;
/** The font to apply at each size */
UPROPERTY(EditDefaultsOnly, Category = "Font")
FSlateFontInfo KeyBindTextFont;
/** The font to apply at each size */
UPROPERTY(EditDefaultsOnly, Category = "Font")
FSlateFontInfo CountdownTextFont;
UPROPERTY(Transient)
FMeasuredText CountdownText;
UPROPERTY(Transient)
FMeasuredText KeybindText;
UPROPERTY(Transient)
FMargin KeybindTextPadding;
UPROPERTY(Transient)
FVector2D KeybindFrameMinimumSize;
/** The material parameter name for hold percentage in the HoldKeybindImage */
UPROPERTY(EditDefaultsOnly, Category = "Keybind Widget")
FName PercentageMaterialParameterName;
/** MID for the progress percentage */
UPROPERTY(Transient)
TObjectPtr<UMaterialInstanceDynamic> ProgressPercentageMID;
virtual void NativeOnInitialized() override;
private:
/**
* Synchronizes the hold progress to whatever is currently set in the
* owning player controller.
*/
void SyncHoldProgress();
/** Called for updating the HoldKeybindImage during a hold keybind */
void UpdateHoldProgress();
/** Called when we want to set up this keybind widget as a hold keybind */
void SetupHoldKeybind();
void ShowHoldBackPlate();
void HandlePlayerControllerSet(UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController);
/** Time when we started using a hold keybind */
float HoldKeybindStartTime = 0;
/** How long, in seconds, we will be doing a hold keybind */
float HoldKeybindDuration = 0;
bool bDrawProgress = false;
bool bDrawBrushForKey = false;
bool bDrawCountdownText = false;
bool bWaitingForPlayerController = false;
UPROPERTY(Transient)
FSlateBrush CachedKeyBrush;
};

View File

@@ -0,0 +1,62 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "UObject/SoftObjectPtr.h"
#include "CommonUIExtensions.generated.h"
enum class ECommonInputType : uint8;
template <typename T> class TSubclassOf;
class APlayerController;
class UCommonActivatableWidget;
class ULocalPlayer;
class UObject;
class UUserWidget;
struct FFrame;
struct FGameplayTag;
UCLASS()
class COMMONGAME_API UCommonUIExtensions : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UCommonUIExtensions() { }
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category = "Global UI Extensions", meta = (WorldContext = "WidgetContextObject"))
static ECommonInputType GetOwningPlayerInputType(const UUserWidget* WidgetContextObject);
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category = "Global UI Extensions", meta = (WorldContext = "WidgetContextObject"))
static bool IsOwningPlayerUsingTouch(const UUserWidget* WidgetContextObject);
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category = "Global UI Extensions", meta = (WorldContext = "WidgetContextObject"))
static bool IsOwningPlayerUsingGamepad(const UUserWidget* WidgetContextObject);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static UCommonActivatableWidget* PushContentToLayer_ForPlayer(const ULocalPlayer* LocalPlayer, UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerName, UPARAM(meta = (AllowAbstract = false)) TSubclassOf<UCommonActivatableWidget> WidgetClass);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static void PushStreamedContentToLayer_ForPlayer(const ULocalPlayer* LocalPlayer, UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerName, UPARAM(meta = (AllowAbstract = false)) TSoftClassPtr<UCommonActivatableWidget> WidgetClass);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static void PopContentFromLayer(UCommonActivatableWidget* ActivatableWidget);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static ULocalPlayer* GetLocalPlayerFromController(APlayerController* PlayerController);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static FName SuspendInputForPlayer(APlayerController* PlayerController, FName SuspendReason);
static FName SuspendInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendReason);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Global UI Extensions")
static void ResumeInputForPlayer(APlayerController* PlayerController, FName SuspendToken);
static void ResumeInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendToken);
private:
static int32 InputSuspensions;
};

View File

@@ -0,0 +1,50 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Subsystems/GameInstanceSubsystem.h"
#include "UObject/SoftObjectPtr.h"
#include "GameUIManagerSubsystem.generated.h"
class FSubsystemCollectionBase;
class UCommonLocalPlayer;
class UGameUIPolicy;
class UObject;
/**
* This manager is intended to be replaced by whatever your game needs to
* actually create, so this class is abstract to prevent it from being created.
*
* If you just need the basic functionality you will start by sublcassing this
* subsystem in your own game.
*/
UCLASS(Abstract, config = Game)
class COMMONGAME_API UGameUIManagerSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UGameUIManagerSubsystem() { }
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
const UGameUIPolicy* GetCurrentUIPolicy() const { return CurrentPolicy; }
UGameUIPolicy* GetCurrentUIPolicy() { return CurrentPolicy; }
virtual void NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer);
virtual void NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer);
virtual void NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer);
protected:
void SwitchToPolicy(UGameUIPolicy* InPolicy);
private:
UPROPERTY(Transient)
TObjectPtr<UGameUIPolicy> CurrentPolicy = nullptr;
UPROPERTY(config, EditAnywhere)
TSoftClassPtr<UGameUIPolicy> DefaultUIPolicyClass;
};

View File

@@ -0,0 +1,103 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/World.h"
#include "GameUIPolicy.generated.h"
class UCommonLocalPlayer;
class UGameUIManagerSubsystem;
class ULocalPlayer;
class UPrimaryGameLayout;
/**
*
*/
UENUM()
enum class ELocalMultiplayerInteractionMode : uint8
{
// Fullscreen viewport for the primary player only, regardless of the other player's existence
PrimaryOnly,
// Fullscreen viewport for one player, but players can swap control over who's is displayed and who's is dormant
SingleToggle,
// Viewports displayed simultaneously for both players
Simultaneous
};
USTRUCT()
struct FRootViewportLayoutInfo
{
GENERATED_BODY()
public:
UPROPERTY(Transient)
TObjectPtr<ULocalPlayer> LocalPlayer = nullptr;
UPROPERTY(Transient)
TObjectPtr<UPrimaryGameLayout> RootLayout = nullptr;
UPROPERTY(Transient)
bool bAddedToViewport = false;
FRootViewportLayoutInfo() {}
FRootViewportLayoutInfo(ULocalPlayer* InLocalPlayer, UPrimaryGameLayout* InRootLayout, bool bIsInViewport)
: LocalPlayer(InLocalPlayer)
, RootLayout(InRootLayout)
, bAddedToViewport(bIsInViewport)
{}
bool operator==(const ULocalPlayer* OtherLocalPlayer) const { return LocalPlayer == OtherLocalPlayer; }
};
UCLASS(Abstract, Blueprintable, Within = GameUIManagerSubsystem)
class COMMONGAME_API UGameUIPolicy : public UObject
{
GENERATED_BODY()
public:
template <typename GameUIPolicyClass = UGameUIPolicy>
static GameUIPolicyClass* GetGameUIPolicyAs(const UObject* WorldContextObject)
{
return Cast<GameUIPolicyClass>(GetGameUIPolicy(WorldContextObject));
}
static UGameUIPolicy* GetGameUIPolicy(const UObject* WorldContextObject);
public:
virtual UWorld* GetWorld() const override;
UGameUIManagerSubsystem* GetOwningUIManager() const;
UPrimaryGameLayout* GetRootLayout(const UCommonLocalPlayer* LocalPlayer) const;
ELocalMultiplayerInteractionMode GetLocalMultiplayerInteractionMode() const { return LocalMultiplayerInteractionMode; }
void RequestPrimaryControl(UPrimaryGameLayout* Layout);
protected:
void AddLayoutToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout);
void RemoveLayoutFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout);
virtual void OnRootLayoutAddedToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout);
virtual void OnRootLayoutRemovedFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout);
virtual void OnRootLayoutReleased(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout);
void CreateLayoutWidget(UCommonLocalPlayer* LocalPlayer);
TSubclassOf<UPrimaryGameLayout> GetLayoutWidgetClass(UCommonLocalPlayer* LocalPlayer);
private:
ELocalMultiplayerInteractionMode LocalMultiplayerInteractionMode = ELocalMultiplayerInteractionMode::PrimaryOnly;
UPROPERTY(EditAnywhere)
TSoftClassPtr<UPrimaryGameLayout> LayoutClass;
UPROPERTY(Transient)
TArray<FRootViewportLayoutInfo> RootViewportLayouts;
private:
void NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer);
void NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer);
void NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer);
friend class UGameUIManagerSubsystem;
};

View File

@@ -0,0 +1,68 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonActivatableWidget.h"
#include "CommonMessagingSubsystem.h"
#include "CommonGameDialog.generated.h"
USTRUCT(BlueprintType)
struct FConfirmationDialogAction
{
GENERATED_BODY()
public:
/** Required: The dialog option to provide. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
ECommonMessagingResult Result = ECommonMessagingResult::Unknown;
/** Optional: Display Text to use instead of the action name associated with the result. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText OptionalDisplayText;
bool operator==(const FConfirmationDialogAction& Other) const
{
return Result == Other.Result &&
OptionalDisplayText.EqualTo(Other.OptionalDisplayText);
}
};
UCLASS()
class COMMONGAME_API UCommonGameDialogDescriptor : public UObject
{
GENERATED_BODY()
public:
static UCommonGameDialogDescriptor* CreateConfirmationOk(const FText& Header, const FText& Body);
static UCommonGameDialogDescriptor* CreateConfirmationOkCancel(const FText& Header, const FText& Body);
static UCommonGameDialogDescriptor* CreateConfirmationYesNo(const FText& Header, const FText& Body);
static UCommonGameDialogDescriptor* CreateConfirmationYesNoCancel(const FText& Header, const FText& Body);
public:
/** The header of the message to display */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText Header;
/** The body of the message to display */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText Body;
/** The confirm button's input action to use. */
UPROPERTY(BlueprintReadWrite)
TArray<FConfirmationDialogAction> ButtonActions;
};
UCLASS(Abstract)
class COMMONGAME_API UCommonGameDialog : public UCommonActivatableWidget
{
GENERATED_BODY()
public:
UCommonGameDialog();
virtual void SetupDialog(UCommonGameDialogDescriptor* Descriptor, FCommonMessagingResultDelegate ResultCallback);
virtual void KillDialog();
};

View File

@@ -0,0 +1,50 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Subsystems/LocalPlayerSubsystem.h"
#include "CommonMessagingSubsystem.generated.h"
class FSubsystemCollectionBase;
class UCommonGameDialogDescriptor;
class UObject;
/** Possible results from a dialog */
UENUM(BlueprintType)
enum class ECommonMessagingResult : uint8
{
/** The "yes" button was pressed */
Confirmed,
/** The "no" button was pressed */
Declined,
/** The "ignore/cancel" button was pressed */
Cancelled,
/** The dialog was explicitly killed (no user input) */
Killed,
Unknown UMETA(Hidden)
};
DECLARE_DELEGATE_OneParam(FCommonMessagingResultDelegate, ECommonMessagingResult /* Result */);
/**
*
*/
UCLASS(config = Game)
class COMMONGAME_API UCommonMessagingSubsystem : public ULocalPlayerSubsystem
{
GENERATED_BODY()
public:
UCommonMessagingSubsystem() { }
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
virtual void ShowConfirmation(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback = FCommonMessagingResultDelegate());
virtual void ShowError(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback = FCommonMessagingResultDelegate());
private:
};

View File

@@ -0,0 +1,137 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonActivatableWidget.h"
#include "CommonUIExtensions.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"
#include "GameplayTagContainer.h"
#include "Widgets/CommonActivatableWidgetContainer.h" // IWYU pragma: keep
#include "PrimaryGameLayout.generated.h"
class APlayerController;
class UClass;
class UCommonActivatableWidgetContainerBase;
class ULocalPlayer;
class UObject;
struct FFrame;
/**
* The state of an async load operation for the UI.
*/
enum class EAsyncWidgetLayerState : uint8
{
Canceled,
Initialize,
AfterPush
};
/**
* The primary game UI layout of your game. This widget class represents how to layout, push and display all layers
* of the UI for a single player. Each player in a split-screen game will receive their own primary game layout.
*/
UCLASS(Abstract, meta = (DisableNativeTick))
class COMMONGAME_API UPrimaryGameLayout : public UCommonUserWidget
{
GENERATED_BODY()
public:
static UPrimaryGameLayout* GetPrimaryGameLayoutForPrimaryPlayer(const UObject* WorldContextObject);
static UPrimaryGameLayout* GetPrimaryGameLayout(APlayerController* PlayerController);
static UPrimaryGameLayout* GetPrimaryGameLayout(ULocalPlayer* LocalPlayer);
public:
UPrimaryGameLayout(const FObjectInitializer& ObjectInitializer);
/** A dormant root layout is collapsed and responds only to persistent actions registered by the owning player */
void SetIsDormant(bool Dormant);
bool IsDormant() const { return bIsDormant; }
public:
template <typename ActivatableWidgetT = UCommonActivatableWidget>
TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass)
{
return PushWidgetToLayerStackAsync<ActivatableWidgetT>(LayerName, bSuspendInputUntilComplete, ActivatableWidgetClass, [](EAsyncWidgetLayerState, ActivatableWidgetT*) {});
}
template <typename ActivatableWidgetT = UCommonActivatableWidget>
TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass, TFunction<void(EAsyncWidgetLayerState, ActivatableWidgetT*)> StateFunc)
{
static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
static FName NAME_PushingWidgetToLayer("PushingWidgetToLayer");
const FName SuspendInputToken = bSuspendInputUntilComplete ? UCommonUIExtensions::SuspendInputForPlayer(GetOwningPlayer(), NAME_PushingWidgetToLayer) : NAME_None;
FStreamableManager& StreamableManager = UAssetManager::Get().GetStreamableManager();
TSharedPtr<FStreamableHandle> StreamingHandle = StreamableManager.RequestAsyncLoad(ActivatableWidgetClass.ToSoftObjectPath(), FStreamableDelegate::CreateWeakLambda(this,
[this, LayerName, ActivatableWidgetClass, StateFunc, SuspendInputToken]()
{
UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
ActivatableWidgetT* Widget = PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass.Get(), [StateFunc](ActivatableWidgetT& WidgetToInit) {
StateFunc(EAsyncWidgetLayerState::Initialize, &WidgetToInit);
});
StateFunc(EAsyncWidgetLayerState::AfterPush, Widget);
})
);
// Setup a cancel delegate so that we can resume input if this handler is canceled.
StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
[this, StateFunc, SuspendInputToken]()
{
UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
StateFunc(EAsyncWidgetLayerState::Canceled, nullptr);
})
);
return StreamingHandle;
}
template <typename ActivatableWidgetT = UCommonActivatableWidget>
ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass)
{
return PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass, [](ActivatableWidgetT&) {});
}
template <typename ActivatableWidgetT = UCommonActivatableWidget>
ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass, TFunctionRef<void(ActivatableWidgetT&)> InitInstanceFunc)
{
static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
if (UCommonActivatableWidgetContainerBase* Layer = GetLayerWidget(LayerName))
{
return Layer->AddWidget<ActivatableWidgetT>(ActivatableWidgetClass, InitInstanceFunc);
}
return nullptr;
}
// Find the widget if it exists on any of the layers and remove it from the layer.
void FindAndRemoveWidgetFromLayer(UCommonActivatableWidget* ActivatableWidget);
// Get the layer widget for the given layer tag.
UCommonActivatableWidgetContainerBase* GetLayerWidget(FGameplayTag LayerName);
protected:
/** Register a layer that widgets can be pushed onto. */
UFUNCTION(BlueprintCallable, Category="Layer")
void RegisterLayer(UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget);
virtual void OnIsDormantChanged();
void OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning);
private:
bool bIsDormant = false;
// Lets us keep track of all suspended input tokens so that multiple async UIs can be loading and we correctly suspend
// for the duration of all of them.
TArray<FName> SuspendInputTokens;
// The registered layers for the primary layout.
UPROPERTY(Transient, meta = (Categories = "UI.Layer"))
TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetContainerBase>> Layers;
};