(Feat) Add MainMenu
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "ItemDataAsset.h"
|
||||
#include "PickupItem.h"
|
||||
#include "SurvivalStatsComponent.h"
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
// Sets default values
|
||||
AFpsPlayer::AFpsPlayer()
|
||||
@@ -72,6 +73,21 @@ void AFpsPlayer::BeginPlay()
|
||||
|
||||
SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);
|
||||
|
||||
// Les reglages d'abord : le pawn est recree a chaque respawn et repart des
|
||||
// valeurs de son CDO, il doit donc les relire lui-meme. L'abonnement couvre
|
||||
// ensuite les changements faits en cours de partie depuis le menu de pause.
|
||||
ApplyUserSettings();
|
||||
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->OnSurvivalSettingsApplied.AddDynamic(this, &AFpsPlayer::ApplyUserSettings);
|
||||
}
|
||||
|
||||
// Le FOV est interpole chaque frame vers BaseFOV : sans ce calage initial on
|
||||
// verrait la camera s'ouvrir lentement jusqu'a la valeur reglee a chaque
|
||||
// apparition.
|
||||
FirstPersonCamera->SetFieldOfView(BaseFOV);
|
||||
|
||||
// On enregistre le mapping context aupres du sous-systeme Enhanced Input
|
||||
// du joueur local. Sans ca, aucune touche n'est lue.
|
||||
if (const APlayerController* PlayerController = Cast<APlayerController>(GetController()))
|
||||
@@ -91,6 +107,45 @@ void AFpsPlayer::BeginPlay()
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayer::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
// Le singleton de reglages survit au monde : sans ce desabonnement, chaque
|
||||
// cadavre laisserait derriere lui un abonne mort de plus.
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->OnSurvivalSettingsApplied.RemoveDynamic(this, &AFpsPlayer::ApplyUserSettings);
|
||||
}
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AFpsPlayer::ApplyUserSettings()
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings)
|
||||
{
|
||||
// Pas de reglages : on garde les valeurs du Blueprint. Le jeu reste
|
||||
// jouable, il n'est simplement pas configurable.
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : USurvivalUserSettings introuvable, les valeurs du Blueprint sont conservees."));
|
||||
return;
|
||||
}
|
||||
|
||||
LookSensitivityX = Settings->GetLookMultiplierX();
|
||||
LookSensitivityY = Settings->GetLookMultiplierY();
|
||||
bInvertLookY = Settings->GetInvertLookY();
|
||||
BaseFOV = Settings->GetFieldOfView();
|
||||
CameraShakeMultiplier = Settings->GetCameraShakeMultiplier();
|
||||
bSprintToggleMode = Settings->GetSprintToggle();
|
||||
bCrouchToggleMode = Settings->GetCrouchToggle();
|
||||
|
||||
// Repasser en maintien alors que la touche n'est plus enfoncee laisserait le
|
||||
// personnage bloque en sprint : on repart d'un etat propre.
|
||||
if (!bSprintToggleMode && bIsSprinting)
|
||||
{
|
||||
StopSprint();
|
||||
}
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AFpsPlayer::Tick(float DeltaTime)
|
||||
{
|
||||
@@ -193,9 +248,12 @@ void AFpsPlayer::UpdateCameraEffects(float DeltaTime)
|
||||
|
||||
void AFpsPlayer::AddCameraPunch(float Strength)
|
||||
{
|
||||
// Le reglage s'applique ICI et pas sur chaque source d'impact : tout ce qui
|
||||
// secoue la camera passe par cette porte, y compris les explosions et les
|
||||
// reculs d'arme a venir. A 0, la camera ne bouge plus du tout.
|
||||
// Mis en attente plutot qu'applique directement : c'est UpdateCameraEffects
|
||||
// qui l'injecte progressivement dans le ressort.
|
||||
PendingPunch += Strength;
|
||||
PendingPunch += Strength * CameraShakeMultiplier;
|
||||
}
|
||||
|
||||
void AFpsPlayer::Landed(const FHitResult& Hit)
|
||||
@@ -263,14 +321,17 @@ void AFpsPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent
|
||||
|
||||
if (SprintAction)
|
||||
{
|
||||
// Le mode maintien / bascule se decide dans les handlers et non ici :
|
||||
// rebrancher les bindings a chaud demanderait de reconstruire l'input
|
||||
// component a chaque changement de reglage.
|
||||
Input->BindAction(SprintAction, ETriggerEvent::Started, this, &AFpsPlayer::StartSprint);
|
||||
Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::StopSprint);
|
||||
Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::HandleSprintReleased);
|
||||
}
|
||||
|
||||
if (CrouchAction)
|
||||
{
|
||||
Input->BindAction(CrouchAction, ETriggerEvent::Started, this, &AFpsPlayer::StartCrouch);
|
||||
Input->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AFpsPlayer::StopCrouch);
|
||||
Input->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AFpsPlayer::HandleCrouchReleased);
|
||||
}
|
||||
|
||||
if (InteractAction)
|
||||
@@ -330,6 +391,13 @@ void AFpsPlayer::StartSprint()
|
||||
return;
|
||||
}
|
||||
|
||||
// En mode bascule, la meme touche coupe le sprint.
|
||||
if (bSprintToggleMode && bIsSprinting)
|
||||
{
|
||||
StopSprint();
|
||||
return;
|
||||
}
|
||||
|
||||
bIsSprinting = true;
|
||||
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
|
||||
}
|
||||
@@ -340,6 +408,17 @@ void AFpsPlayer::StopSprint()
|
||||
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
|
||||
}
|
||||
|
||||
void AFpsPlayer::HandleSprintReleased()
|
||||
{
|
||||
// En bascule, relacher la touche ne doit rien faire.
|
||||
if (bSprintToggleMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopSprint();
|
||||
}
|
||||
|
||||
void AFpsPlayer::UseSelectedItem()
|
||||
{
|
||||
const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
|
||||
@@ -514,10 +593,27 @@ void AFpsPlayer::ClearTransientInputStates()
|
||||
|
||||
void AFpsPlayer::StartCrouch()
|
||||
{
|
||||
// En mode bascule, la meme touche releve le personnage.
|
||||
if (bCrouchToggleMode && bIsCrouched)
|
||||
{
|
||||
StopCrouch();
|
||||
return;
|
||||
}
|
||||
|
||||
StopSprint();
|
||||
Crouch();
|
||||
}
|
||||
|
||||
void AFpsPlayer::HandleCrouchReleased()
|
||||
{
|
||||
if (bCrouchToggleMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopCrouch();
|
||||
}
|
||||
|
||||
void AFpsPlayer::StopCrouch()
|
||||
{
|
||||
// UnCrouch() echoue silencieusement s'il y a un plafond : le perso
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "MainMenuWidget.h"
|
||||
#include "MenuCameraSpot.h"
|
||||
#include "ScreenFadeComponent.h"
|
||||
#include "SettingsMenuWidget.h"
|
||||
#include "SurvivalGameInstance.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MainMenu"
|
||||
@@ -19,7 +20,9 @@ namespace
|
||||
// Le menu reste sous le voile de fondu, qui est en 1000.
|
||||
constexpr int32 ZOrderMainMenu = 0;
|
||||
|
||||
// La boite de confirmation passe devant le menu, mais sous le fondu.
|
||||
// L'ecran de reglages recouvre le menu, la boite de confirmation passe
|
||||
// devant les deux, et le fondu reste au-dessus de tout a 1000.
|
||||
constexpr int32 ZOrderSettingsMenu = 50;
|
||||
constexpr int32 ZOrderQuitDialog = 100;
|
||||
}
|
||||
|
||||
@@ -201,6 +204,56 @@ void AMainMenuPlayerController::QuitGame()
|
||||
}
|
||||
}
|
||||
|
||||
void AMainMenuPlayerController::OpenSettings()
|
||||
{
|
||||
// Une partie se charge deja : ouvrir un ecran maintenant serait un piege.
|
||||
if (PendingAction != EMenuPendingAction::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SettingsMenuClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AMainMenuPlayerController : SettingsMenuClass n'est pas assignee, les reglages ne s'ouvriront pas."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Creee a la premiere ouverture seulement, puis reutilisee.
|
||||
if (!SettingsMenu)
|
||||
{
|
||||
SettingsMenu = CreateWidget<USettingsMenuWidget>(this, SettingsMenuClass);
|
||||
if (!SettingsMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsMenu->OnCloseRequested.AddDynamic(this, &AMainMenuPlayerController::HandleSettingsCloseRequested);
|
||||
SettingsMenu->AddToViewport(ZOrderSettingsMenu);
|
||||
}
|
||||
|
||||
SettingsMenu->Open();
|
||||
|
||||
// Le menu reste lisible derriere mais cesse de repondre : sans ca on
|
||||
// pourrait lancer une partie a travers l'ecran de reglages.
|
||||
if (MainMenu)
|
||||
{
|
||||
MainMenu->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
}
|
||||
|
||||
void AMainMenuPlayerController::HandleSettingsCloseRequested()
|
||||
{
|
||||
if (SettingsMenu)
|
||||
{
|
||||
SettingsMenu->Close();
|
||||
}
|
||||
|
||||
if (MainMenu)
|
||||
{
|
||||
MainMenu->SetVisibility(ESlateVisibility::Visible);
|
||||
}
|
||||
}
|
||||
|
||||
void AMainMenuPlayerController::HandleQuitConfirmed()
|
||||
{
|
||||
// Pas de fondu : le joueur vient de confirmer, le faire patienter une
|
||||
@@ -238,6 +291,12 @@ void AMainMenuPlayerController::HandleFadeFinished()
|
||||
|
||||
void AMainMenuPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (SettingsMenu)
|
||||
{
|
||||
SettingsMenu->RemoveFromParent();
|
||||
SettingsMenu = nullptr;
|
||||
}
|
||||
|
||||
if (QuitDialog)
|
||||
{
|
||||
QuitDialog->RemoveFromParent();
|
||||
|
||||
@@ -20,6 +20,11 @@ void UMainMenuWidget::NativeConstruct()
|
||||
QuitButton->OnClicked.AddDynamic(this, &UMainMenuWidget::HandleQuitClicked);
|
||||
}
|
||||
|
||||
if (SettingsButton)
|
||||
{
|
||||
SettingsButton->OnClicked.AddDynamic(this, &UMainMenuWidget::HandleSettingsClicked);
|
||||
}
|
||||
|
||||
// Les boutons de coop sont visibles mais inertes : ils annoncent la couleur
|
||||
// sans promettre une partie qui echouerait.
|
||||
if (HostButton)
|
||||
@@ -48,3 +53,11 @@ void UMainMenuWidget::HandleQuitClicked()
|
||||
Controller->QuitGame();
|
||||
}
|
||||
}
|
||||
|
||||
void UMainMenuWidget::HandleSettingsClicked()
|
||||
{
|
||||
if (AMainMenuPlayerController* Controller = GetOwningPlayer<AMainMenuPlayerController>())
|
||||
{
|
||||
Controller->OpenSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "PauseMenuWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "FpsPlayerController.h"
|
||||
|
||||
void UPauseMenuWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (ResumeButton)
|
||||
{
|
||||
ResumeButton->OnClicked.AddDynamic(this, &UPauseMenuWidget::HandleResumeClicked);
|
||||
}
|
||||
|
||||
if (QuitToMenuButton)
|
||||
{
|
||||
QuitToMenuButton->OnClicked.AddDynamic(this, &UPauseMenuWidget::HandleQuitToMenuClicked);
|
||||
}
|
||||
|
||||
if (SettingsButton)
|
||||
{
|
||||
SettingsButton->OnClicked.AddDynamic(this, &UPauseMenuWidget::HandleSettingsClicked);
|
||||
}
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::NativeDestruct()
|
||||
{
|
||||
if (ResumeButton)
|
||||
{
|
||||
ResumeButton->OnClicked.RemoveDynamic(this, &UPauseMenuWidget::HandleResumeClicked);
|
||||
}
|
||||
|
||||
if (SettingsButton)
|
||||
{
|
||||
SettingsButton->OnClicked.RemoveDynamic(this, &UPauseMenuWidget::HandleSettingsClicked);
|
||||
}
|
||||
|
||||
if (QuitToMenuButton)
|
||||
{
|
||||
QuitToMenuButton->OnClicked.RemoveDynamic(this, &UPauseMenuWidget::HandleQuitToMenuClicked);
|
||||
}
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::HandleSettingsClicked()
|
||||
{
|
||||
if (AFpsPlayerController* Controller = GetOwningPlayer<AFpsPlayerController>())
|
||||
{
|
||||
Controller->OpenSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::HandleResumeClicked()
|
||||
{
|
||||
if (AFpsPlayerController* Controller = GetOwningPlayer<AFpsPlayerController>())
|
||||
{
|
||||
Controller->ResumeGame();
|
||||
}
|
||||
}
|
||||
|
||||
void UPauseMenuWidget::HandleQuitToMenuClicked()
|
||||
{
|
||||
if (AFpsPlayerController* Controller = GetOwningPlayer<AFpsPlayerController>())
|
||||
{
|
||||
Controller->RequestQuitToMainMenu();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ UScreenFadeComponent::UScreenFadeComponent()
|
||||
// pour une valeur qui ne bouge qu'une seconde par mort serait du gaspillage.
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
|
||||
// Le fondu doit continuer d'avancer quand le monde est fige : c'est le seul
|
||||
// moyen de partir au noir depuis le menu de pause. Sans ce drapeau, l'ecran
|
||||
// resterait bloque a mi-chemin et le delegue ne se declencherait jamais.
|
||||
PrimaryComponentTick.bTickEvenWhenPaused = true;
|
||||
}
|
||||
|
||||
void UScreenFadeComponent::BeginPlay()
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SettingRowWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "Components/ProgressBar.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void USettingRowWidget::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
|
||||
// NativeOnInitialized et non NativeConstruct : l'onglet parent appelle
|
||||
// SetupAs... depuis SON NativeConstruct, et rien ne garantit l'ordre entre
|
||||
// le construct du parent et celui de ses enfants. Initialize, lui, tourne
|
||||
// une seule fois a la creation de l'arbre, avant tout le monde.
|
||||
if (LeftArrowButton)
|
||||
{
|
||||
LeftArrowButton->OnClicked.AddDynamic(this, &USettingRowWidget::HandleLeftClicked);
|
||||
}
|
||||
|
||||
if (RightArrowButton)
|
||||
{
|
||||
RightArrowButton->OnClicked.AddDynamic(this, &USettingRowWidget::HandleRightClicked);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingRowWidget::SetupAsChoice(const FText& Label, const TArray<FText>& Options, int32 Index)
|
||||
{
|
||||
bNumeric = false;
|
||||
ChoiceLabels = Options;
|
||||
StepCount = FMath::Max(Options.Num(), 1);
|
||||
CurrentIndex = FMath::Clamp(Index, 0, StepCount - 1);
|
||||
|
||||
if (LabelText)
|
||||
{
|
||||
LabelText->SetText(Label);
|
||||
}
|
||||
|
||||
// Une barre de remplissage sur "Maintien / Bascule" ne veut rien dire : elle
|
||||
// serait vide ou pleine sans que ca informe de quoi que ce soit. On la
|
||||
// masque plutot que d'imposer un second WBP juste pour les choix.
|
||||
if (ValueBar)
|
||||
{
|
||||
ValueBar->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void USettingRowWidget::SetupAsNumber(const FText& Label, float Min, float Max, float Step, float Value, const FText& Suffix)
|
||||
{
|
||||
bNumeric = true;
|
||||
ChoiceLabels.Reset();
|
||||
NumericMin = Min;
|
||||
NumericStep = FMath::Max(Step, KINDA_SMALL_NUMBER);
|
||||
NumericSuffix = Suffix;
|
||||
|
||||
// +1 : les deux extremites comptent. 70 a 120 par 5 fait 11 paliers, pas 10.
|
||||
StepCount = FMath::Max(FMath::FloorToInt((Max - Min) / NumericStep) + 1, 1);
|
||||
|
||||
if (LabelText)
|
||||
{
|
||||
LabelText->SetText(Label);
|
||||
}
|
||||
|
||||
// HitTestInvisible et non Visible : la barre est un decor, elle ne doit pas
|
||||
// intercepter les clics destines aux fleches qui l'encadrent.
|
||||
if (ValueBar)
|
||||
{
|
||||
ValueBar->SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
|
||||
SetNumericValueSilently(Value);
|
||||
}
|
||||
|
||||
float USettingRowWidget::GetNumericValue() const
|
||||
{
|
||||
return NumericMin + CurrentIndex * NumericStep;
|
||||
}
|
||||
|
||||
void USettingRowWidget::SetIndexSilently(int32 NewIndex)
|
||||
{
|
||||
CurrentIndex = FMath::Clamp(NewIndex, 0, StepCount - 1);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void USettingRowWidget::SetNumericValueSilently(float Value)
|
||||
{
|
||||
SetIndexSilently(FMath::RoundToInt((Value - NumericMin) / NumericStep));
|
||||
}
|
||||
|
||||
void USettingRowWidget::HandleLeftClicked()
|
||||
{
|
||||
StepIndex(-1);
|
||||
}
|
||||
|
||||
void USettingRowWidget::HandleRightClicked()
|
||||
{
|
||||
StepIndex(1);
|
||||
}
|
||||
|
||||
void USettingRowWidget::StepIndex(int32 Delta)
|
||||
{
|
||||
const int32 NewIndex = FMath::Clamp(CurrentIndex + Delta, 0, StepCount - 1);
|
||||
|
||||
// Pas de bouclage, volontairement : passer de 100 a 5 de sensibilite d'un
|
||||
// clic de trop rendrait le jeu injouable sans qu'on comprenne pourquoi.
|
||||
if (NewIndex == CurrentIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentIndex = NewIndex;
|
||||
Refresh();
|
||||
|
||||
OnIndexChanged.Broadcast(CurrentIndex);
|
||||
}
|
||||
|
||||
void USettingRowWidget::Refresh()
|
||||
{
|
||||
if (ValueText)
|
||||
{
|
||||
if (bNumeric)
|
||||
{
|
||||
FNumberFormattingOptions Format;
|
||||
Format.MaximumFractionalDigits = 0;
|
||||
|
||||
const FText Number = FText::AsNumber(GetNumericValue(), &Format);
|
||||
|
||||
// FormatOrdered plutot qu'une concatenation : selon la langue, l'unite
|
||||
// ne se colle pas forcement du meme cote ni avec la meme espace.
|
||||
ValueText->SetText(NumericSuffix.IsEmpty()
|
||||
? Number
|
||||
: FText::FormatOrdered(NSLOCTEXT("Reglages", "ValeurAvecUnite", "{0}{1}"), Number, NumericSuffix));
|
||||
}
|
||||
else if (ChoiceLabels.IsValidIndex(CurrentIndex))
|
||||
{
|
||||
ValueText->SetText(ChoiceLabels[CurrentIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ValueBar)
|
||||
{
|
||||
// Un seul palier : la barre n'a aucun sens, on la remplit entierement
|
||||
// plutot que de diviser par zero.
|
||||
ValueBar->SetPercent(StepCount > 1
|
||||
? static_cast<float>(CurrentIndex) / static_cast<float>(StepCount - 1)
|
||||
: 1.f);
|
||||
}
|
||||
|
||||
// Les fleches se grisent aux extremites : le joueur voit qu'il est au bout
|
||||
// au lieu de cliquer dans le vide.
|
||||
if (LeftArrowButton)
|
||||
{
|
||||
LeftArrowButton->SetIsEnabled(CurrentIndex > 0);
|
||||
}
|
||||
|
||||
if (RightArrowButton)
|
||||
{
|
||||
RightArrowButton->SetIsEnabled(CurrentIndex < StepCount - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SettingsGameplayWidget.h"
|
||||
|
||||
#include "SettingRowWidget.h"
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Reglages"
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Les deux seules listes de choix de cet onglet. */
|
||||
TArray<FText> MakeNoYesLabels()
|
||||
{
|
||||
return { LOCTEXT("Non", "Non"), LOCTEXT("Oui", "Oui") };
|
||||
}
|
||||
|
||||
TArray<FText> MakeHoldToggleLabels()
|
||||
{
|
||||
return { LOCTEXT("Maintien", "Maintien"), LOCTEXT("Bascule", "Bascule") };
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
// Les libelles et les bornes sont poses une seule fois ; seules les valeurs
|
||||
// bougent ensuite, via RefreshFromSettings.
|
||||
using namespace SurvivalSettingsRange;
|
||||
|
||||
if (SensitivityRow)
|
||||
{
|
||||
SensitivityRow->SetupAsNumber(LOCTEXT("Sensibilite", "Sensibilité"),
|
||||
SensitivityMin, SensitivityMax, SensitivityStep, SensitivityMin, FText::GetEmpty());
|
||||
SensitivityRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleSensitivityChanged);
|
||||
}
|
||||
|
||||
if (SeparateVerticalRow)
|
||||
{
|
||||
SeparateVerticalRow->SetupAsChoice(LOCTEXT("SensibiliteSeparee", "Sensibilité verticale séparée"),
|
||||
MakeNoYesLabels(), 0);
|
||||
SeparateVerticalRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleSeparateVerticalChanged);
|
||||
}
|
||||
|
||||
if (SensitivityVerticalRow)
|
||||
{
|
||||
SensitivityVerticalRow->SetupAsNumber(LOCTEXT("SensibiliteVerticale", "Sensibilité verticale"),
|
||||
SensitivityMin, SensitivityMax, SensitivityStep, SensitivityMin, FText::GetEmpty());
|
||||
SensitivityVerticalRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleSensitivityVerticalChanged);
|
||||
}
|
||||
|
||||
if (InvertLookRow)
|
||||
{
|
||||
InvertLookRow->SetupAsChoice(LOCTEXT("InverserY", "Inverser l'axe vertical"), MakeNoYesLabels(), 0);
|
||||
InvertLookRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleInvertLookChanged);
|
||||
}
|
||||
|
||||
if (FieldOfViewRow)
|
||||
{
|
||||
FieldOfViewRow->SetupAsNumber(LOCTEXT("ChampDeVision", "Champ de vision"),
|
||||
FieldOfViewMin, FieldOfViewMax, FieldOfViewStep, FieldOfViewMin, LOCTEXT("Degres", "°"));
|
||||
FieldOfViewRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleFieldOfViewChanged);
|
||||
}
|
||||
|
||||
if (CameraShakeRow)
|
||||
{
|
||||
CameraShakeRow->SetupAsNumber(LOCTEXT("BougeCamera", "Bougé de caméra"),
|
||||
CameraShakeMin, CameraShakeMax, CameraShakeStep, CameraShakeMin, LOCTEXT("Pourcent", " %"));
|
||||
CameraShakeRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleCameraShakeChanged);
|
||||
}
|
||||
|
||||
if (SprintModeRow)
|
||||
{
|
||||
SprintModeRow->SetupAsChoice(LOCTEXT("Sprint", "Sprint"), MakeHoldToggleLabels(), 0);
|
||||
SprintModeRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleSprintModeChanged);
|
||||
}
|
||||
|
||||
if (CrouchModeRow)
|
||||
{
|
||||
CrouchModeRow->SetupAsChoice(LOCTEXT("Accroupi", "Accroupi"), MakeHoldToggleLabels(), 0);
|
||||
CrouchModeRow->OnIndexChanged.AddDynamic(this, &USettingsGameplayWidget::HandleCrouchModeChanged);
|
||||
}
|
||||
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::RefreshFromSettings()
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USettingsGameplayWidget : GameUserSettingsClassName n'est pas configure dans DefaultEngine.ini, l'onglet restera inerte."));
|
||||
return;
|
||||
}
|
||||
|
||||
// SetNumericValueSilently et non un setter classique : diffuser ici ferait
|
||||
// boucler l'onglet sur lui-meme.
|
||||
if (SensitivityRow)
|
||||
{
|
||||
SensitivityRow->SetNumericValueSilently(Settings->GetLookSensitivity());
|
||||
}
|
||||
|
||||
const bool bSeparate = Settings->GetSeparateVerticalSensitivity();
|
||||
|
||||
if (SeparateVerticalRow)
|
||||
{
|
||||
SeparateVerticalRow->SetIndexSilently(bSeparate ? 1 : 0);
|
||||
}
|
||||
|
||||
if (SensitivityVerticalRow)
|
||||
{
|
||||
SensitivityVerticalRow->SetNumericValueSilently(Settings->GetLookSensitivityVertical());
|
||||
}
|
||||
|
||||
UpdateVerticalRowVisibility(bSeparate);
|
||||
|
||||
if (InvertLookRow)
|
||||
{
|
||||
InvertLookRow->SetIndexSilently(Settings->GetInvertLookY() ? 1 : 0);
|
||||
}
|
||||
|
||||
if (FieldOfViewRow)
|
||||
{
|
||||
FieldOfViewRow->SetNumericValueSilently(Settings->GetFieldOfView());
|
||||
}
|
||||
|
||||
if (CameraShakeRow)
|
||||
{
|
||||
CameraShakeRow->SetNumericValueSilently(Settings->GetCameraShakeScale());
|
||||
}
|
||||
|
||||
if (SprintModeRow)
|
||||
{
|
||||
SprintModeRow->SetIndexSilently(Settings->GetSprintToggle() ? 1 : 0);
|
||||
}
|
||||
|
||||
if (CrouchModeRow)
|
||||
{
|
||||
CrouchModeRow->SetIndexSilently(Settings->GetCrouchToggle() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::ResetToDefaults()
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Settings->SetSurvivalGameplayToDefaults();
|
||||
PushToPawn(Settings);
|
||||
RefreshFromSettings();
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::UpdateVerticalRowVisibility(bool bSeparate)
|
||||
{
|
||||
if (SensitivityVerticalRow)
|
||||
{
|
||||
// Collapsed et non Hidden : la ligne ne doit pas laisser un trou dans la
|
||||
// liste quand elle est inutile.
|
||||
SensitivityVerticalRow->SetVisibility(bSeparate ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::PushToPawn(USurvivalUserSettings* Settings)
|
||||
{
|
||||
// Pas de SaveSettings ici : ecrire le .ini a chaque clic de fleche est du
|
||||
// gaspillage. C'est l'ecran de reglages qui sauvegarde en se fermant.
|
||||
Settings->ApplySurvivalSettings();
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleSensitivityChanged(int32 NewIndex)
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings || !SensitivityRow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Settings->SetLookSensitivity(SensitivityRow->GetNumericValue());
|
||||
|
||||
// Sans axe separe, le vertical suit : on garde les deux lignes coherentes
|
||||
// pour que le joueur ne decouvre pas un ecart en activant la separation.
|
||||
if (!Settings->GetSeparateVerticalSensitivity() && SensitivityVerticalRow)
|
||||
{
|
||||
SensitivityVerticalRow->SetNumericValueSilently(Settings->GetLookSensitivityVertical());
|
||||
}
|
||||
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleSeparateVerticalChanged(int32 NewIndex)
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bSeparate = NewIndex == 1;
|
||||
Settings->SetSeparateVerticalSensitivity(bSeparate);
|
||||
|
||||
if (SensitivityVerticalRow)
|
||||
{
|
||||
SensitivityVerticalRow->SetNumericValueSilently(Settings->GetLookSensitivityVertical());
|
||||
}
|
||||
|
||||
UpdateVerticalRowVisibility(bSeparate);
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleSensitivityVerticalChanged(int32 NewIndex)
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings || !SensitivityVerticalRow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Settings->SetLookSensitivityVertical(SensitivityVerticalRow->GetNumericValue());
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleInvertLookChanged(int32 NewIndex)
|
||||
{
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->SetInvertLookY(NewIndex == 1);
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleFieldOfViewChanged(int32 NewIndex)
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings || !FieldOfViewRow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Settings->SetFieldOfView(FieldOfViewRow->GetNumericValue());
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleCameraShakeChanged(int32 NewIndex)
|
||||
{
|
||||
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
|
||||
if (!Settings || !CameraShakeRow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Settings->SetCameraShakeScale(CameraShakeRow->GetNumericValue());
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleSprintModeChanged(int32 NewIndex)
|
||||
{
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->SetSprintToggle(NewIndex == 1);
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsGameplayWidget::HandleCrouchModeChanged(int32 NewIndex)
|
||||
{
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->SetCrouchToggle(NewIndex == 1);
|
||||
PushToPawn(Settings);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,268 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SettingsMenuWidget.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "Components/WidgetSwitcher.h"
|
||||
#include "ConfirmDialogWidget.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "SettingsTabWidget.h"
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Reglages"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Ordre des pages dans le WidgetSwitcher. Les trois dernieres n'existent pas
|
||||
// encore : leur bouton se grise tout seul tant que la page est absente.
|
||||
constexpr int32 TabIndexGameplay = 0;
|
||||
constexpr int32 TabIndexDisplay = 1;
|
||||
constexpr int32 TabIndexGraphics = 2;
|
||||
constexpr int32 TabIndexAudio = 3;
|
||||
}
|
||||
|
||||
USettingsMenuWidget::USettingsMenuWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Sans ce drapeau le widget ne peut pas prendre le focus clavier, et
|
||||
// NativeOnKeyDown ne serait jamais appele. Il ne se change qu'a la
|
||||
// construction, d'ou sa presence ici et pas dans Open().
|
||||
SetIsFocusable(true);
|
||||
|
||||
ResetDialogMessage = LOCTEXT("ResetConfirm", "Réinitialiser cet onglet ?");
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (BackButton)
|
||||
{
|
||||
BackButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleBackClicked);
|
||||
}
|
||||
|
||||
if (ResetButton)
|
||||
{
|
||||
ResetButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleResetClicked);
|
||||
}
|
||||
|
||||
if (GameplayTabButton)
|
||||
{
|
||||
GameplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGameplayTabClicked);
|
||||
}
|
||||
|
||||
if (DisplayTabButton)
|
||||
{
|
||||
DisplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleDisplayTabClicked);
|
||||
}
|
||||
|
||||
if (GraphicsTabButton)
|
||||
{
|
||||
GraphicsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGraphicsTabClicked);
|
||||
}
|
||||
|
||||
if (AudioTabButton)
|
||||
{
|
||||
AudioTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleAudioTabClicked);
|
||||
}
|
||||
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->OnConfirmed.AddDynamic(this, &USettingsMenuWidget::HandleResetConfirmed);
|
||||
ResetDialog->OnCancelled.AddDynamic(this, &USettingsMenuWidget::HandleResetCancelled);
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
UpdateTabButtonStates();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::NativeDestruct()
|
||||
{
|
||||
if (BackButton)
|
||||
{
|
||||
BackButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleBackClicked);
|
||||
}
|
||||
|
||||
if (ResetButton)
|
||||
{
|
||||
ResetButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleResetClicked);
|
||||
}
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::Open()
|
||||
{
|
||||
SetVisibility(ESlateVisibility::Visible);
|
||||
|
||||
// L'ecran a pu etre ouvert, referme, puis rouvert apres un changement fait
|
||||
// ailleurs : on relit toujours plutot que de faire confiance a l'affichage.
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->RefreshFromSettings();
|
||||
}
|
||||
|
||||
// Le focus doit etre repris a CHAQUE ouverture, sinon Echap reste sans effet.
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::Close()
|
||||
{
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
bResetDialogOpen = false;
|
||||
|
||||
SetVisibility(ESlateVisibility::Collapsed);
|
||||
|
||||
// La sauvegarde a lieu ici et pas a chaque clic de fleche : ecrire le .ini
|
||||
// vingt fois pendant qu'on cherche la bonne sensibilite ne sert a rien.
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
FReply USettingsMenuWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
|
||||
{
|
||||
const FKey Key = InKeyEvent.GetKey();
|
||||
|
||||
if (Key == EKeys::Escape)
|
||||
{
|
||||
// La boite de confirmation gere Echap elle-meme quand elle a le focus.
|
||||
// Si elle l'a perdu, on annule ici plutot que de fermer tout l'ecran en
|
||||
// laissant la question affichee.
|
||||
if (bResetDialogOpen)
|
||||
{
|
||||
HandleResetCancelled();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
OnCloseRequested.Broadcast();
|
||||
|
||||
// Handled : sans ca la touche continue de remonter et le menu de pause
|
||||
// la consommerait une seconde fois.
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
|
||||
}
|
||||
|
||||
USettingsTabWidget* USettingsMenuWidget::GetActiveTab() const
|
||||
{
|
||||
return TabSwitcher ? Cast<USettingsTabWidget>(TabSwitcher->GetActiveWidget()) : nullptr;
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::ShowTab(int32 Index)
|
||||
{
|
||||
if (!TabSwitcher || Index < 0 || Index >= TabSwitcher->GetNumWidgets())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TabSwitcher->SetActiveWidgetIndex(Index);
|
||||
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->RefreshFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::UpdateTabButtonStates()
|
||||
{
|
||||
const int32 PageCount = TabSwitcher ? TabSwitcher->GetNumWidgets() : 0;
|
||||
|
||||
if (GameplayTabButton)
|
||||
{
|
||||
GameplayTabButton->SetIsEnabled(PageCount > TabIndexGameplay);
|
||||
}
|
||||
|
||||
if (DisplayTabButton)
|
||||
{
|
||||
DisplayTabButton->SetIsEnabled(PageCount > TabIndexDisplay);
|
||||
}
|
||||
|
||||
if (GraphicsTabButton)
|
||||
{
|
||||
GraphicsTabButton->SetIsEnabled(PageCount > TabIndexGraphics);
|
||||
}
|
||||
|
||||
if (AudioTabButton)
|
||||
{
|
||||
AudioTabButton->SetIsEnabled(PageCount > TabIndexAudio);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleBackClicked()
|
||||
{
|
||||
OnCloseRequested.Broadcast();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetClicked()
|
||||
{
|
||||
// Sans boite assignee on ne bloque pas le joueur : on reinitialise.
|
||||
if (!ResetDialog)
|
||||
{
|
||||
HandleResetConfirmed();
|
||||
return;
|
||||
}
|
||||
|
||||
bResetDialogOpen = true;
|
||||
ResetDialog->Open(ResetDialogMessage);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetConfirmed()
|
||||
{
|
||||
bResetDialogOpen = false;
|
||||
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
// Uniquement l'onglet visible : un "Reinitialiser" qui remettrait aussi la
|
||||
// resolution et le volume serait une mauvaise surprise.
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->ResetToDefaults();
|
||||
}
|
||||
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetCancelled()
|
||||
{
|
||||
bResetDialogOpen = false;
|
||||
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleGameplayTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexGameplay);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleDisplayTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexDisplay);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleGraphicsTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexGraphics);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleAudioTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexAudio);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,27 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SurvivalGameInstance.h"
|
||||
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
void USurvivalGameInstance::Init()
|
||||
{
|
||||
Super::Init();
|
||||
|
||||
// Les reglages sont deja LUS depuis le .ini par le moteur, mais rien ne les
|
||||
// a encore APPLIQUES : sans cet appel, la resolution et la qualite
|
||||
// choisies au dernier lancement resteraient lettre morte jusqu'au premier
|
||||
// passage dans le menu.
|
||||
//
|
||||
// C'est le bon endroit : le GameInstance est le seul objet qui naisse avant
|
||||
// la premiere map et survive a tous les changements de niveau.
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
Settings->ApplySettings(/*bCheckForCommandLineOverrides=*/false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USurvivalGameInstance : GameUserSettingsClassName n'est pas configure dans DefaultEngine.ini, aucun reglage ne sera pris en compte."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Arrondit sur le palier le plus proche, puis borne. */
|
||||
float SnapToStep(float Value, float Min, float Max, float Step)
|
||||
{
|
||||
const float Snapped = Min + FMath::RoundToFloat((Value - Min) / Step) * Step;
|
||||
return FMath::Clamp(Snapped, Min, Max);
|
||||
}
|
||||
}
|
||||
|
||||
USurvivalUserSettings::USurvivalUserSettings()
|
||||
{
|
||||
// Le constructeur tourne sur le CDO, avant toute lecture du .ini : ces
|
||||
// valeurs sont donc bien les defauts d'usine, ecrasees ensuite par le
|
||||
// fichier s'il existe.
|
||||
SetSurvivalGameplayToDefaults();
|
||||
}
|
||||
|
||||
USurvivalUserSettings* USurvivalUserSettings::Get()
|
||||
{
|
||||
return GEngine ? Cast<USurvivalUserSettings>(GEngine->GetGameUserSettings()) : nullptr;
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetSurvivalGameplayToDefaults()
|
||||
{
|
||||
LookSensitivity = 35.f;
|
||||
bSeparateVerticalSensitivity = false;
|
||||
LookSensitivityVertical = 35.f;
|
||||
bInvertLookY = false;
|
||||
FieldOfView = 90.f;
|
||||
CameraShakeScale = 100.f;
|
||||
bSprintToggle = false;
|
||||
bCrouchToggle = false;
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetToDefaults()
|
||||
{
|
||||
// Le "Reinitialiser" global du moteur doit aussi remettre NOS valeurs :
|
||||
// sans cette surcharge, l'onglet Jeu resterait seul inchange.
|
||||
Super::SetToDefaults();
|
||||
|
||||
SetSurvivalGameplayToDefaults();
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::ApplySurvivalSettings()
|
||||
{
|
||||
OnSurvivalSettingsApplied.Broadcast();
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::ApplyNonResolutionSettings()
|
||||
{
|
||||
Super::ApplyNonResolutionSettings();
|
||||
|
||||
// ApplySettings() passe forcement par ici : brancher la diffusion a ce
|
||||
// niveau garantit que nos reglages suivent, quel que soit le chemin.
|
||||
ApplySurvivalSettings();
|
||||
}
|
||||
|
||||
float USurvivalUserSettings::GetLookMultiplierX() const
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
|
||||
const float Normalized = FMath::GetRangePct(SensitivityMin, SensitivityMax, LookSensitivity);
|
||||
return FMath::Lerp(LookMultiplierAtMin, LookMultiplierAtMax, Normalized);
|
||||
}
|
||||
|
||||
float USurvivalUserSettings::GetLookMultiplierY() const
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
|
||||
// Sans axe separe, le vertical suit l'horizontal : c'est ce que veulent
|
||||
// 95 % des joueurs, et deux curseurs desynchronises par inadvertance
|
||||
// donnent une visee bancale sans qu'on comprenne pourquoi.
|
||||
if (!bSeparateVerticalSensitivity)
|
||||
{
|
||||
return GetLookMultiplierX();
|
||||
}
|
||||
|
||||
const float Normalized = FMath::GetRangePct(SensitivityMin, SensitivityMax, LookSensitivityVertical);
|
||||
return FMath::Lerp(LookMultiplierAtMin, LookMultiplierAtMax, Normalized);
|
||||
}
|
||||
|
||||
float USurvivalUserSettings::GetCameraShakeMultiplier() const
|
||||
{
|
||||
return FMath::Clamp(CameraShakeScale / 100.f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetLookSensitivity(float Value)
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
LookSensitivity = SnapToStep(Value, SensitivityMin, SensitivityMax, SensitivityStep);
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetSeparateVerticalSensitivity(bool bValue)
|
||||
{
|
||||
// En repassant sur "non separe" on recale le vertical sur l'horizontal :
|
||||
// sinon la valeur oubliee ressortirait telle quelle a la reactivation.
|
||||
if (!bValue)
|
||||
{
|
||||
LookSensitivityVertical = LookSensitivity;
|
||||
}
|
||||
|
||||
bSeparateVerticalSensitivity = bValue;
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetLookSensitivityVertical(float Value)
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
LookSensitivityVertical = SnapToStep(Value, SensitivityMin, SensitivityMax, SensitivityStep);
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetInvertLookY(bool bValue)
|
||||
{
|
||||
bInvertLookY = bValue;
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetFieldOfView(float Value)
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
FieldOfView = SnapToStep(Value, FieldOfViewMin, FieldOfViewMax, FieldOfViewStep);
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetCameraShakeScale(float Value)
|
||||
{
|
||||
using namespace SurvivalSettingsRange;
|
||||
CameraShakeScale = SnapToStep(Value, CameraShakeMin, CameraShakeMax, CameraShakeStep);
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetSprintToggle(bool bValue)
|
||||
{
|
||||
bSprintToggle = bValue;
|
||||
}
|
||||
|
||||
void USurvivalUserSettings::SetCrouchToggle(bool bValue)
|
||||
{
|
||||
bCrouchToggle = bValue;
|
||||
}
|
||||
@@ -89,10 +89,23 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Camera")
|
||||
void AddCameraPunch(float Strength);
|
||||
|
||||
/**
|
||||
* Recopie les reglages du joueur dans le pawn.
|
||||
*
|
||||
* Appelee au BeginPlay ET a chaque fois que USurvivalUserSettings diffuse :
|
||||
* apres un respawn le pawn est un NOUVEL acteur, une valeur poussee une
|
||||
* seule fois serait perdue avec le cadavre. UFUNCTION obligatoire, c'est ce
|
||||
* qui permet le AddDynamic sur le delegue.
|
||||
*/
|
||||
UFUNCTION()
|
||||
void ApplyUserSettings();
|
||||
|
||||
protected:
|
||||
// Called when the game starts or when spawned
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
|
||||
virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
|
||||
virtual void OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
|
||||
@@ -209,6 +222,10 @@ protected:
|
||||
/**
|
||||
* Sensibilité horizontale : degrés de rotation par unité de souris.
|
||||
* 1.0 = brut (beaucoup trop rapide), ~0.4 est un bon point de départ.
|
||||
*
|
||||
* ATTENTION : ces trois valeurs, comme BaseFOV, sont ECRASEES au BeginPlay
|
||||
* par USurvivalUserSettings. Elles ne servent plus que de repli si l'objet
|
||||
* de réglages est absent ; pour changer la sensibilité, passe par le menu.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
|
||||
float LookSensitivityX = 0.4f;
|
||||
@@ -344,6 +361,16 @@ protected:
|
||||
void StopCrouch();
|
||||
void Interact();
|
||||
|
||||
/**
|
||||
* Relachement de la touche de sprint / d'accroupissement.
|
||||
*
|
||||
* Separes de StopSprint et StopCrouch, qui restent des ordres inconditionnels :
|
||||
* en mode bascule le relachement ne doit rien faire, alors que la mort ou
|
||||
* l'ouverture de l'inventaire doivent bel et bien couper le sprint.
|
||||
*/
|
||||
void HandleSprintReleased();
|
||||
void HandleCrouchReleased();
|
||||
|
||||
/**
|
||||
* Clic gauche : agit avec l'objet actif de la barre rapide.
|
||||
* Aujourd'hui seuls les consommables font quelque chose ; c'est ici que
|
||||
@@ -392,4 +419,15 @@ private:
|
||||
|
||||
/** Évite de spammer le log si IA_Look est mal configurée. */
|
||||
bool bLoggedLookTypeWarning = false;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Copies locales des réglages joueur, rafraîchies par ApplyUserSettings
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/** 0 = caméra parfaitement fixe, 1 = amplitude d'origine. */
|
||||
float CameraShakeMultiplier = 1.f;
|
||||
|
||||
bool bSprintToggleMode = false;
|
||||
|
||||
bool bCrouchToggleMode = false;
|
||||
};
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "FpsPlayerController.generated.h"
|
||||
|
||||
class UConfirmDialogWidget;
|
||||
class UHotbarWidget;
|
||||
class UInputAction;
|
||||
class UInventoryComponent;
|
||||
class UInteractionPromptWidget;
|
||||
class UInventoryWidget;
|
||||
class UPauseMenuWidget;
|
||||
class UScreenFadeComponent;
|
||||
class USettingsMenuWidget;
|
||||
class USurvivalStatsWidget;
|
||||
class UUserWidget;
|
||||
struct FInputActionValue;
|
||||
@@ -36,6 +39,21 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "UI")
|
||||
bool IsInventoryOpen() const { return bInventoryOpen; }
|
||||
|
||||
/** Ferme le menu de pause et relance le monde. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI|Pause")
|
||||
void ResumeGame();
|
||||
|
||||
/** Ouvre la demande de confirmation. Ne quitte rien par elle-meme. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI|Pause")
|
||||
void RequestQuitToMainMenu();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "UI|Pause")
|
||||
bool IsPauseMenuOpen() const { return bPauseMenuOpen; }
|
||||
|
||||
/** Ouvre l'ecran de reglages par-dessus le menu de pause. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI|Reglages")
|
||||
void OpenSettings();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void OnPossess(APawn* InPawn) override;
|
||||
@@ -105,6 +123,17 @@ protected:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||
TObjectPtr<UInputAction> HotbarScrollAction;
|
||||
|
||||
/**
|
||||
* Echap : ferme l'inventaire s'il est ouvert, sinon bascule la pause.
|
||||
*
|
||||
* ATTENTION : coche "Trigger When Paused" sur l'asset IA_Pause. Un jeu en
|
||||
* pause ne fait plus qu'un tick reduit ou seules les actions portant ce
|
||||
* drapeau sont evaluees -- sans lui, on peut mettre en pause mais plus
|
||||
* jamais en sortir au clavier.
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||
TObjectPtr<UInputAction> PauseAction;
|
||||
|
||||
/**
|
||||
* Viseur, affiche en permanence. Widget separe du prompt : ce dernier se
|
||||
* met en Collapsed des qu'on ne vise plus rien, et emporterait le viseur
|
||||
@@ -129,6 +158,42 @@ protected:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
|
||||
TSubclassOf<USurvivalStatsWidget> SurvivalStatsClass;
|
||||
|
||||
/** Ton WBP_PauseMenu. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
|
||||
TSubclassOf<UPauseMenuWidget> PauseMenuClass;
|
||||
|
||||
/**
|
||||
* Ton WBP_ConfirmDialog, le meme que celui du menu principal. Laisse-le
|
||||
* assigne : sans lui on retourne au menu sans rien demander, et la partie
|
||||
* est perdue sur un clic.
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
|
||||
TSubclassOf<UConfirmDialogWidget> QuitToMenuDialogClass;
|
||||
|
||||
/** La question posee avant d'abandonner la partie. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
|
||||
FText QuitToMenuMessage;
|
||||
|
||||
/**
|
||||
* Ton WBP_Settings, le meme que celui du menu principal. C'est tout
|
||||
* l'interet de son delegue OnCloseRequested : l'ecran ignore qui l'ouvre.
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Reglages")
|
||||
TSubclassOf<USettingsMenuWidget> SettingsMenuClass;
|
||||
|
||||
/**
|
||||
* Ta map de menu principal.
|
||||
*
|
||||
* TSoftObjectPtr comme dans AMainMenuPlayerController : une reference dure
|
||||
* ferait charger la map du menu en meme temps que celle du jeu.
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
|
||||
TSoftObjectPtr<UWorld> MainMenuLevel;
|
||||
|
||||
/** Fondu au noir avant de rendre la main au menu principal. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
|
||||
float QuitToMenuFadeDuration = 1.f;
|
||||
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
|
||||
TObjectPtr<UUserWidget> Crosshair;
|
||||
|
||||
@@ -144,9 +209,43 @@ protected:
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
|
||||
TObjectPtr<USurvivalStatsWidget> SurvivalStatsWidget;
|
||||
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Pause")
|
||||
TObjectPtr<UPauseMenuWidget> PauseMenu;
|
||||
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Pause")
|
||||
TObjectPtr<UConfirmDialogWidget> QuitToMenuDialog;
|
||||
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Reglages")
|
||||
TObjectPtr<USettingsMenuWidget> SettingsMenu;
|
||||
|
||||
private:
|
||||
void CreateHudWidgets();
|
||||
|
||||
/**
|
||||
* Echap. Une seule touche pour trois usages, d'ou une priorite explicite :
|
||||
* boite de confirmation, puis inventaire, puis pause. Sans cet ordre on se
|
||||
* retrouve avec l'inventaire et la pause ouverts en meme temps.
|
||||
*/
|
||||
void HandlePauseInput();
|
||||
|
||||
void OpenPauseMenu();
|
||||
|
||||
/** Oui : fondu au noir, puis retour au menu principal. */
|
||||
UFUNCTION()
|
||||
void HandleQuitToMenuConfirmed();
|
||||
|
||||
/** Non : on referme la boite et on rend la main au menu de pause. */
|
||||
UFUNCTION()
|
||||
void HandleQuitToMenuCancelled();
|
||||
|
||||
/** Le fondu est termine : on charge la map du menu, si c'est ce qu'on attendait. */
|
||||
UFUNCTION()
|
||||
void HandleFadeFinished();
|
||||
|
||||
/** L'ecran de reglages demande a se fermer : on rend la main au menu de pause. */
|
||||
UFUNCTION()
|
||||
void HandleSettingsCloseRequested();
|
||||
|
||||
/** Jette la pile survolee dans la grille, si l'inventaire est ouvert. */
|
||||
void DropHoveredItem();
|
||||
|
||||
@@ -166,4 +265,18 @@ private:
|
||||
FTimerHandle RespawnTimerHandle;
|
||||
|
||||
bool bInventoryOpen = false;
|
||||
|
||||
bool bPauseMenuOpen = false;
|
||||
|
||||
/**
|
||||
* La boite de confirmation avale Echap elle-meme quand elle a le focus,
|
||||
* mais un clic ailleurs le lui retire : sans ce booleen, Echap refermerait
|
||||
* alors le menu de pause en laissant la question affichee dans le vide.
|
||||
*/
|
||||
bool bQuitToMenuDialogOpen = false;
|
||||
|
||||
bool bSettingsOpen = false;
|
||||
|
||||
/** Distingue le fondu de sortie de tous les autres dans HandleFadeFinished. */
|
||||
bool bQuittingToMainMenu = false;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ class AMenuCameraSpot;
|
||||
class UConfirmDialogWidget;
|
||||
class UMainMenuWidget;
|
||||
class UScreenFadeComponent;
|
||||
class USettingsMenuWidget;
|
||||
|
||||
/**
|
||||
* Ce qu'on fait une fois l'ecran devenu noir.
|
||||
@@ -45,6 +46,10 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Menu")
|
||||
void QuitGame();
|
||||
|
||||
/** Ouvre l'ecran de reglages par-dessus le menu. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Menu")
|
||||
void OpenSettings();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
@@ -75,6 +80,10 @@ protected:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
|
||||
FText QuitDialogMessage;
|
||||
|
||||
/** Ton WBP_Settings, exactement le meme que celui du menu de pause. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
|
||||
TSubclassOf<USettingsMenuWidget> SettingsMenuClass;
|
||||
|
||||
/** Fondu d'arrivee sur le menu, au lancement du jeu. */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
|
||||
float OpeningFadeDuration = 1.5f;
|
||||
@@ -93,6 +102,9 @@ protected:
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
|
||||
TObjectPtr<UConfirmDialogWidget> QuitDialog;
|
||||
|
||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
|
||||
TObjectPtr<USettingsMenuWidget> SettingsMenu;
|
||||
|
||||
private:
|
||||
/** Tire un angle de vue au hasard, en evitant celui du dernier passage. */
|
||||
AMenuCameraSpot* PickCameraSpot();
|
||||
@@ -109,6 +121,10 @@ private:
|
||||
UFUNCTION()
|
||||
void HandleQuitCancelled();
|
||||
|
||||
/** L'ecran de reglages demande a se fermer : on rend la main au menu. */
|
||||
UFUNCTION()
|
||||
void HandleSettingsCloseRequested();
|
||||
|
||||
/** Ferme le jeu sans rien demander. */
|
||||
void CloseGame();
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ protected:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> QuitButton;
|
||||
|
||||
/** BindWidgetOptional : un WBP_MainMenu sans bouton Reglages reste valide. */
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> SettingsButton;
|
||||
|
||||
/**
|
||||
* Coop a venir. BindWidgetOptional et non BindWidget : tu peux dessiner ton
|
||||
* WBP_MainMenu sans eux aujourd'hui, le Blueprint compilera quand meme.
|
||||
@@ -52,4 +56,7 @@ private:
|
||||
|
||||
UFUNCTION()
|
||||
void HandleQuitClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSettingsClicked();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "PauseMenuWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
|
||||
/**
|
||||
* Le menu de pause : trois boutons par-dessus la partie figee.
|
||||
*
|
||||
* Comme UMainMenuWidget, il ne decide de rien -- il appelle
|
||||
* AFpsPlayerController. Mettre SetPause ou OpenLevel ici serait doublement
|
||||
* faux : un widget n'existe que sur la machine du joueur local, et la mise en
|
||||
* pause appartient a l'autorite.
|
||||
*
|
||||
* Classe Abstract : on cree un WBP_PauseMenu qui en herite.
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class SURVIVAL_PROJET_API UPauseMenuWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
protected:
|
||||
/** Reprend la partie. Nomme exactement "ResumeButton" dans le WBP. */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> ResumeButton;
|
||||
|
||||
/** Retour au menu principal, apres confirmation. */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> QuitToMenuButton;
|
||||
|
||||
/**
|
||||
* BindWidgetOptional : un WBP_PauseMenu sans bouton Reglages reste valide.
|
||||
*
|
||||
* Pas de bouton "Quitter le jeu" ici, volontairement : la sortie se fait
|
||||
* depuis le menu principal, ou l'on repasse de toute facon.
|
||||
*/
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> SettingsButton;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleResumeClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSettingsClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleQuitToMenuClicked();
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "SettingRowWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UProgressBar;
|
||||
class UTextBlock;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSettingRowIndexChanged, int32, NewIndex);
|
||||
|
||||
/**
|
||||
* Une ligne de reglage : un libelle, une fleche gauche, une valeur, une fleche
|
||||
* droite, et une barre de remplissage optionnelle derriere la valeur.
|
||||
*
|
||||
* TOUT reglage est ramene a un index dans une liste de paliers, y compris les
|
||||
* nombres : "Champ de vision, 70 a 120 par pas de 5" devient un index de 0 a 10.
|
||||
* La ligne ne connait donc jamais que CurrentIndex et StepCount, et c'est ce qui
|
||||
* permet de n'avoir qu'UN SEUL WBP a dessiner pour tout le menu, qu'il s'agisse
|
||||
* d'un choix (Maintien / Bascule) ou d'une valeur continue.
|
||||
*
|
||||
* Elle ne sait pas ce qu'elle regle : elle previent via OnIndexChanged, et c'est
|
||||
* l'onglet qui traduit l'index en appel sur USurvivalUserSettings. Meme principe
|
||||
* que UConfirmDialogWidget, qui ignore ce qu'il confirme.
|
||||
*
|
||||
* Classe Abstract : on cree un WBP_SettingRow qui en herite.
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class SURVIVAL_PROJET_API USettingRowWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void NativeOnInitialized() override;
|
||||
|
||||
/** Previent d'un changement venant du joueur, jamais d'un rafraichissement. */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Reglages")
|
||||
FOnSettingRowIndexChanged OnIndexChanged;
|
||||
|
||||
/**
|
||||
* Mode choix : la valeur affichee est prise telle quelle dans Options.
|
||||
* Sert aussi bien a un oui/non qu'a une liste de qualites.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void SetupAsChoice(const FText& Label, const TArray<FText>& Options, int32 Index);
|
||||
|
||||
/**
|
||||
* Mode nombre : la valeur affichee est calculee a partir de l'index.
|
||||
* Suffix sert aux unites ("%", "°") et peut rester vide.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void SetupAsNumber(const FText& Label, float Min, float Max, float Step, float Value, const FText& Suffix);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages")
|
||||
int32 GetIndex() const { return CurrentIndex; }
|
||||
|
||||
/** Reconvertit l'index en valeur reelle. Sans objet en mode choix. */
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages")
|
||||
float GetNumericValue() const;
|
||||
|
||||
/**
|
||||
* Repositionne la ligne sans rien diffuser.
|
||||
*
|
||||
* A utiliser pour tout rafraichissement venant des reglages : diffuser ici
|
||||
* ferait boucler l'onglet sur lui-meme (il reecrit le reglage, qui rafraichit
|
||||
* la ligne, qui rediffuse...).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void SetIndexSilently(int32 NewIndex);
|
||||
|
||||
/** Repositionne la ligne sur une valeur reelle, sans rien diffuser. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void SetNumericValueSilently(float Value);
|
||||
|
||||
protected:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UTextBlock> LabelText;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UTextBlock> ValueText;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> LeftArrowButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> RightArrowButton;
|
||||
|
||||
/**
|
||||
* Facultative : une ligne de choix (Maintien / Bascule) n'a rien a remplir.
|
||||
* Volontairement une ProgressBar et non un Slider : la barre montre ou l'on
|
||||
* en est, ce sont les fleches qui reglent. Un slider glissable produirait des
|
||||
* valeurs entre deux paliers que le setter refuserait ensuite.
|
||||
*/
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UProgressBar> ValueBar;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleLeftClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleRightClicked();
|
||||
|
||||
/** Deplace l'index de Delta paliers et diffuse, si le mouvement est possible. */
|
||||
void StepIndex(int32 Delta);
|
||||
|
||||
/** Reecrit le texte, la barre et l'etat des deux fleches. */
|
||||
void Refresh();
|
||||
|
||||
/** Vide en mode nombre. */
|
||||
TArray<FText> ChoiceLabels;
|
||||
|
||||
FText NumericSuffix;
|
||||
|
||||
float NumericMin = 0.f;
|
||||
float NumericStep = 1.f;
|
||||
|
||||
int32 CurrentIndex = 0;
|
||||
|
||||
/** Nombre de paliers, donc d'index valides. Toujours au moins 1. */
|
||||
int32 StepCount = 1;
|
||||
|
||||
bool bNumeric = false;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SettingsTabWidget.h"
|
||||
#include "SettingsGameplayWidget.generated.h"
|
||||
|
||||
class USettingRowWidget;
|
||||
class USurvivalUserSettings;
|
||||
|
||||
/**
|
||||
* L'onglet "Jeu & Controles".
|
||||
*
|
||||
* Il ne stocke rien : chaque ligne ecrit directement dans USurvivalUserSettings,
|
||||
* qui diffuse au pawn dans la foulee. C'est ce qui rend l'effet immediat sans
|
||||
* bouton "Appliquer".
|
||||
*
|
||||
* Classe Abstract : on cree un WBP_SettingsGameplay qui en herite, avec un
|
||||
* WBP_SettingRow par ligne, nomme exactement comme le BindWidget correspondant.
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class SURVIVAL_PROJET_API USettingsGameplayWidget : public USettingsTabWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
virtual void RefreshFromSettings() override;
|
||||
virtual void ResetToDefaults() override;
|
||||
|
||||
protected:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> SensitivityRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> SeparateVerticalRow;
|
||||
|
||||
/** Masquee tant que la sensibilite verticale n'est pas separee. */
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> SensitivityVerticalRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> InvertLookRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> FieldOfViewRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> CameraShakeRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> SprintModeRow;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<USettingRowWidget> CrouchModeRow;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleSensitivityChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSeparateVerticalChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSensitivityVerticalChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleInvertLookChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleFieldOfViewChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCameraShakeChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSprintModeChanged(int32 NewIndex);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCrouchModeChanged(int32 NewIndex);
|
||||
|
||||
/** Ecrit puis diffuse. Tous les handlers finissent ici. */
|
||||
void PushToPawn(USurvivalUserSettings* Settings);
|
||||
|
||||
/** Affiche ou masque la ligne de sensibilite verticale. */
|
||||
void UpdateVerticalRowVisibility(bool bSeparate);
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "SettingsMenuWidget.generated.h"
|
||||
|
||||
class UButton;
|
||||
class UConfirmDialogWidget;
|
||||
class USettingsTabWidget;
|
||||
class UWidgetSwitcher;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSettingsCloseRequested);
|
||||
|
||||
/**
|
||||
* L'ecran de reglages : une barre d'onglets, un WidgetSwitcher, Retour et
|
||||
* Reinitialiser.
|
||||
*
|
||||
* Il ne sait PAS qui l'a ouvert. Il demande a se fermer via OnCloseRequested et
|
||||
* c'est l'appelant -- menu principal ou menu de pause -- qui decide de ce que
|
||||
* "Retour" veut dire. Sans ca il faudrait un ecran par contexte.
|
||||
*
|
||||
* Il ne connait pas davantage le contenu de ses onglets : il appelle
|
||||
* RefreshFromSettings et ResetToDefaults sur USettingsTabWidget. Ajouter
|
||||
* l'onglet Graphismes ne demandera aucune ligne ici.
|
||||
*
|
||||
* Classe Abstract : on cree un WBP_Settings qui en herite.
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class SURVIVAL_PROJET_API USettingsMenuWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
USettingsMenuWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Reglages")
|
||||
FOnSettingsCloseRequested OnCloseRequested;
|
||||
|
||||
/**
|
||||
* Affiche l'ecran, rafraichit l'onglet visible et prend le focus clavier.
|
||||
*
|
||||
* Passe par ici et jamais par SetVisibility : sans le focus, Echap ne
|
||||
* refermerait pas l'ecran.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void Open();
|
||||
|
||||
/** Masque l'ecran et ecrit le fichier de reglages. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void Close();
|
||||
|
||||
protected:
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UWidgetSwitcher> TabSwitcher;
|
||||
|
||||
UPROPERTY(meta = (BindWidget))
|
||||
TObjectPtr<UButton> BackButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> ResetButton;
|
||||
|
||||
/**
|
||||
* Les onglets a venir. Grises automatiquement tant que le WidgetSwitcher ne
|
||||
* contient pas la page correspondante -- meme principe que les boutons de
|
||||
* coop du menu principal : ils annoncent la couleur sans mener nulle part.
|
||||
*/
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> GameplayTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> DisplayTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> GraphicsTabButton;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UButton> AudioTabButton;
|
||||
|
||||
/**
|
||||
* Boite de confirmation POSEE DANS le WBP_Settings, et non ajoutee au
|
||||
* viewport : elle se retrouve ainsi naturellement au-dessus de l'ecran de
|
||||
* reglages, sans avoir a lui trouver un ZOrder qui n'entre en conflit ni
|
||||
* avec le menu de pause ni avec la boite d'abandon de partie.
|
||||
*
|
||||
* Absente, "Reinitialiser" agit sans rien demander.
|
||||
*/
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UConfirmDialogWidget> ResetDialog;
|
||||
|
||||
/** La question posee avant de reinitialiser l'onglet visible. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Reglages")
|
||||
FText ResetDialogMessage;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleBackClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleResetClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleResetConfirmed();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleResetCancelled();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleGameplayTabClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleDisplayTabClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleGraphicsTabClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleAudioTabClicked();
|
||||
|
||||
/** Change de page et rafraichit la nouvelle. Ignore un index inexistant. */
|
||||
void ShowTab(int32 Index);
|
||||
|
||||
/** La page actuellement affichee, ou nullptr si le switcher est vide. */
|
||||
USettingsTabWidget* GetActiveTab() const;
|
||||
|
||||
/** Grise les boutons dont la page n'existe pas encore. */
|
||||
void UpdateTabButtonStates();
|
||||
|
||||
bool bResetDialogOpen = false;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "SettingsTabWidget.generated.h"
|
||||
|
||||
/**
|
||||
* Base commune a tous les onglets de reglages.
|
||||
*
|
||||
* Deux verbes, rien de plus : l'ecran parent rafraichit ou reinitialise l'onglet
|
||||
* visible sans jamais savoir ce qu'il contient. Ajouter l'onglet Graphismes plus
|
||||
* tard ne demandera donc aucune ligne dans USettingsMenuWidget.
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class SURVIVAL_PROJET_API USettingsTabWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Relit les reglages et repositionne les lignes, sans rien diffuser. */
|
||||
virtual void RefreshFromSettings() {}
|
||||
|
||||
/** Remet a l'usine les seuls reglages de cet onglet, puis se rafraichit. */
|
||||
virtual void ResetToDefaults() {}
|
||||
};
|
||||
@@ -24,6 +24,8 @@ class SURVIVAL_PROJET_API USurvivalGameInstance : public UGameInstance
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Init() override;
|
||||
|
||||
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
|
||||
UFUNCTION(BlueprintPure, Category = "Menu")
|
||||
FName GetLastMenuCameraName() const { return LastMenuCameraName; }
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameUserSettings.h"
|
||||
#include "SurvivalUserSettings.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalSettingsApplied);
|
||||
|
||||
/**
|
||||
* Bornes et pas des reglages numeriques.
|
||||
*
|
||||
* Ici et pas dans le widget : c'est l'objet de reglages qui borne ses propres
|
||||
* valeurs, et l'ecran doit proposer exactement les memes paliers. Deux jeux de
|
||||
* constantes finiraient par diverger et le menu afficherait des valeurs que le
|
||||
* setter refuse.
|
||||
*/
|
||||
namespace SurvivalSettingsRange
|
||||
{
|
||||
constexpr float SensitivityMin = 5.f;
|
||||
constexpr float SensitivityMax = 100.f;
|
||||
constexpr float SensitivityStep = 5.f;
|
||||
|
||||
constexpr float FieldOfViewMin = 70.f;
|
||||
constexpr float FieldOfViewMax = 120.f;
|
||||
constexpr float FieldOfViewStep = 5.f;
|
||||
|
||||
constexpr float CameraShakeMin = 0.f;
|
||||
constexpr float CameraShakeMax = 100.f;
|
||||
constexpr float CameraShakeStep = 10.f;
|
||||
|
||||
/**
|
||||
* Multiplicateurs reels correspondant aux extremites de l'echelle affichee.
|
||||
*
|
||||
* Le joueur regle un nombre de 5 a 100 parce que "0.4" ne veut rien dire ;
|
||||
* le pawn, lui, veut le multiplicateur. Le defaut de 35 retombe sur ~0.41,
|
||||
* soit la valeur qui etait codee en dur dans AFpsPlayer.
|
||||
*/
|
||||
constexpr float LookMultiplierAtMin = 0.05f;
|
||||
constexpr float LookMultiplierAtMax = 1.2f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Les reglages du joueur, et le seul endroit qui fasse autorite dessus.
|
||||
*
|
||||
* Sous-classe de UGameUserSettings et non un objet a nous : le moteur fournit
|
||||
* deja le singleton, sa persistance dans Saved/Config/Windows/GameUserSettings.ini
|
||||
* et tout le volet video (resolution, VSync, qualite). C'est l'equivalent d'un
|
||||
* PlayerPrefs + QualitySettings + Screen d'Unity reunis, sauf qu'il est deja
|
||||
* ecrit. On l'etend, on ne le remplace pas.
|
||||
*
|
||||
* Pour que le moteur instancie CETTE classe et pas la sienne, Config/DefaultEngine.ini
|
||||
* contient :
|
||||
* [/Script/Engine.Engine]
|
||||
* GameUserSettingsClassName=/Script/Survival_projet.SurvivalUserSettings
|
||||
*
|
||||
* Les UPROPERTY(config) sont lues et ecrites automatiquement : aucun code de
|
||||
* serialisation a maintenir.
|
||||
*/
|
||||
UCLASS()
|
||||
class SURVIVAL_PROJET_API USurvivalUserSettings : public UGameUserSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
USurvivalUserSettings();
|
||||
|
||||
/**
|
||||
* Le raccourci typé vers le singleton. GEngine->GetGameUserSettings()
|
||||
* renvoie la classe de base ; ce helper evite un cast a chaque appel.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages", meta = (DisplayName = "Get Survival User Settings"))
|
||||
static USurvivalUserSettings* Get();
|
||||
|
||||
/**
|
||||
* Previent tout le monde que les reglages de jeu ont change.
|
||||
*
|
||||
* Indispensable a cause du respawn : le pawn est un NOUVEL acteur a chaque
|
||||
* mort, et une valeur poussee une fois dans l'ancien serait perdue. Chaque
|
||||
* pawn lit les reglages a son BeginPlay et s'abonne ici pour la suite.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "Reglages")
|
||||
FOnSurvivalSettingsApplied OnSurvivalSettingsApplied;
|
||||
|
||||
/**
|
||||
* Diffuse les reglages de jeu, sans toucher au volet video.
|
||||
*
|
||||
* Distinct d'ApplyNonResolutionSettings, qui repousse aussi la qualite et la
|
||||
* limite de FPS : l'appeler a chaque clic de fleche sur la sensibilite
|
||||
* provoquerait un a-coup pour rien.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void ApplySurvivalSettings();
|
||||
|
||||
/** Remet les seuls reglages de jeu a leur valeur d'usine. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages")
|
||||
void SetSurvivalGameplayToDefaults();
|
||||
|
||||
virtual void SetToDefaults() override;
|
||||
virtual void ApplyNonResolutionSettings() override;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Valeurs pretes a l'emploi
|
||||
//
|
||||
// Le pawn ne doit jamais lire les champs bruts : ce sont des valeurs
|
||||
// d'affichage (5 a 100), pas des multiplicateurs.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
float GetLookMultiplierX() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
float GetLookMultiplierY() const;
|
||||
|
||||
/** 0 = camera parfaitement fixe, 1 = amplitude d'origine. */
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
|
||||
float GetCameraShakeMultiplier() const;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Accesseurs bruts, pour l'ecran de reglages
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
float GetLookSensitivity() const { return LookSensitivity; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetLookSensitivity(float Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
bool GetSeparateVerticalSensitivity() const { return bSeparateVerticalSensitivity; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetSeparateVerticalSensitivity(bool bValue);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
float GetLookSensitivityVertical() const { return LookSensitivityVertical; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetLookSensitivityVertical(float Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
bool GetInvertLookY() const { return bInvertLookY; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetInvertLookY(bool bValue);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
|
||||
float GetFieldOfView() const { return FieldOfView; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Jeu")
|
||||
void SetFieldOfView(float Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
|
||||
float GetCameraShakeScale() const { return CameraShakeScale; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Jeu")
|
||||
void SetCameraShakeScale(float Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
bool GetSprintToggle() const { return bSprintToggle; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetSprintToggle(bool bValue);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
|
||||
bool GetCrouchToggle() const { return bCrouchToggle; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
|
||||
void SetCrouchToggle(bool bValue);
|
||||
|
||||
private:
|
||||
/** Echelle lisible (5 a 100), convertie a la lecture. Jamais brute. */
|
||||
UPROPERTY(config)
|
||||
float LookSensitivity;
|
||||
|
||||
UPROPERTY(config)
|
||||
bool bSeparateVerticalSensitivity;
|
||||
|
||||
UPROPERTY(config)
|
||||
float LookSensitivityVertical;
|
||||
|
||||
UPROPERTY(config)
|
||||
bool bInvertLookY;
|
||||
|
||||
UPROPERTY(config)
|
||||
float FieldOfView;
|
||||
|
||||
/** Pourcentage. A 0, plus aucun impact camera : pour le mal des transports. */
|
||||
UPROPERTY(config)
|
||||
float CameraShakeScale;
|
||||
|
||||
UPROPERTY(config)
|
||||
bool bSprintToggle;
|
||||
|
||||
UPROPERTY(config)
|
||||
bool bCrouchToggle;
|
||||
};
|
||||
Reference in New Issue
Block a user