(Feat) Add MainMenu
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "FpsPlayerController.h"
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "ConfirmDialogWidget.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "FpsPlayer.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
@@ -14,9 +15,14 @@
|
||||
#include "InventoryComponent.h"
|
||||
#include "InteractionPromptWidget.h"
|
||||
#include "InventoryWidget.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "PauseMenuWidget.h"
|
||||
#include "ScreenFadeComponent.h"
|
||||
#include "SettingsMenuWidget.h"
|
||||
#include "SurvivalStatsWidget.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "PauseMenu"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Ordre d'empilement des widgets plein ecran, du fond vers l'avant.
|
||||
@@ -35,6 +41,12 @@ namespace
|
||||
constexpr int32 ZOrderInteractionPrompt = 10;
|
||||
constexpr int32 ZOrderInventory = 20;
|
||||
constexpr int32 ZOrderHotbar = 30;
|
||||
|
||||
// Le menu de pause passe devant tout le HUD, et la boite de confirmation
|
||||
// devant lui. Meme echelle que dans le menu principal.
|
||||
constexpr int32 ZOrderPauseMenu = 100;
|
||||
constexpr int32 ZOrderSettingsMenu = 150;
|
||||
constexpr int32 ZOrderQuitToMenuDialog = 200;
|
||||
}
|
||||
|
||||
AFpsPlayerController::AFpsPlayerController()
|
||||
@@ -45,12 +57,29 @@ AFpsPlayerController::AFpsPlayerController()
|
||||
// Rappel Unreal : ce constructeur tourne aussi dans l'editeur, sur le CDO.
|
||||
// Il ne doit contenir que de la construction, jamais de logique de jeu.
|
||||
ScreenFade = CreateDefaultSubobject<UScreenFadeComponent>(TEXT("ScreenFade"));
|
||||
|
||||
QuitToMenuMessage = LOCTEXT("QuitToMenuConfirm", "Retourner au menu principal ?\nTa progression sera perdue.");
|
||||
}
|
||||
|
||||
void AFpsPlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Le mode d'input vit sur le VIEWPORT, pas sur le controller. OpenLevel
|
||||
// detruit le controller du menu principal mais pas la fenetre : son
|
||||
// GameAndUI et son curseur visible restent en place, et la camera ne
|
||||
// repondrait qu'au clic droit maintenu. On ne suppose donc jamais l'etat
|
||||
// laisse par la map precedente, on le repose.
|
||||
if (IsLocalController())
|
||||
{
|
||||
SetInputMode(FInputModeGameOnly());
|
||||
SetShowMouseCursor(false);
|
||||
}
|
||||
|
||||
// Le delegue se declenche a la fin de CHAQUE fondu : c'est
|
||||
// bQuittingToMainMenu qui distingue le fondu de sortie du reste.
|
||||
ScreenFade->OnFadeFinished.AddDynamic(this, &AFpsPlayerController::HandleFadeFinished);
|
||||
|
||||
if (bFadeInOnStart)
|
||||
{
|
||||
ScreenFade->FadeIn(StartupFadeDuration);
|
||||
@@ -157,6 +186,15 @@ void AFpsPlayerController::SetupInputComponent()
|
||||
{
|
||||
Input->BindAction(HotbarScrollAction, ETriggerEvent::Triggered, this, &AFpsPlayerController::HandleHotbarScroll);
|
||||
}
|
||||
|
||||
if (PauseAction)
|
||||
{
|
||||
Input->BindAction(PauseAction, ETriggerEvent::Started, this, &AFpsPlayerController::HandlePauseInput);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayerController : PauseAction n'est pas assignee, le menu de pause sera inaccessible."));
|
||||
}
|
||||
}
|
||||
|
||||
UInventoryComponent* AFpsPlayerController::GetPawnInventory() const
|
||||
@@ -298,6 +336,24 @@ void AFpsPlayerController::CreateHudWidgets()
|
||||
}
|
||||
}
|
||||
|
||||
if (!PauseMenu)
|
||||
{
|
||||
if (PauseMenuClass)
|
||||
{
|
||||
PauseMenu = CreateWidget<UPauseMenuWidget>(this, PauseMenuClass);
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->AddToViewport(ZOrderPauseMenu);
|
||||
// Cree une fois pour toutes et simplement cache, comme
|
||||
// l'inventaire : le menu de pause survit ainsi aux respawns.
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayerController : PauseMenuClass n'est pas assignee, le menu de pause ne s'ouvrira pas."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::ToggleInventory()
|
||||
@@ -356,8 +412,277 @@ void AFpsPlayerController::ToggleInventory()
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::HandlePauseInput()
|
||||
{
|
||||
// La boite a le focus et consomme deja Echap elle-meme. Si elle l'a perdu,
|
||||
// on l'ignore quand meme : repondre ici refermerait le menu en laissant la
|
||||
// question posee.
|
||||
if (bQuitToMenuDialogOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// L'ecran de reglages consomme Echap lui-meme tant qu'il a le focus. Comme
|
||||
// pour la boite, on ne repond ici que s'il l'a perdu.
|
||||
if (bSettingsOpen)
|
||||
{
|
||||
HandleSettingsCloseRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
// Echap sert d'abord a annuler ce qui est ouvert. Fermer l'inventaire est
|
||||
// ce que le joueur attend en premier.
|
||||
if (bInventoryOpen)
|
||||
{
|
||||
ToggleInventory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bPauseMenuOpen)
|
||||
{
|
||||
ResumeGame();
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPauseMenu();
|
||||
}
|
||||
|
||||
void AFpsPlayerController::OpenPauseMenu()
|
||||
{
|
||||
if (!PauseMenu || bPauseMenuOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Mettre en pause pendant l'ecran de mort figerait le timer de respawn : le
|
||||
// joueur resterait devant un ecran noir dont plus rien ne le sortirait.
|
||||
if (GetWorldTimerManager().IsTimerActive(RespawnTimerHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bPauseMenuOpen = true;
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Visible);
|
||||
|
||||
// GameAndUI et non UIOnly, meme raison que l'inventaire : en UIOnly plus
|
||||
// aucune action Enhanced Input ne passe, et Echap ne refermerait rien.
|
||||
FInputModeGameAndUI Mode;
|
||||
Mode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
|
||||
Mode.SetHideCursorDuringCapture(false);
|
||||
SetInputMode(Mode);
|
||||
SetShowMouseCursor(true);
|
||||
|
||||
// Pas de DisableInput sur le pawn ici, contrairement a l'inventaire : le
|
||||
// monde est reellement fige, et les actions du pawn n'ont pas
|
||||
// "Trigger When Paused" -- elles ne se declenchent donc plus d'elles-memes.
|
||||
SetPause(true);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::ResumeGame()
|
||||
{
|
||||
if (!bPauseMenuOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reprendre alors qu'une boite ou les reglages sont encore ouverts les
|
||||
// laisserait affiches par-dessus la partie relancee.
|
||||
if (bQuitToMenuDialogOpen)
|
||||
{
|
||||
HandleQuitToMenuCancelled();
|
||||
}
|
||||
|
||||
if (bSettingsOpen)
|
||||
{
|
||||
HandleSettingsCloseRequested();
|
||||
}
|
||||
|
||||
bPauseMenuOpen = false;
|
||||
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
SetPause(false);
|
||||
|
||||
SetInputMode(FInputModeGameOnly());
|
||||
SetShowMouseCursor(false);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::OpenSettings()
|
||||
{
|
||||
if (bSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SettingsMenuClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayerController : SettingsMenuClass n'est pas assignee, les reglages ne s'ouvriront pas."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Creee a la premiere ouverture seulement : un ecran de reglages qu'on ne
|
||||
// visite peut-etre jamais n'a pas a peser sur chaque demarrage de partie.
|
||||
if (!SettingsMenu)
|
||||
{
|
||||
SettingsMenu = CreateWidget<USettingsMenuWidget>(this, SettingsMenuClass);
|
||||
if (!SettingsMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsMenu->OnCloseRequested.AddDynamic(this, &AFpsPlayerController::HandleSettingsCloseRequested);
|
||||
SettingsMenu->AddToViewport(ZOrderSettingsMenu);
|
||||
}
|
||||
|
||||
bSettingsOpen = true;
|
||||
SettingsMenu->Open();
|
||||
|
||||
// Le menu de pause reste lisible derriere mais cesse de repondre : sans ca
|
||||
// on pourrait cliquer Reprendre a travers l'ecran de reglages.
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::HandleSettingsCloseRequested()
|
||||
{
|
||||
bSettingsOpen = false;
|
||||
|
||||
if (SettingsMenu)
|
||||
{
|
||||
SettingsMenu->Close();
|
||||
}
|
||||
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Visible);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::RequestQuitToMainMenu()
|
||||
{
|
||||
// Le fondu de sortie est deja lance : on ignore le second clic plutot que
|
||||
// d'empiler deux chargements.
|
||||
if (bQuittingToMainMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainMenuLevel.IsNull())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayerController : MainMenuLevel n'est pas assignee, impossible de retourner au menu."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sans boite assignee on ne bloque pas le joueur dans sa partie : on part.
|
||||
if (!QuitToMenuDialogClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayerController : QuitToMenuDialogClass n'est pas assignee, retour au menu sans confirmation."));
|
||||
HandleQuitToMenuConfirmed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Creee au premier clic seulement, puis reutilisee.
|
||||
if (!QuitToMenuDialog)
|
||||
{
|
||||
QuitToMenuDialog = CreateWidget<UConfirmDialogWidget>(this, QuitToMenuDialogClass);
|
||||
if (!QuitToMenuDialog)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QuitToMenuDialog->OnConfirmed.AddDynamic(this, &AFpsPlayerController::HandleQuitToMenuConfirmed);
|
||||
QuitToMenuDialog->OnCancelled.AddDynamic(this, &AFpsPlayerController::HandleQuitToMenuCancelled);
|
||||
QuitToMenuDialog->AddToViewport(ZOrderQuitToMenuDialog);
|
||||
}
|
||||
|
||||
bQuitToMenuDialogOpen = true;
|
||||
QuitToMenuDialog->Open(QuitToMenuMessage);
|
||||
|
||||
// Le menu reste lisible derriere mais cesse de repondre : sans ca, on
|
||||
// pourrait cliquer Reprendre alors que la question est encore posee.
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::HandleQuitToMenuConfirmed()
|
||||
{
|
||||
bQuitToMenuDialogOpen = false;
|
||||
bQuittingToMainMenu = true;
|
||||
|
||||
if (QuitToMenuDialog)
|
||||
{
|
||||
QuitToMenuDialog->Close();
|
||||
}
|
||||
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
// On reste en pause pendant le fondu : relancer le monde une seconde pour
|
||||
// l'abandonner ensuite laisserait le temps a la faim ou a une chute de tuer
|
||||
// un joueur qui a deja quitte. C'est UScreenFadeComponent qui continue de
|
||||
// ticker en pause, pas le monde.
|
||||
ScreenFade->FadeOut(QuitToMenuFadeDuration);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::HandleQuitToMenuCancelled()
|
||||
{
|
||||
bQuitToMenuDialogOpen = false;
|
||||
|
||||
if (QuitToMenuDialog)
|
||||
{
|
||||
QuitToMenuDialog->Close();
|
||||
}
|
||||
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Visible);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::HandleFadeFinished()
|
||||
{
|
||||
if (!bQuittingToMainMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// La pause vit sur les WorldSettings, que OpenLevel detruit avec le monde.
|
||||
// On la leve quand meme : un monde qu'on quitte en etat de pause est un
|
||||
// piege si le chargement echoue.
|
||||
SetPause(false);
|
||||
|
||||
UGameplayStatics::OpenLevelBySoftObjectPtr(this, MainMenuLevel);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (SettingsMenu)
|
||||
{
|
||||
SettingsMenu->RemoveFromParent();
|
||||
SettingsMenu = nullptr;
|
||||
}
|
||||
|
||||
if (QuitToMenuDialog)
|
||||
{
|
||||
QuitToMenuDialog->RemoveFromParent();
|
||||
QuitToMenuDialog = nullptr;
|
||||
}
|
||||
|
||||
if (PauseMenu)
|
||||
{
|
||||
PauseMenu->RemoveFromParent();
|
||||
PauseMenu = nullptr;
|
||||
}
|
||||
|
||||
if (InventoryWidget)
|
||||
{
|
||||
InventoryWidget->RemoveFromParent();
|
||||
@@ -392,3 +717,5 @@ void AFpsPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
Reference in New Issue
Block a user