Files
Unreal_EmberWild/Source/Survival_projet/Public/SurvivalGameInstance.h
T
2026-08-06 13:27:21 +02:00

256 lines
10 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "SurvivalGameInstance.generated.h"
class UInputMappingContext;
class ULocalPlayer;
/**
* Le seul objet qui survit aux changements de map.
*
* C'est l'equivalent le plus proche d'un singleton DontDestroyOnLoad d'Unity :
* il naît au lancement du jeu et meurt a sa fermeture. Tout le reste --
* GameMode, GameState, PlayerController, pawns, widgets -- est detruit et
* recree a chaque OpenLevel.
*
* Y mettre : les reglages, la sauvegarde en cours, et plus tard l'hebergement
* et le rejoindre-une-partie. Surtout pas d'etat de gameplay, qui appartient au
* monde et doit disparaitre avec lui.
*/
UCLASS()
class SURVIVAL_PROJET_API USurvivalGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
virtual void Shutdown() override;
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
UFUNCTION(BlueprintPure, Category = "Menu")
FName GetLastMenuCameraName() const { return LastMenuCameraName; }
UFUNCTION(BlueprintCallable, Category = "Menu")
void SetLastMenuCameraName(FName SpotName) { LastMenuCameraName = SpotName; }
/**
* Declare les IMC remappables aupres des reglages d'input du joueur.
*
* Appelee A LA DEMANDE, par l'ecran de reglages avant de lire les touches, et
* non depuis un evenement de demarrage. Raison : UserSettings n'est cree que
* dans UEnhancedInputLocalPlayerSubsystem::PlayerControllerChanged, et
* ULocalPlayer diffuse ses evenements AVANT de prevenir ses sous-systemes --
* tout hook precoce arrive donc trop tot, quel qu'il soit.
*
* Idempotente : le moteur ignore un contexte deja connu.
*/
UFUNCTION(BlueprintCallable, Category = "Input")
void RegisterMappableContexts();
/**
* Traduit les touches par defaut vers la disposition clavier du joueur.
*
* Les IMC sont ecrits en QWERTY US -- EKeys::W pour avancer -- et Windows livre
* au moteur une touche DEJA traduite par la disposition : en AZERTY, la touche
* du haut du bloc de deplacement arrive comme EKeys::Z et le mapping "W" ne se
* declenche jamais. On repose donc chaque touche a la position equivalente.
* Le detail du mecanisme est dans KeyboardLayout.h.
*
* Ne touche QUE les mappings que le joueur n'a pas remappes lui-meme : un
* choix explicite l'emporte toujours sur une deduction. Et sur un clavier
* QWERTY la conversion rend la touche d'origine, donc rien n'est ecrit du tout
* et le profil reste vierge.
*
* Declenchee UNE FOIS par RegisterMappableContexts(), au premier enregistrement
* des contextes ; ensuite seul "Reinitialiser" la redemande. Elle appartient a
* l'initialisation, pas au rafraichissement d'ecran -- l'onglet Touches se
* rafraichit apres chaque remappage, et repasser dessus reprendrait sa touche
* au joueur qui aurait justement choisi celle de l'IMC (la lettre W sur un
* AZERTY, par exemple : identique au defaut, donc indiscernable d'un mapping
* jamais touche).
*/
UFUNCTION(BlueprintCallable, Category = "Input")
void ApplyKeyboardLayoutToDefaultKeys();
/**
* Un depart VOULU vers le menu commence : le filet de secours reseau doit
* se taire.
*
* Sans ce drapeau, la connexion qui tombe pendant le fondu de sortie
* declencherait HandleNetworkFailure, qui chargerait le menu sur-le-champ
* et couperait le fondu en plein milieu -- exactement le saut d'image qu'on
* cherchait a eviter en fondant.
*/
void BeginReturnToMainMenu() { bReturningToMainMenu = true; }
/** On est arrive au menu : le filet redevient actif pour la partie suivante. */
void ClearReturnToMainMenu() { bReturningToMainMenu = false; }
/**
* Rend la main au rendu du monde, coupe depuis le chargement de la map.
*
* A n'appeler QUE lorsque le voile de fondu est deja noir : le monde
* reapparait alors derriere lui, invisible.
*
* Pourquoi couper le rendu plutot que poser un widget. Mesure sur une vraie
* connexion : 2,4 secondes s'ecoulent entre `LoadMap` chez le client et la
* naissance de son PlayerController, que le serveur lui envoie. Pendant ce
* temps la map est chargee et le moteur la rend depuis une vue par defaut --
* sous le terrain. Il n'existe alors AUCUN acteur a qui accrocher un voile,
* et le composant de fondu vit justement sur ce controller absent. Le
* viewport, lui, survit aux changements de map et n'appartient a personne.
*
* Attend aussi la fin du streaming avant de rendre la main : c'est le seul
* instant du chargement ou l'on sait que rien ne sera montre tant qu'on n'a
* pas fini. Sans ca, le joueur voit le terrain passer de sa version
* grossiere a la vraie, et les modeles apparaitre un a un.
*/
UFUNCTION(BlueprintCallable, Category = "Chargement")
void ResumeWorldRendering();
/**
* Laisse le monde rendre SceneWarmupFrames images derriere le voile noir,
* puis rappelle OnComplete.
*
* Compte des IMAGES et non des secondes, et c'est tout l'interet. Ce qui
* reste a se raffiner apres le flush de streaming -- Nanite, les ombres
* virtuelles, la convergence de Lumen -- progresse a chaque image RENDUE,
* pas avec le temps qui passe. Une duree fixe donnerait quarante images sur
* une machine rapide et cinq sur une machine lente : exactement celle qui en
* aurait eu le plus besoin.
*/
void BeginSceneWarmup(FSimpleDelegate OnComplete);
protected:
/**
* Temps maximal accordé au streaming avant de montrer le monde, en secondes.
*
* Ce blocage se produit ECRAN NOIR, donc il ne se voit pas -- c'est du temps
* de chargement, pas une saccade. Le dépasser vaut mieux que de montrer un
* terrain qui se raffine sous les yeux du joueur, mais on le borne quand
* même : une machine lente ne doit pas rester bloquée indéfiniment.
*
* Réglable sur BP_SurvivalGameInstance.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0", Units = "s"))
float StreamingFlushTimeLimit = 5.f;
/**
* Nombre d'images rendues derriere le voile noir avant de l'eclaircir.
* Voir BeginSceneWarmup(). Reglable sur BP_SurvivalGameInstance.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0"))
int32 SceneWarmupFrames = 45;
/**
* Plafond de la chauffe, en secondes.
*
* Le compte d'images reste la mesure PRINCIPALE -- c'est la bonne unite.
* Ceci n'est qu'un garde-fou : sur une machine tres lente, 45 images
* pourraient prendre plusieurs secondes d'ecran noir, et mieux vaut un
* decor pas tout a fait converge qu'une attente qui inquiete.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0.1", Units = "s"))
float SceneWarmupMaxSeconds = 2.f;
/**
* Les mapping contexts dont les touches sont remappables. Mets-y IMC_Default.
*
* Ici et PAS sur le pawn : le menu principal n'a aucun pawn, et l'ecran de
* reglages doit pourtant afficher les touches. C'est le critere habituel --
* "est-ce que ca doit survivre au changement de map ?" -- qui envoie cette
* liste sur le GameInstance.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TArray<TObjectPtr<UInputMappingContext>> MappableContexts;
private:
/**
* L'hote a quitte, ou la connexion est tombee.
*
* Sans ce filet, un client dont l'hote ferme le jeu reste planté dans un
* monde qui ne repond plus : le moteur coupe la connexion, mais rien ne le
* ramene nulle part. C'est ici et pas sur le PlayerController parce que
* celui-ci est justement detruit par l'echec -- le GameInstance est le seul
* objet dont on soit certain qu'il vive encore a cet instant.
*/
void HandleNetworkFailure(UWorld* World, UNetDriver* NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString);
/** Meme filet pour un voyage qui echoue en route. */
void HandleTravelFailure(UWorld* World, ETravelFailure::Type FailureType, const FString& ErrorString);
/**
* Ferme la session en cours et rouvre le menu principal.
*
* La map du menu n'est PAS une propriete de plus : c'est deja la
* GameDefaultMap du projet. En dupliquer le chemin ici donnerait deux
* valeurs a tenir en phase, et celle-ci ne serait jamais testee.
*/
void ReturnToMainMenu();
/**
* Une map va etre chargee : on coupe le rendu jusqu'a ce qu'un controller
* soit pret.
*
* PreLoadMap et surtout PAS PostLoadMapWithWorld : ce dernier est diffuse
* APRES le BeginPlay des acteurs. Sur une map chargee localement -- le menu,
* ou la partie chez l'hote -- le controller rendait donc la main au rendu,
* et on la lui reprenait juste apres. Ecran noir definitif, jusqu'au filet
* de securite. Chez un client ca passait par accident, son controller
* arrivant plus tard.
*/
void HandlePreLoadMap(const FString& MapName);
void SetWorldRenderingSuspended(bool bSuspended);
/** Une image de chauffe vient de passer : on decompte, ou on rappelle. */
void TickSceneWarmup();
/** Images restantes a rendre avant d'eclaircir. */
int32 RemainingWarmupFrames = 0;
/** Debut de la chauffe, pour appliquer le plafond en secondes. */
double WarmupStartTime = 0.0;
/** Ce qu'on fait une fois la chauffe finie -- eclaircir, cote appelant. */
FSimpleDelegate SceneWarmupCompleted;
FDelegateHandle NetworkFailureHandle;
FDelegateHandle TravelFailureHandle;
FDelegateHandle PreLoadMapHandle;
/**
* Filet : si aucun controller ne rend la main -- connexion qui n'aboutit
* pas, spawn rate -- on rallume quand meme. Un ecran noir definitif se lit
* comme un plantage, et c'est pire que le defaut qu'on corrige.
*/
FTimerHandle RenderingResumeTimeout;
/** Un retour au menu est deja en cours, voulu ou non. */
bool bReturningToMainMenu = false;
/**
* La disposition clavier a deja ete appliquee dans cette session.
*
* Pas de UPROPERTY(config) : ce n'est PAS "l'a-t-on deja fait un jour". Les
* surcharges posees, elles, sont persistees par Enhanced Input, et elles
* suffisent a rendre l'operation sans effet au lancement suivant. Ce drapeau
* ne protege que du re-declenchement en cours de session.
*/
bool bKeyboardLayoutApplied = false;
/**
* Stocke un NOM et pas un index : les index changent des qu'on ajoute ou
* supprime une camera dans la map, et on retomberait sur autre chose.
*
* Ne sert qu'a eviter de retirer deux fois de suite le meme angle quand le
* joueur revient au menu -- d'ou le Transient, ca n'a pas a etre sauvegarde.
*/
UPROPERTY(Transient)
FName LastMenuCameraName = NAME_None;
};