(Feat) Fix Fade Loading

This commit is contained in:
2026-08-03 21:10:45 +02:00
parent 7595df6862
commit 1456ed91c7
11 changed files with 925 additions and 28 deletions
@@ -28,6 +28,7 @@ class SURVIVAL_PROJET_API USurvivalGameInstance : public UGameInstance
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")
@@ -50,7 +51,87 @@ public:
UFUNCTION(BlueprintCallable, Category = "Input")
void RegisterMappableContexts();
/**
* 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.
*
@@ -63,6 +144,70 @@ protected:
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;
/**
* 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.