(Feat) Add MainMenu
This commit is contained in:
+38
-4
@@ -44,8 +44,9 @@ qu'un assemblage de composants plus la caméra et le déplacement.
|
|||||||
| Nature | Emplacement |
|
| Nature | Emplacement |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Le corps : déplacement, caméra, inventaire, stats, interaction | **Pawn** |
|
| Le corps : déplacement, caméra, inventaire, stats, interaction | **Pawn** |
|
||||||
| Le joueur : widgets HUD, input d'interface (Tab, Q, hotbar) | **PlayerController** |
|
| Le joueur : widgets HUD, input d'interface (Tab, Q, hotbar, Échap) | **PlayerController** |
|
||||||
| Les règles de la partie, le respawn | **GameMode** |
|
| Les règles de la partie, le respawn | **GameMode** |
|
||||||
|
| Ce qui survit au changement de map : réglages, sauvegarde | **GameInstance** |
|
||||||
| Ce que les autres savent de toi (plus tard, si multi) | **PlayerState** |
|
| Ce que les autres savent de toi (plus tard, si multi) | **PlayerState** |
|
||||||
|
|
||||||
**Données vs état.** `UItemDataAsset` décrit ce qu'un objet **est** (nom, mesh, icône, effets).
|
**Données vs état.** `UItemDataAsset` décrit ce qu'un objet **est** (nom, mesh, icône, effets).
|
||||||
@@ -64,6 +65,15 @@ pour itérer en PIE sans recompiler.
|
|||||||
`TWeakObjectPtr` ne permet pas de détecter une transition par simple comparaison de pointeurs,
|
`TWeakObjectPtr` ne permet pas de détecter une transition par simple comparaison de pointeurs,
|
||||||
il faut un booléen d'état à côté.
|
il faut un booléen d'état à côté.
|
||||||
|
|
||||||
|
**Le mode d'input vit sur le viewport, pas sur le controller.** `OpenLevel` détruit le
|
||||||
|
PlayerController mais pas la fenêtre : un controller qui arrive sur une map ne doit jamais
|
||||||
|
supposer l'état laissé par la précédente, il repose son `SetInputMode`. Symptôme quand on
|
||||||
|
l'oublie, en Standalone seulement : la caméra ne répond qu'au clic droit maintenu.
|
||||||
|
|
||||||
|
**Enhanced Input en pause** : un jeu en pause ne fait qu'un tick d'input réduit, où seules les
|
||||||
|
actions cochées **« Trigger When Paused »** sont évaluées. C'est une case sur l'asset `IA_`,
|
||||||
|
pas du C++ — et son corollaire est qu'aucune action du pawn ne se déclenche en pause.
|
||||||
|
|
||||||
## Systèmes en place
|
## Systèmes en place
|
||||||
|
|
||||||
| Système | Fichiers |
|
| Système | Fichiers |
|
||||||
@@ -74,6 +84,10 @@ il faut un booléen d'état à côté.
|
|||||||
| Objets | `ItemDataAsset`, `PickupItem` |
|
| Objets | `ItemDataAsset`, `PickupItem` |
|
||||||
| Inventaire | `InventoryComponent`, `InventoryWidget`, `InventorySlotWidget`, `HotbarWidget`, `InventoryDragDropOperation` |
|
| Inventaire | `InventoryComponent`, `InventoryWidget`, `InventorySlotWidget`, `HotbarWidget`, `InventoryDragDropOperation` |
|
||||||
| Survie | `SurvivalStatsComponent`, `SurvivalStatsWidget` |
|
| Survie | `SurvivalStatsComponent`, `SurvivalStatsWidget` |
|
||||||
|
| Menu principal | `MainMenuGameMode`, `MainMenuPlayerController`, `MainMenuWidget`, `MenuCameraSpot` |
|
||||||
|
| Menu de pause | `PauseMenuWidget` + la partie pause de `FpsPlayerController` |
|
||||||
|
| Réglages | `SurvivalUserSettings`, `SettingsMenuWidget`, `SettingsTabWidget`, `SettingsGameplayWidget`, `SettingRowWidget` |
|
||||||
|
| Fondus, boîtes de dialogue | `ScreenFadeComponent`, `ConfirmDialogWidget` |
|
||||||
|
|
||||||
### Décisions de gameplay figées
|
### Décisions de gameplay figées
|
||||||
|
|
||||||
@@ -93,7 +107,18 @@ il faut un booléen d'état à côté.
|
|||||||
- **Fondus** : un unique voile plein écran `WBP_Fade` au ZOrder 1000. Fondre widget par widget
|
- **Fondus** : un unique voile plein écran `WBP_Fade` au ZOrder 1000. Fondre widget par widget
|
||||||
est fragile — les cases réécrivent leur propre `RenderOpacity`.
|
est fragile — les cases réécrivent leur propre `RenderOpacity`.
|
||||||
- Après un respawn, le pawn est un **nouvel acteur** : les widgets doivent se rebrancher via
|
- Après un respawn, le pawn est un **nouvel acteur** : les widgets doivent se rebrancher via
|
||||||
`BindToOwningPawn()`, appelé depuis `OnPossess`.
|
`BindToOwningPawn()`, appelé depuis `OnPossess`. Même raison côté réglages : le pawn les
|
||||||
|
**relit** à son `BeginPlay` et s'abonne à `OnSurvivalSettingsApplied`.
|
||||||
|
- **Échap** a une priorité explicite dans `HandlePauseInput()` : boîte de confirmation →
|
||||||
|
réglages → inventaire → pause. Sans cet ordre, deux écrans se retrouvent ouverts en même temps.
|
||||||
|
- **Menu de pause** : `SetPause(true)` fige le monde, mais **pas Slate** — les animations UMG et
|
||||||
|
`ScreenFadeComponent` continuent (d'où son `bTickEvenWhenPaused`). Pas de bouton « Quitter le
|
||||||
|
jeu » : la sortie se fait depuis le menu principal.
|
||||||
|
- **Réglages** : `USurvivalUserSettings` fait autorité, jamais le CDO du pawn. Application
|
||||||
|
immédiate, aucun bouton « Appliquer » ; la sauvegarde a lieu à la fermeture de l'écran.
|
||||||
|
L'écran ignore qui l'ouvre (`OnCloseRequested`), d'où un seul `WBP_Settings` pour le menu
|
||||||
|
principal et la pause. Une ligne = un index dans une liste de paliers, y compris les nombres :
|
||||||
|
d'où un seul `WBP_SettingRow` pour tout le menu.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
@@ -108,9 +133,18 @@ il faut un booléen d'état à côté.
|
|||||||
## État actuel et suite
|
## État actuel et suite
|
||||||
|
|
||||||
Boucle complète : récolter → ranger → consommer → survivre → mourir → réapparaître.
|
Boucle complète : récolter → ranger → consommer → survivre → mourir → réapparaître.
|
||||||
|
Autour : menu principal, menu de pause, et un écran de réglages dont **seul l'onglet
|
||||||
|
« Jeu & Contrôles » est rempli**.
|
||||||
|
|
||||||
Prochaine étape recommandée : **spawner de ressources avec repousse** (`AResourceSpawner`,
|
Suite immédiate sur les réglages, dans l'ordre : onglet **Affichage** (le moteur fait tout,
|
||||||
liste pondérée de DataAssets, rayon, densité, délai). Sans lui la carte se vide en dix minutes.
|
prévoir la confirmation à rebours de 10 s sur la résolution), onglet **Graphismes** (les dix
|
||||||
|
curseurs de `UGameUserSettings` ; végétation et distance d'affichage comptent double avec
|
||||||
|
SoStylized), onglet **Audio** quand des `SoundClass` existeront, et enfin le **remappage des
|
||||||
|
touches** via `UEnhancedInputUserSettings` — le seul morceau réellement coûteux.
|
||||||
|
|
||||||
|
Prochaine étape gameplay recommandée : **spawner de ressources avec repousse**
|
||||||
|
(`AResourceSpawner`, liste pondérée de DataAssets, rayon, densité, délai). Sans lui la carte se
|
||||||
|
vide en dix minutes.
|
||||||
|
|
||||||
Ensuite, par ordre de valeur : craft, objet visible en main, coffres, sauvegarde,
|
Ensuite, par ordre de valeur : craft, objet visible en main, coffres, sauvegarde,
|
||||||
cycle jour/nuit.
|
cycle jour/nuit.
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ FontDPI=72
|
|||||||
[/Script/Engine.Engine]
|
[/Script/Engine.Engine]
|
||||||
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/Survival_projet")
|
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/Survival_projet")
|
||||||
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/Survival_projet")
|
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/Survival_projet")
|
||||||
|
; Sans cette ligne le moteur instancie UGameUserSettings et nos reglages de jeu
|
||||||
|
; n'existent tout simplement pas au runtime.
|
||||||
|
GameUserSettingsClassName=/Script/Survival_projet.SurvivalUserSettings
|
||||||
|
|
||||||
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
||||||
bEnablePlugin=True
|
bEnablePlugin=True
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,6 +17,7 @@
|
|||||||
#include "ItemDataAsset.h"
|
#include "ItemDataAsset.h"
|
||||||
#include "PickupItem.h"
|
#include "PickupItem.h"
|
||||||
#include "SurvivalStatsComponent.h"
|
#include "SurvivalStatsComponent.h"
|
||||||
|
#include "SurvivalUserSettings.h"
|
||||||
|
|
||||||
// Sets default values
|
// Sets default values
|
||||||
AFpsPlayer::AFpsPlayer()
|
AFpsPlayer::AFpsPlayer()
|
||||||
@@ -72,6 +73,21 @@ void AFpsPlayer::BeginPlay()
|
|||||||
|
|
||||||
SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);
|
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
|
// On enregistre le mapping context aupres du sous-systeme Enhanced Input
|
||||||
// du joueur local. Sans ca, aucune touche n'est lue.
|
// du joueur local. Sans ca, aucune touche n'est lue.
|
||||||
if (const APlayerController* PlayerController = Cast<APlayerController>(GetController()))
|
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
|
// Called every frame
|
||||||
void AFpsPlayer::Tick(float DeltaTime)
|
void AFpsPlayer::Tick(float DeltaTime)
|
||||||
{
|
{
|
||||||
@@ -193,9 +248,12 @@ void AFpsPlayer::UpdateCameraEffects(float DeltaTime)
|
|||||||
|
|
||||||
void AFpsPlayer::AddCameraPunch(float Strength)
|
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
|
// Mis en attente plutot qu'applique directement : c'est UpdateCameraEffects
|
||||||
// qui l'injecte progressivement dans le ressort.
|
// qui l'injecte progressivement dans le ressort.
|
||||||
PendingPunch += Strength;
|
PendingPunch += Strength * CameraShakeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AFpsPlayer::Landed(const FHitResult& Hit)
|
void AFpsPlayer::Landed(const FHitResult& Hit)
|
||||||
@@ -263,14 +321,17 @@ void AFpsPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent
|
|||||||
|
|
||||||
if (SprintAction)
|
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::Started, this, &AFpsPlayer::StartSprint);
|
||||||
Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::StopSprint);
|
Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::HandleSprintReleased);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CrouchAction)
|
if (CrouchAction)
|
||||||
{
|
{
|
||||||
Input->BindAction(CrouchAction, ETriggerEvent::Started, this, &AFpsPlayer::StartCrouch);
|
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)
|
if (InteractAction)
|
||||||
@@ -330,6 +391,13 @@ void AFpsPlayer::StartSprint()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// En mode bascule, la meme touche coupe le sprint.
|
||||||
|
if (bSprintToggleMode && bIsSprinting)
|
||||||
|
{
|
||||||
|
StopSprint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bIsSprinting = true;
|
bIsSprinting = true;
|
||||||
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
|
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
|
||||||
}
|
}
|
||||||
@@ -340,6 +408,17 @@ void AFpsPlayer::StopSprint()
|
|||||||
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
|
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFpsPlayer::HandleSprintReleased()
|
||||||
|
{
|
||||||
|
// En bascule, relacher la touche ne doit rien faire.
|
||||||
|
if (bSprintToggleMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StopSprint();
|
||||||
|
}
|
||||||
|
|
||||||
void AFpsPlayer::UseSelectedItem()
|
void AFpsPlayer::UseSelectedItem()
|
||||||
{
|
{
|
||||||
const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
|
const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
|
||||||
@@ -514,10 +593,27 @@ void AFpsPlayer::ClearTransientInputStates()
|
|||||||
|
|
||||||
void AFpsPlayer::StartCrouch()
|
void AFpsPlayer::StartCrouch()
|
||||||
{
|
{
|
||||||
|
// En mode bascule, la meme touche releve le personnage.
|
||||||
|
if (bCrouchToggleMode && bIsCrouched)
|
||||||
|
{
|
||||||
|
StopCrouch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
StopSprint();
|
StopSprint();
|
||||||
Crouch();
|
Crouch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AFpsPlayer::HandleCrouchReleased()
|
||||||
|
{
|
||||||
|
if (bCrouchToggleMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StopCrouch();
|
||||||
|
}
|
||||||
|
|
||||||
void AFpsPlayer::StopCrouch()
|
void AFpsPlayer::StopCrouch()
|
||||||
{
|
{
|
||||||
// UnCrouch() echoue silencieusement s'il y a un plafond : le perso
|
// UnCrouch() echoue silencieusement s'il y a un plafond : le perso
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "FpsPlayerController.h"
|
#include "FpsPlayerController.h"
|
||||||
|
|
||||||
#include "Blueprint/UserWidget.h"
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "ConfirmDialogWidget.h"
|
||||||
#include "EnhancedInputComponent.h"
|
#include "EnhancedInputComponent.h"
|
||||||
#include "FpsPlayer.h"
|
#include "FpsPlayer.h"
|
||||||
#include "GameFramework/GameModeBase.h"
|
#include "GameFramework/GameModeBase.h"
|
||||||
@@ -14,9 +15,14 @@
|
|||||||
#include "InventoryComponent.h"
|
#include "InventoryComponent.h"
|
||||||
#include "InteractionPromptWidget.h"
|
#include "InteractionPromptWidget.h"
|
||||||
#include "InventoryWidget.h"
|
#include "InventoryWidget.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "PauseMenuWidget.h"
|
||||||
#include "ScreenFadeComponent.h"
|
#include "ScreenFadeComponent.h"
|
||||||
|
#include "SettingsMenuWidget.h"
|
||||||
#include "SurvivalStatsWidget.h"
|
#include "SurvivalStatsWidget.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "PauseMenu"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Ordre d'empilement des widgets plein ecran, du fond vers l'avant.
|
// Ordre d'empilement des widgets plein ecran, du fond vers l'avant.
|
||||||
@@ -35,6 +41,12 @@ namespace
|
|||||||
constexpr int32 ZOrderInteractionPrompt = 10;
|
constexpr int32 ZOrderInteractionPrompt = 10;
|
||||||
constexpr int32 ZOrderInventory = 20;
|
constexpr int32 ZOrderInventory = 20;
|
||||||
constexpr int32 ZOrderHotbar = 30;
|
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()
|
AFpsPlayerController::AFpsPlayerController()
|
||||||
@@ -45,12 +57,29 @@ AFpsPlayerController::AFpsPlayerController()
|
|||||||
// Rappel Unreal : ce constructeur tourne aussi dans l'editeur, sur le CDO.
|
// 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.
|
// Il ne doit contenir que de la construction, jamais de logique de jeu.
|
||||||
ScreenFade = CreateDefaultSubobject<UScreenFadeComponent>(TEXT("ScreenFade"));
|
ScreenFade = CreateDefaultSubobject<UScreenFadeComponent>(TEXT("ScreenFade"));
|
||||||
|
|
||||||
|
QuitToMenuMessage = LOCTEXT("QuitToMenuConfirm", "Retourner au menu principal ?\nTa progression sera perdue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AFpsPlayerController::BeginPlay()
|
void AFpsPlayerController::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::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)
|
if (bFadeInOnStart)
|
||||||
{
|
{
|
||||||
ScreenFade->FadeIn(StartupFadeDuration);
|
ScreenFade->FadeIn(StartupFadeDuration);
|
||||||
@@ -157,6 +186,15 @@ void AFpsPlayerController::SetupInputComponent()
|
|||||||
{
|
{
|
||||||
Input->BindAction(HotbarScrollAction, ETriggerEvent::Triggered, this, &AFpsPlayerController::HandleHotbarScroll);
|
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
|
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()
|
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)
|
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)
|
if (InventoryWidget)
|
||||||
{
|
{
|
||||||
InventoryWidget->RemoveFromParent();
|
InventoryWidget->RemoveFromParent();
|
||||||
@@ -392,3 +717,5 @@ void AFpsPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
|
|
||||||
Super::EndPlay(EndPlayReason);
|
Super::EndPlay(EndPlayReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "MainMenuWidget.h"
|
#include "MainMenuWidget.h"
|
||||||
#include "MenuCameraSpot.h"
|
#include "MenuCameraSpot.h"
|
||||||
#include "ScreenFadeComponent.h"
|
#include "ScreenFadeComponent.h"
|
||||||
|
#include "SettingsMenuWidget.h"
|
||||||
#include "SurvivalGameInstance.h"
|
#include "SurvivalGameInstance.h"
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "MainMenu"
|
#define LOCTEXT_NAMESPACE "MainMenu"
|
||||||
@@ -19,7 +20,9 @@ namespace
|
|||||||
// Le menu reste sous le voile de fondu, qui est en 1000.
|
// Le menu reste sous le voile de fondu, qui est en 1000.
|
||||||
constexpr int32 ZOrderMainMenu = 0;
|
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;
|
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()
|
void AMainMenuPlayerController::HandleQuitConfirmed()
|
||||||
{
|
{
|
||||||
// Pas de fondu : le joueur vient de confirmer, le faire patienter une
|
// 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)
|
void AMainMenuPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
{
|
{
|
||||||
|
if (SettingsMenu)
|
||||||
|
{
|
||||||
|
SettingsMenu->RemoveFromParent();
|
||||||
|
SettingsMenu = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (QuitDialog)
|
if (QuitDialog)
|
||||||
{
|
{
|
||||||
QuitDialog->RemoveFromParent();
|
QuitDialog->RemoveFromParent();
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ void UMainMenuWidget::NativeConstruct()
|
|||||||
QuitButton->OnClicked.AddDynamic(this, &UMainMenuWidget::HandleQuitClicked);
|
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
|
// Les boutons de coop sont visibles mais inertes : ils annoncent la couleur
|
||||||
// sans promettre une partie qui echouerait.
|
// sans promettre une partie qui echouerait.
|
||||||
if (HostButton)
|
if (HostButton)
|
||||||
@@ -48,3 +53,11 @@ void UMainMenuWidget::HandleQuitClicked()
|
|||||||
Controller->QuitGame();
|
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.
|
// pour une valeur qui ne bouge qu'une seconde par mort serait du gaspillage.
|
||||||
PrimaryComponentTick.bCanEverTick = true;
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
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()
|
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")
|
UFUNCTION(BlueprintCallable, Category = "Camera")
|
||||||
void AddCameraPunch(float Strength);
|
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:
|
protected:
|
||||||
// Called when the game starts or when spawned
|
// Called when the game starts or when spawned
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
|
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
|
||||||
virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
|
virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
|
||||||
virtual void OnEndCrouch(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.
|
* 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.
|
* 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"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
|
||||||
float LookSensitivityX = 0.4f;
|
float LookSensitivityX = 0.4f;
|
||||||
@@ -344,6 +361,16 @@ protected:
|
|||||||
void StopCrouch();
|
void StopCrouch();
|
||||||
void Interact();
|
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.
|
* Clic gauche : agit avec l'objet actif de la barre rapide.
|
||||||
* Aujourd'hui seuls les consommables font quelque chose ; c'est ici que
|
* 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. */
|
/** Évite de spammer le log si IA_Look est mal configurée. */
|
||||||
bool bLoggedLookTypeWarning = false;
|
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 "GameFramework/PlayerController.h"
|
||||||
#include "FpsPlayerController.generated.h"
|
#include "FpsPlayerController.generated.h"
|
||||||
|
|
||||||
|
class UConfirmDialogWidget;
|
||||||
class UHotbarWidget;
|
class UHotbarWidget;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UInventoryComponent;
|
class UInventoryComponent;
|
||||||
class UInteractionPromptWidget;
|
class UInteractionPromptWidget;
|
||||||
class UInventoryWidget;
|
class UInventoryWidget;
|
||||||
|
class UPauseMenuWidget;
|
||||||
class UScreenFadeComponent;
|
class UScreenFadeComponent;
|
||||||
|
class USettingsMenuWidget;
|
||||||
class USurvivalStatsWidget;
|
class USurvivalStatsWidget;
|
||||||
class UUserWidget;
|
class UUserWidget;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
@@ -36,6 +39,21 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category = "UI")
|
UFUNCTION(BlueprintPure, Category = "UI")
|
||||||
bool IsInventoryOpen() const { return bInventoryOpen; }
|
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:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
virtual void OnPossess(APawn* InPawn) override;
|
virtual void OnPossess(APawn* InPawn) override;
|
||||||
@@ -105,6 +123,17 @@ protected:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||||
TObjectPtr<UInputAction> HotbarScrollAction;
|
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
|
* 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
|
* met en Collapsed des qu'on ne vise plus rien, et emporterait le viseur
|
||||||
@@ -129,6 +158,42 @@ protected:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
|
||||||
TSubclassOf<USurvivalStatsWidget> SurvivalStatsClass;
|
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")
|
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
|
||||||
TObjectPtr<UUserWidget> Crosshair;
|
TObjectPtr<UUserWidget> Crosshair;
|
||||||
|
|
||||||
@@ -144,9 +209,43 @@ protected:
|
|||||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
|
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
|
||||||
TObjectPtr<USurvivalStatsWidget> SurvivalStatsWidget;
|
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:
|
private:
|
||||||
void CreateHudWidgets();
|
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. */
|
/** Jette la pile survolee dans la grille, si l'inventaire est ouvert. */
|
||||||
void DropHoveredItem();
|
void DropHoveredItem();
|
||||||
|
|
||||||
@@ -166,4 +265,18 @@ private:
|
|||||||
FTimerHandle RespawnTimerHandle;
|
FTimerHandle RespawnTimerHandle;
|
||||||
|
|
||||||
bool bInventoryOpen = false;
|
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 UConfirmDialogWidget;
|
||||||
class UMainMenuWidget;
|
class UMainMenuWidget;
|
||||||
class UScreenFadeComponent;
|
class UScreenFadeComponent;
|
||||||
|
class USettingsMenuWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ce qu'on fait une fois l'ecran devenu noir.
|
* Ce qu'on fait une fois l'ecran devenu noir.
|
||||||
@@ -45,6 +46,10 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Menu")
|
UFUNCTION(BlueprintCallable, Category = "Menu")
|
||||||
void QuitGame();
|
void QuitGame();
|
||||||
|
|
||||||
|
/** Ouvre l'ecran de reglages par-dessus le menu. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Menu")
|
||||||
|
void OpenSettings();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
@@ -75,6 +80,10 @@ protected:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
|
||||||
FText QuitDialogMessage;
|
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. */
|
/** Fondu d'arrivee sur le menu, au lancement du jeu. */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
|
||||||
float OpeningFadeDuration = 1.5f;
|
float OpeningFadeDuration = 1.5f;
|
||||||
@@ -93,6 +102,9 @@ protected:
|
|||||||
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
|
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
|
||||||
TObjectPtr<UConfirmDialogWidget> QuitDialog;
|
TObjectPtr<UConfirmDialogWidget> QuitDialog;
|
||||||
|
|
||||||
|
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
|
||||||
|
TObjectPtr<USettingsMenuWidget> SettingsMenu;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** Tire un angle de vue au hasard, en evitant celui du dernier passage. */
|
/** Tire un angle de vue au hasard, en evitant celui du dernier passage. */
|
||||||
AMenuCameraSpot* PickCameraSpot();
|
AMenuCameraSpot* PickCameraSpot();
|
||||||
@@ -109,6 +121,10 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleQuitCancelled();
|
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. */
|
/** Ferme le jeu sans rien demander. */
|
||||||
void CloseGame();
|
void CloseGame();
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ protected:
|
|||||||
UPROPERTY(meta = (BindWidget))
|
UPROPERTY(meta = (BindWidget))
|
||||||
TObjectPtr<UButton> QuitButton;
|
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
|
* Coop a venir. BindWidgetOptional et non BindWidget : tu peux dessiner ton
|
||||||
* WBP_MainMenu sans eux aujourd'hui, le Blueprint compilera quand meme.
|
* WBP_MainMenu sans eux aujourd'hui, le Blueprint compilera quand meme.
|
||||||
@@ -52,4 +56,7 @@ private:
|
|||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleQuitClicked();
|
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()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
virtual void Init() override;
|
||||||
|
|
||||||
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
|
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
|
||||||
UFUNCTION(BlueprintPure, Category = "Menu")
|
UFUNCTION(BlueprintPure, Category = "Menu")
|
||||||
FName GetLastMenuCameraName() const { return LastMenuCameraName; }
|
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