Harden MVP menu input and quit flow

This commit is contained in:
2026-05-18 23:20:32 -07:00
parent 15b40c5ac1
commit b9efcdf3dc
5 changed files with 261 additions and 17 deletions
@@ -16,6 +16,7 @@
#include "Engine/LocalPlayer.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "InputCoreTypes.h"
#include "InputMappingContext.h"
#include "Blueprint/UserWidget.h"
#include "TimerManager.h"
@@ -84,6 +85,9 @@ void AAgrarianGamePlayerController::BeginPlay()
{
if (MvpFrontendStartupDelaySeconds > 0.0f)
{
SetIgnoreMoveInput(true);
SetIgnoreLookInput(true);
bShowMouseCursor = false;
GetWorldTimerManager().SetTimer(
MvpFrontendStartupTimerHandle,
this,
@@ -121,6 +125,11 @@ void AAgrarianGamePlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindKey(EKeys::Enter, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpConfirmInput);
InputComponent->BindKey(EKeys::SpaceBar, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpConfirmInput);
InputComponent->BindKey(EKeys::BackSpace, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpBackInput);
InputComponent->BindKey(EKeys::Escape, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpEscapeInput);
// only add IMCs for local player controllers
if (IsLocalPlayerController())
{
@@ -152,7 +161,7 @@ bool AAgrarianGamePlayerController::ShouldUseTouchControls() const
void AAgrarianGamePlayerController::ShowMvpFrontend()
{
if (!IsLocalPlayerController() || MvpFrontendWidget)
if (!IsLocalPlayerController() || (MvpFrontendWidget && MvpFrontendWidget->IsInViewport()))
{
return;
}
@@ -162,16 +171,90 @@ void AAgrarianGamePlayerController::ShowMvpFrontend()
MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass();
}
MvpFrontendWidget = CreateWidget<UAgrarianMvpFrontendWidget>(this, MvpFrontendWidgetClass);
if (!MvpFrontendWidget)
{
MvpFrontendWidget = CreateWidget<UAgrarianMvpFrontendWidget>(this, MvpFrontendWidgetClass);
}
if (!MvpFrontendWidget)
{
return;
}
MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection);
MvpFrontendWidget->AddToPlayerScreen(10);
if (!MvpFrontendWidget->IsInViewport())
{
MvpFrontendWidget->AddToPlayerScreen(10);
}
SetInputMode(FInputModeGameAndUI().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
bShowMouseCursor = true;
SetIgnoreMoveInput(true);
SetIgnoreLookInput(true);
}
void AAgrarianGamePlayerController::ShowMvpPauseMenu()
{
if (!IsLocalPlayerController())
{
return;
}
if (!MvpFrontendWidgetClass)
{
MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass();
}
if (!MvpFrontendWidget || !MvpFrontendWidget->IsInViewport())
{
if (!MvpFrontendWidget)
{
MvpFrontendWidget = CreateWidget<UAgrarianMvpFrontendWidget>(this, MvpFrontendWidgetClass);
}
if (MvpFrontendWidget)
{
MvpFrontendWidget->AddToPlayerScreen(10);
}
}
if (!MvpFrontendWidget)
{
return;
}
MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu);
SetInputMode(FInputModeGameAndUI().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
bShowMouseCursor = true;
SetIgnoreMoveInput(true);
SetIgnoreLookInput(true);
}
void AAgrarianGamePlayerController::HandleMvpConfirmInput()
{
if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport())
{
MvpFrontendWidget->ConfirmActiveScreen();
return;
}
}
void AAgrarianGamePlayerController::HandleMvpBackInput()
{
if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport())
{
MvpFrontendWidget->BackFromActiveScreen();
}
}
void AAgrarianGamePlayerController::HandleMvpEscapeInput()
{
if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport())
{
MvpFrontendWidget->BackFromActiveScreen();
return;
}
ShowMvpPauseMenu();
}
void AAgrarianGamePlayerController::AgrarianGrantItem(FName ItemId, int32 Quantity)
@@ -62,6 +62,10 @@ protected:
/** Returns true if the player should use UMG touch controls */
bool ShouldUseTouchControls() const;
void ShowMvpFrontend();
void ShowMvpPauseMenu();
void HandleMvpConfirmInput();
void HandleMvpBackInput();
void HandleMvpEscapeInput();
public:
UFUNCTION(Exec)
@@ -33,7 +33,7 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
}
@@ -42,13 +42,13 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
if (Key == EKeys::BackSpace || Key == EKeys::Escape)
{
ReturnFromActiveScreen();
BackFromActiveScreen();
return FReply::Handled();
}
}
@@ -57,7 +57,22 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
}
else if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
{
const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape)
{
ConfirmActiveScreen();
return FReply::Handled();
}
if (Key == EKeys::Q)
{
SaveAndQuit();
return FReply::Handled();
}
}
@@ -106,7 +121,7 @@ FReply UAgrarianMvpFrontendWidget::NativeOnMouseButtonDown(const FGeometry& InGe
if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize))
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
}
@@ -121,13 +136,13 @@ FReply UAgrarianMvpFrontendWidget::NativeOnMouseButtonDown(const FGeometry& InGe
if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize))
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
if (BackButtonSize.X > 96.0f && IsPointInside(LocalMousePosition, BackButtonPosition, BackButtonSize))
{
ReturnFromActiveScreen();
BackFromActiveScreen();
return FReply::Handled();
}
}
@@ -140,7 +155,27 @@ FReply UAgrarianMvpFrontendWidget::NativeOnMouseButtonDown(const FGeometry& InGe
if (IsPointInside(LocalMousePosition, ButtonPosition, ButtonSize))
{
ContinueFromActiveScreen();
ConfirmActiveScreen();
return FReply::Handled();
}
}
else if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
{
const float MenuContentX = PanelPosition.X + (48.0f * Scale);
const float MenuContentWidth = PanelSize.X - (96.0f * Scale);
const FVector2D ResumeButtonPosition(MenuContentX, PanelPosition.Y + (220.0f * Scale));
const FVector2D ButtonSize(FMath::Min(MenuContentWidth, 310.0f * Scale), 56.0f * Scale);
const FVector2D QuitButtonPosition(MenuContentX, ResumeButtonPosition.Y + (72.0f * Scale));
if (IsPointInside(LocalMousePosition, ResumeButtonPosition, ButtonSize))
{
ConfirmActiveScreen();
return FReply::Handled();
}
if (IsPointInside(LocalMousePosition, QuitButtonPosition, ButtonSize))
{
SaveAndQuit();
return FReply::Handled();
}
}
@@ -172,6 +207,25 @@ void UAgrarianMvpFrontendWidget::SetHighContrastMode(bool bNewUseHighContrast)
InvalidateLayoutAndVolatility();
}
void UAgrarianMvpFrontendWidget::ConfirmActiveScreen()
{
ContinueFromActiveScreen();
}
void UAgrarianMvpFrontendWidget::BackFromActiveScreen()
{
ReturnFromActiveScreen();
}
void UAgrarianMvpFrontendWidget::SaveAndQuit()
{
if (APlayerController* PlayerController = GetOwningPlayer())
{
PlayerController->ConsoleCommand(TEXT("AgrarianSaveWorld"));
PlayerController->ConsoleCommand(TEXT("quit"));
}
}
int32 UAgrarianMvpFrontendWidget::NativePaint(
const FPaintArgs& Args,
const FGeometry& AllottedGeometry,
@@ -188,7 +242,7 @@ int32 UAgrarianMvpFrontendWidget::NativePaint(
FVector2D PanelPosition = FVector2D::ZeroVector;
FVector2D PanelSize = FVector2D::ZeroVector;
GetPanelLayout(Size, Scale, PanelPosition, PanelSize);
const FLinearColor BackdropColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 0.96f) : FLinearColor(0.015f, 0.018f, 0.014f, 0.92f);
const FLinearColor BackdropColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 1.0f) : FLinearColor(0.015f, 0.018f, 0.014f, 0.99f);
const FLinearColor PanelColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 0.98f) : FLinearColor(0.035f, 0.045f, 0.034f, 0.96f);
const FLinearColor AccentColor = bUseHighContrast ? FLinearColor(0.95f, 0.95f, 0.30f, 1.0f) : FLinearColor(0.45f, 0.72f, 0.40f, 1.0f);
@@ -279,6 +333,12 @@ void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen()
}
if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading)
{
CompleteFrontendFlow();
return;
}
if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
{
CompleteFrontendFlow();
}
@@ -289,6 +349,12 @@ void UAgrarianMvpFrontendWidget::ReturnFromActiveScreen()
if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer)
{
SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection);
return;
}
if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
{
CompleteFrontendFlow();
}
}
@@ -298,6 +364,8 @@ void UAgrarianMvpFrontendWidget::CompleteFrontendFlow()
{
PlayerController->SetInputMode(FInputModeGameOnly());
PlayerController->bShowMouseCursor = false;
PlayerController->SetIgnoreMoveInput(false);
PlayerController->SetIgnoreLookInput(false);
}
RemoveFromParent();
@@ -319,10 +387,10 @@ void UAgrarianMvpFrontendWidget::DrawMainMenu(
const FSlateFontInfo HintFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(16.0f * Scale));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, MainMenuTitle, FVector2D(ContentX, PanelPosition.Y + (58.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, MainMenuSubtitle, FVector2D(ContentX + (4.0f * Scale), PanelPosition.Y + (128.0f * Scale)), ContentWidth, SubtitleFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Game menu")), FVector2D(ContentX + (4.0f * Scale), PanelPosition.Y + (128.0f * Scale)), ContentWidth, SubtitleFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f));
const FVector2D ButtonPosition(ContentX, PanelPosition.Y + (230.0f * Scale));
const FVector2D ButtonSize(FMath::Min(ContentWidth, 280.0f * Scale), 58.0f * Scale);
const FVector2D ButtonPosition(ContentX, PanelPosition.Y + (220.0f * Scale));
const FVector2D ButtonSize(FMath::Min(ContentWidth, 310.0f * Scale), 56.0f * Scale);
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
@@ -331,8 +399,19 @@ void UAgrarianMvpFrontendWidget::DrawMainMenu(
ESlateDrawEffect::None,
FLinearColor(0.35f, 0.58f, 0.30f, 0.95f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, PrimaryActionLabel, ButtonPosition + FVector2D(22.0f * Scale, 12.0f * Scale), ButtonSize.X - (44.0f * Scale), ButtonFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Placeholder flow: splash, startup credits, character selection, join, loading, then Ground Zero.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (66.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Resume")), ButtonPosition + FVector2D(22.0f * Scale, 11.0f * Scale), ButtonSize.X - (44.0f * Scale), ButtonFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f));
const FVector2D QuitButtonPosition(ContentX, ButtonPosition.Y + (72.0f * Scale));
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(ButtonSize), FSlateLayoutTransform(FVector2f(QuitButtonPosition))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.42f, 0.22f, 0.18f, 0.95f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Save & Quit")), QuitButtonPosition + FVector2D(22.0f * Scale, 11.0f * Scale), ButtonSize.X - (44.0f * Scale), ButtonFont, FLinearColor(1.0f, 0.92f, 0.84f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Press Escape to open this menu. Save & Quit writes the current world save before closing.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (66.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f));
}
void UAgrarianMvpFrontendWidget::DrawCharacterSelection(
@@ -64,6 +64,15 @@ public:
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void SetHighContrastMode(bool bNewUseHighContrast);
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void ConfirmActiveScreen();
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void BackFromActiveScreen();
UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI")
void SaveAndQuit();
protected:
virtual void NativeConstruct() override;