Keep multiplayer options in the start menu only; free M for the world map.

The menu no longer opens in-game and will not reappear after gameplay has started.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pixachux 2026-07-22 19:09:34 +02:00
parent 932157eda5
commit 7cc715cad5
9 changed files with 59 additions and 27 deletions

View File

@ -1,16 +1,9 @@
# Multiplayer, Steam & Server Admin
## In-Game Menu (`M`)
## Start Menu
Opens automatically on start and again with **M**:
- **Map** selection
- **Backend**: Auto / Steam / LAN
- **Host Port** + Max Players
- **Find Steam** / **Find LAN**
- **Join selected session**
- **Connect by IP:Port**
- **Play Solo**
Map / Steam / LAN / IP:Port selection appears **only at game start**.
It is not bound to a key in-game (`M` is reserved for the world map).
## Steam

View File

@ -32,7 +32,6 @@ A life-simulation action RPG remake inspired by Fantasy Life, built in **Unreal
| Interact / Craft | E | X / Square |
| Attack | LMB | RT |
| Toggle camera (Top-Down / FP) | C | Menu |
| Multiplayer menu | M | — |
| Pause | Esc | Start |
### Pause menu shortcuts

View File

@ -21,6 +21,11 @@ void UVocationGameInstance::Init()
OnDataReady.Broadcast();
}
void UVocationGameInstance::MarkGameplayStarted()
{
bHasEnteredGameplay = true;
}
void UVocationGameInstance::InitializeDefaultData()
{
ItemDefinitions.Empty();

View File

@ -76,7 +76,7 @@ void AVocationHUD::DrawMainHUD()
}
else
{
const FString Controls = TEXT("WASD Move | C Camera | M Multiplayer | E Interact | LMB Attack | I Save | Esc Pause");
const FString Controls = TEXT("WASD Move | C Camera | E Interact | LMB Attack | I Save | Esc Pause");
Canvas->DrawColor = FColor(200, 200, 200);
Canvas->DrawText(GEngine->GetSmallFont(), Controls, 40.f * Scale, Canvas->ClipY - 60.f * Scale, Scale, Scale);
}

View File

@ -2,6 +2,7 @@
#include "VocationMainMenuWidget.h"
#include "VocationSessionSubsystem.h"
#include "VocationGameInstance.h"
#include "Components/Button.h"
#include "Components/ComboBoxString.h"
#include "Components/EditableTextBox.h"
@ -72,7 +73,7 @@ void UVocationMainMenuWidget::BuildUI()
}
};
AddToColumn(MakeLabel(WidgetTree, TEXT("Title"), TEXT("VocationLife Multiplayer")));
AddToColumn(MakeLabel(WidgetTree, TEXT("Title"), TEXT("VocationLife — Start Menu")));
AddToColumn(MakeLabel(WidgetTree, TEXT("MapLabel"), TEXT("Map")));
MapCombo = WidgetTree->ConstructWidget<UComboBoxString>(UComboBoxString::StaticClass(), TEXT("MapCombo"));
@ -182,6 +183,22 @@ void UVocationMainMenuWidget::SetStatus(const FString& Message)
}
}
void UVocationMainMenuWidget::LeaveStartMenu()
{
if (UVocationGameInstance* GI = GetGameInstance<UVocationGameInstance>())
{
GI->MarkGameplayStarted();
}
if (APlayerController* PC = GetOwningPlayer())
{
PC->bShowMouseCursor = false;
PC->SetInputMode(FInputModeGameOnly());
}
RemoveFromParent();
}
void UVocationMainMenuWidget::OnHostClicked()
{
UVocationSessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<UVocationSessionSubsystem>() : nullptr;
@ -202,8 +219,8 @@ void UVocationMainMenuWidget::OnHostClicked()
if (BackendIndex == 2) Backend = EVocationNetBackend::LAN;
SetStatus(TEXT("Hosting..."));
LeaveStartMenu();
Sessions->HostSession(MaxPlayers, Name, MapPath, Port, Backend, Backend == EVocationNetBackend::LAN);
RemoveFromParent();
}
void UVocationMainMenuWidget::OnFindSteamClicked()
@ -235,6 +252,7 @@ void UVocationMainMenuWidget::OnJoinSelectedClicked()
return;
}
SetStatus(TEXT("Joining session..."));
LeaveStartMenu();
Sessions->JoinSessionByIndex(Index);
}
}
@ -246,8 +264,8 @@ void UVocationMainMenuWidget::OnConnectIpClicked()
const FString IP = IpBox ? IpBox->GetText().ToString() : TEXT("127.0.0.1");
const int32 Port = JoinPortBox ? FCString::Atoi(*JoinPortBox->GetText().ToString()) : 7777;
SetStatus(FString::Printf(TEXT("Connecting to %s:%d ..."), *IP, Port));
LeaveStartMenu();
Sessions->ConnectByIP(IP, Port);
RemoveFromParent();
}
}
@ -257,8 +275,8 @@ void UVocationMainMenuWidget::OnSoloClicked()
{
const int32 MapIndex = MapCombo ? MapCombo->GetSelectedIndex() : 0;
const FString MapPath = CachedMaps.IsValidIndex(MapIndex) ? CachedMaps[MapIndex].MapPath : TEXT("/Engine/Maps/Entry");
LeaveStartMenu();
Sessions->TravelToMap(MapPath, false, 7777);
RemoveFromParent();
}
}
@ -295,6 +313,6 @@ void UVocationMainMenuWidget::OnSessionJoined(bool bSuccess)
SetStatus(bSuccess ? TEXT("Join/host ok") : TEXT("Join/host failed"));
if (bSuccess)
{
RemoveFromParent();
LeaveStartMenu();
}
}

