Segment MVP startup and pause flow
This commit is contained in:
@@ -818,7 +818,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
|
||||
- [x] Reclassify the current investor demo as systems-first, not investor visual MVP, until the visual/menu quality gate below is complete. Added an explicit investor-demo status document, legal notice wording, startup notice wording, packaged README classification, and verification so the current demo is presented as a systems-first investor prototype until 0.1.O visual/menu gates are actually complete.
|
||||
- [x] Replace the native painted MVP frontend with a proper UMG menu flow using real button widgets, hover/pressed states, keyboard/controller focus, mouse click targets, and predictable back/escape behavior. Converted the MVP frontend to a WidgetTree-built UMG flow with UButton/UTextBlock controls, hover/pressed button styling, focusable primary actions, mouse-click character selection, back handling, and save/quit actions while preserving the existing player-controller API.
|
||||
- [ ] Make startup credits, character selection, server/join, loading, pause, save, and quit feel like separate intentional segments, with no impression that gameplay has started underneath the UI.
|
||||
- [x] Make startup credits, character selection, server/join, loading, pause, save, and quit feel like separate intentional segments, with no impression that gameplay has started underneath the UI. Switched startup/menu control to UI-only input, added explicit segment labels for character selection, server join, loading, pause, and saving, and made Save & Quit transition through a dedicated saving screen before issuing save/quit.
|
||||
- [ ] Add a visually verified packaged-client startup test using Sunshine/Moonlight or another real GPU desktop capture path, because QEMU guest-agent screenshots cannot validate the interactive rendered desktop.
|
||||
- [ ] Add first realistic playable character proxies for the selected young adult male and female archetypes, replacing the default mannequin/dummy presentation for investor builds.
|
||||
- [ ] Replace box/sphere/cylinder survival objects with readable MVP meshes for campfires, primitive shelter pieces, resource pickups, water sources, wildlife, and gathered items.
|
||||
|
||||
@@ -16,10 +16,12 @@ EXPECTED = {
|
||||
"SaveAndQuit",
|
||||
],
|
||||
"AgrarianMvpFrontendWidget.cpp": [
|
||||
"NativeOnMouseButtonDown",
|
||||
"IsPointInside(LocalMousePosition, MaleCardPosition, CardSize)",
|
||||
"IsPointInside(LocalMousePosition, FemaleCardPosition, CardSize)",
|
||||
"UButton::StaticClass()",
|
||||
"HandleMaleCharacterClicked",
|
||||
"HandleFemaleCharacterClicked",
|
||||
"OnClicked.AddDynamic",
|
||||
"Save & Quit",
|
||||
"Saving World",
|
||||
"ConsoleCommand(TEXT(\"AgrarianSaveWorld\"))",
|
||||
"ConsoleCommand(TEXT(\"quit\"))",
|
||||
"PlayerController->SetIgnoreMoveInput(false)",
|
||||
@@ -32,6 +34,7 @@ EXPECTED = {
|
||||
"AgrarianGamePlayerController.cpp": [
|
||||
"SetIgnoreMoveInput(true)",
|
||||
"SetIgnoreLookInput(true)",
|
||||
"FInputModeUIOnly",
|
||||
"InputComponent->BindKey(EKeys::Enter",
|
||||
"InputComponent->BindKey(EKeys::SpaceBar",
|
||||
"InputComponent->BindKey(EKeys::Escape",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify the MVP startup/menu flow is segmented and modal."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FRONTEND_H = ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.h"
|
||||
FRONTEND_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.cpp"
|
||||
CONTROLLER_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp"
|
||||
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise SystemExit(f"FAILED: {message}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
header = FRONTEND_H.read_text(encoding="utf-8")
|
||||
frontend = FRONTEND_CPP.read_text(encoding="utf-8")
|
||||
controller = CONTROLLER_CPP.read_text(encoding="utf-8")
|
||||
roadmap = ROADMAP.read_text(encoding="utf-8")
|
||||
|
||||
for token in (
|
||||
"SavingAndQuit",
|
||||
"ExecuteSaveAndQuit",
|
||||
):
|
||||
require(token in header, f"missing segmented flow declaration: {token}")
|
||||
|
||||
for token in (
|
||||
"Character Selection",
|
||||
"Server Join",
|
||||
"Loading Segment",
|
||||
"Pause Menu",
|
||||
"Gameplay is paused while this menu is active.",
|
||||
"Saving World",
|
||||
"Writing the current world state",
|
||||
"SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit)",
|
||||
"GetTimerManager().SetTimer",
|
||||
"ExecuteSaveAndQuit",
|
||||
"ConsoleCommand(TEXT(\"AgrarianSaveWorld\"))",
|
||||
"ConsoleCommand(TEXT(\"quit\"))",
|
||||
):
|
||||
require(token in frontend, f"missing segmented frontend token: {token}")
|
||||
|
||||
require(
|
||||
controller.count("FInputModeUIOnly") >= 3,
|
||||
"startup/frontend/pause paths should use UI-only input while menus are active",
|
||||
)
|
||||
for token in (
|
||||
"SetIgnoreMoveInput(true)",
|
||||
"SetIgnoreLookInput(true)",
|
||||
"SetInputMode(FInputModeGameOnly())",
|
||||
"SetIgnoreMoveInput(false)",
|
||||
"SetIgnoreLookInput(false)",
|
||||
"saving",
|
||||
):
|
||||
require(token in controller + frontend, f"missing modal input or debug token: {token}")
|
||||
|
||||
require(
|
||||
"- [x] Make startup credits, character selection, server/join, loading, pause, save, and quit feel like separate intentional segments" in roadmap,
|
||||
"0.1.O segmented startup/menu roadmap item is not checked off",
|
||||
)
|
||||
|
||||
print("OK: MVP startup, menu, loading, pause, save, and quit flow is segmented and modal.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -39,7 +39,7 @@ REQUIRED = {
|
||||
ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp": [
|
||||
"GetWorldTimerManager().SetTimer",
|
||||
"ShowMvpFrontend",
|
||||
"FInputModeGameAndUI",
|
||||
"FInputModeUIOnly",
|
||||
],
|
||||
ROOT / "Config" / "DefaultGame.ini": [
|
||||
"ProjectVersion=0.1.N-investor.20260518",
|
||||
|
||||
@@ -87,6 +87,7 @@ void AAgrarianGamePlayerController::BeginPlay()
|
||||
{
|
||||
SetIgnoreMoveInput(true);
|
||||
SetIgnoreLookInput(true);
|
||||
SetInputMode(FInputModeUIOnly());
|
||||
bShowMouseCursor = false;
|
||||
GetWorldTimerManager().SetTimer(
|
||||
MvpFrontendStartupTimerHandle,
|
||||
@@ -186,7 +187,7 @@ void AAgrarianGamePlayerController::ShowMvpFrontend()
|
||||
{
|
||||
MvpFrontendWidget->AddToPlayerScreen(10);
|
||||
}
|
||||
SetInputMode(FInputModeGameAndUI().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
|
||||
SetInputMode(FInputModeUIOnly().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
|
||||
bShowMouseCursor = true;
|
||||
SetIgnoreMoveInput(true);
|
||||
SetIgnoreLookInput(true);
|
||||
@@ -223,7 +224,7 @@ void AAgrarianGamePlayerController::ShowMvpPauseMenu()
|
||||
}
|
||||
|
||||
MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu);
|
||||
SetInputMode(FInputModeGameAndUI().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
|
||||
SetInputMode(FInputModeUIOnly().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock));
|
||||
bShowMouseCursor = true;
|
||||
SetIgnoreMoveInput(true);
|
||||
SetIgnoreLookInput(true);
|
||||
@@ -548,7 +549,14 @@ void AAgrarianGamePlayerController::AgrarianShowMvpScreen(FName ScreenName)
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join|loading"));
|
||||
if (ScreenName == TEXT("saving") || ScreenName == TEXT("SavingAndQuit"))
|
||||
{
|
||||
MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit);
|
||||
ClientMessage(TEXT("MVP frontend screen: saving and quit."));
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join|loading|saving"));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -102,6 +103,10 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
|
||||
return FReply::Handled();
|
||||
}
|
||||
}
|
||||
else if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit)
|
||||
{
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
|
||||
}
|
||||
@@ -145,6 +150,19 @@ void UAgrarianMvpFrontendWidget::BackFromActiveScreen()
|
||||
}
|
||||
|
||||
void UAgrarianMvpFrontendWidget::SaveAndQuit()
|
||||
{
|
||||
SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit);
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
FTimerHandle SaveAndQuitTimerHandle;
|
||||
World->GetTimerManager().SetTimer(SaveAndQuitTimerHandle, this, &UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit, 0.35f, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteSaveAndQuit();
|
||||
}
|
||||
|
||||
void UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit()
|
||||
{
|
||||
if (APlayerController* PlayerController = GetOwningPlayer())
|
||||
{
|
||||
@@ -173,9 +191,20 @@ void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen()
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
|
||||
{
|
||||
CompleteFrontendFlow();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +278,9 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree()
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu)
|
||||
{
|
||||
AddText(Panel, FText::FromString(TEXT("Pause Menu")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale);
|
||||
AddText(Panel, MainMenuTitle, FMath::RoundToInt(54.0f * Scale), true, TextColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Game menu")), FMath::RoundToInt(22.0f * Scale), false, MutedTextColor, 72.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Gameplay is paused while this menu is active.")), FMath::RoundToInt(22.0f * Scale), false, MutedTextColor, 72.0f * Scale);
|
||||
PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Resume")), ButtonColor, ButtonHoverColor, 16.0f * Scale);
|
||||
PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked);
|
||||
PrimaryFocusButton->OnHovered.AddDynamic(this, &UAgrarianMvpFrontendWidget::FocusPrimaryButton);
|
||||
@@ -263,6 +293,7 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree()
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection)
|
||||
{
|
||||
AddText(Panel, FText::FromString(TEXT("Character Selection")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Choose your first pioneer")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Select the person who will step into Ground Zero.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 24.0f * Scale);
|
||||
|
||||
@@ -311,6 +342,7 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree()
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer)
|
||||
{
|
||||
AddText(Panel, FText::FromString(TEXT("Server Join")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Join MVP server")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale);
|
||||
AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 36.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Server address")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 4.0f * Scale);
|
||||
@@ -359,6 +391,15 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree()
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit)
|
||||
{
|
||||
AddText(Panel, FText::FromString(TEXT("Saving World")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Writing the current world state")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("The demo will close after the save command is issued.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
AddText(Panel, FText::FromString(TEXT("Loading Segment")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Preparing Ground Zero")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale);
|
||||
AddText(Panel, FText::FromString(TEXT("Loading terrain, weather, survival state, and server session data.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 70.0f * Scale);
|
||||
AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1} | Server: {2}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel(), JoinServerAddress), FMath::RoundToInt(18.0f * Scale), false, TextColor, 34.0f * Scale);
|
||||
|
||||
@@ -16,7 +16,8 @@ enum class EAgrarianMvpFrontendScreen : uint8
|
||||
MainMenu,
|
||||
CharacterSelection,
|
||||
JoinServer,
|
||||
Loading
|
||||
Loading,
|
||||
SavingAndQuit
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
@@ -93,6 +94,9 @@ private:
|
||||
UFUNCTION()
|
||||
void FocusPrimaryButton();
|
||||
|
||||
UFUNCTION()
|
||||
void ExecuteSaveAndQuit();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePrimaryActionClicked();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user