View File

@ -44,10 +44,10 @@ void AVocationPlayerController::BeginPlay()
GI->SetGraphicsPreset(GI->GetGraphicsPreset(), GI->IsRayTracingEnabled());
}
// Show multiplayer menu on first load (local player only).
// Start menu only once per game session — not reopenable in-game (M reserved for map).
if (IsLocalController() && !IsRunningDedicatedServer())
{
ToggleMultiplayerMenu();
ShowStartMenuIfNeeded();
}
}
@ -75,7 +75,6 @@ void AVocationPlayerController::SetupInputComponent()
InputComponent->BindKey(EKeys::F3, IE_Pressed, this, &AVocationPlayerController::ApplyHighGraphics).bConsumeInput = false;
InputComponent->BindKey(EKeys::F4, IE_Pressed, this, &AVocationPlayerController::ApplyUltraGraphics).bConsumeInput = false;
InputComponent->BindKey(EKeys::F5, IE_Pressed, this, &AVocationPlayerController::ApplyRayTracingGraphics).bConsumeInput = false;
InputComponent->BindKey(EKeys::M, IE_Pressed, this, &AVocationPlayerController::ToggleMultiplayerMenu).bConsumeInput = false;
InputComponent->BindKey(EKeys::H, IE_Pressed, this, &AVocationPlayerController::HostCoopGame).bConsumeInput = false;
InputComponent->BindKey(EKeys::J, IE_Pressed, this, &AVocationPlayerController::JoinFirstFoundSession).bConsumeInput = false;
InputComponent->BindKey(EKeys::S, IE_Pressed, this, &AVocationPlayerController::QuickSave).bConsumeInput = false;
@ -118,13 +117,16 @@ void AVocationPlayerController::ClearInteractionPrompt()
}
}
void AVocationPlayerController::ToggleMultiplayerMenu()
void AVocationPlayerController::ShowStartMenuIfNeeded()
{
UVocationGameInstance* GI = GetGameInstance<UVocationGameInstance>();
if (!GI || GI->HasGameplayStarted())
{
return;
}
if (MultiplayerMenu && MultiplayerMenu->IsInViewport())
{
MultiplayerMenu->RemoveFromParent();
bShowMouseCursor = false;
SetInputMode(FInputModeGameOnly());
return;
}
@ -133,9 +135,8 @@ void AVocationPlayerController::ToggleMultiplayerMenu()
{
MultiplayerMenu->AddToViewport(100);
bShowMouseCursor = true;
FInputModeGameAndUI Mode;
FInputModeUIOnly Mode;
Mode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
Mode.SetHideCursorDuringCapture(false);
SetInputMode(Mode);
}
}
@ -144,6 +145,10 @@ void AVocationPlayerController::HostCoopGame()
{
if (UVocationSessionSubsystem* Sessions = GetGameInstance()->GetSubsystem<UVocationSessionSubsystem>())
{
if (UVocationGameInstance* GI = GetGameInstance<UVocationGameInstance>())
{
GI->MarkGameplayStarted();
}
Sessions->HostSession(4, TEXT("VocationLife"), TEXT("/Engine/Maps/Entry"), 7777, EVocationNetBackend::Auto, false);
}
}

View File

@ -48,6 +48,13 @@ public:
UFUNCTION(BlueprintCallable, Category = "VocationLife|Graphics")
bool IsRayTracingEnabled() const { return bRayTracingEnabled; }
/** Once true, the start/multiplayer menu will not open again this session. */
UFUNCTION(BlueprintCallable, Category = "VocationLife|UI")
void MarkGameplayStarted();
UFUNCTION(BlueprintCallable, Category = "VocationLife|UI")
bool HasGameplayStarted() const { return bHasEnteredGameplay; }
UPROPERTY(BlueprintAssignable, Category = "VocationLife")
FOnVocationDataReady OnDataReady;
@ -68,4 +75,7 @@ protected:
UPROPERTY()
bool bRayTracingEnabled = false;
UPROPERTY()
bool bHasEnteredGameplay = false;
};

View File

@ -55,6 +55,8 @@ protected:
UFUNCTION()
void OnSessionJoined(bool bSuccess);
void LeaveStartMenu();
UPROPERTY()
TObjectPtr<UComboBoxString> MapCombo;

View File

@ -33,7 +33,7 @@ public:
void ClearInteractionPrompt();
UFUNCTION(BlueprintCallable, Category = "VocationLife|UI")
void ToggleMultiplayerMenu();
void ShowStartMenuIfNeeded();
UFUNCTION(BlueprintCallable, Category = "VocationLife|Session")
void HostCoopGame();