(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
@@ -121,7 +121,21 @@ public:
protected:
virtual void BeginPlay() override;
/** Serveur uniquement. Chez un client, c'est AcknowledgePossession qui arrive. */
virtual void OnPossess(APawn* InPawn) override;
/**
* Le pendant CLIENT de OnPossess.
*
* OnPossess n'existe QUE sur le serveur : chez un client le pawn arrive par
* replication et cette fonction n'est jamais appelee. Comme c'est elle qui
* construisait le HUD, un joueur qui se connectait n'avait aucune interface --
* ni viseur, ni barre rapide, ni jauges. Le piege est d'autant plus fourbe
* que l'hote, lui, est bien le serveur et voit son HUD parfaitement.
*/
virtual void AcknowledgePossession(APawn* P) override;
virtual void SetupInputComponent() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
@@ -141,6 +155,16 @@ protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu")
bool bFadeInOnStart = true;
/**
* Délai maximal d'attente du pawn avant d'éclaircir quand même.
*
* Filet de sécurité pur : si la possession n'arrivait jamais, l'écran
* resterait noir pour toujours et le joueur croirait le jeu planté. Mieux
* vaut une vue bancale qu'un écran mort.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "1", Units = "s"))
float StartupFadeTimeout = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
float StartupFadeDuration = 1.5f;
@@ -307,6 +331,45 @@ protected:
private:
void CreateHudWidgets();
/**
* Tout ce qu'il faut faire quand ce controller prend un pawn, quel que soit
* le cote du reseau : creer le HUD s'il manque, rebrancher les widgets sur
* le nouveau corps, ecouter sa mort.
*
* Appelee par OnPossess (serveur) ET AcknowledgePossession (client), donc
* potentiellement deux fois chez l'hote qui est les deux a la fois. Elle
* doit rester idempotente -- d'ou AddUniqueDynamic sur OnDied, et
* CreateHudWidgets qui ne cree que ce qui n'existe pas encore.
*/
void SetupForPossessedPawn(APawn* InPawn);
/**
* Éclaircit l'écran d'arrivée, une seule fois, et seulement quand le joueur
* a réellement un corps.
*
* La condition n'est PAS « ai-je un pawn » mais « la caméra est-elle sur mon
* pawn ». Chez un client, le contrôleur commence sur un `SpectatorPawn` né
* au centre du monde -- sous le terrain -- et y reste encore un instant
* APRÈS `AcknowledgePossession`. Mesuré sur une vraie connexion : au moment
* où la possession est acquittée, `GetViewTarget()` vaut toujours
* `SpectatorPawn_0`. Éclaircir là montre le dessous de la carte.
*
* Tant que la caméra n'y est pas, on repasse toutes les `StartupFadeRetry`
* secondes. L'écran reste noir pendant ce temps -- le rendu du monde est
* suspendu par le GameInstance depuis le chargement de la map.
*/
void TryStartupFadeIn();
/**
* Éclaircit pour de bon, sans vérifier la caméra.
* Appelée par le filet de sécurité : mieux vaut une vue bancale qu'un écran
* noir définitif, qui se lit comme un plantage.
*/
void CompleteStartupFadeIn();
/** L'éclaircissement lui-même, après le temps de chauffe du monde. */
void StartStartupFade();
/**
* Echap. Une seule touche pour trois usages, d'ou une priorite explicite :
* boite de confirmation, puis inventaire, puis pause. Sans cet ordre on se
@@ -320,6 +383,37 @@ private:
UFUNCTION()
void HandleQuitToMenuConfirmed();
/**
* L'hote s'en va : ce client doit partir aussi.
*
* Une RPC explicite et non un simple abandon de connexion. Quand l'hote
* ferme sa partie, le moteur ne previent personne : les clients restent
* dans un monde mort, avec le pawn fantome de l'hote encore plante devant
* eux, jusqu'a un timeout qui peut ne jamais venir. On leur dit donc de
* partir, et ils partent avec leur propre fondu.
*/
UFUNCTION(Client, Reliable)
void Client_ReturnToMainMenu();
/**
* Lance le depart : ferme les ecrans, previent le GameInstance, fond au noir.
*
* Idempotente -- un client peut recevoir l'ordre de l'hote alors qu'il avait
* deja clique Quitter de son cote, et deux fondus simultanes se
* marcheraient dessus.
*/
void BeginQuitToMainMenu();
/**
* Envoie l'ordre de depart a tous les AUTRES joueurs. Serveur uniquement.
*
* Appele AVANT le fondu de l'hote et non apres : les clients ont ainsi
* toute la duree de ce fondu pour recevoir le message et lancer le leur.
* Prevenir a la fin donnerait un client qui decouvre la coupure sans
* explication, ecran de jeu fige.
*/
void KickRemoteClientsToMainMenu();
/** Non : on referme la boite et on rend la main au menu de pause. */
UFUNCTION()
void HandleQuitToMenuCancelled();
@@ -328,6 +422,17 @@ private:
UFUNCTION()
void HandleFadeFinished();
/**
* Le joueur a accepte une invitation EN PLEINE PARTIE : on fond au noir
* avant que le voyage vers l'autre hote parte.
*
* bQuittingToMainMenu reste faux : ce n'est pas nous qui chargeons la map
* suivante, c'est USessionSubsystem. HandleFadeFinished doit donc laisser
* passer ce fondu-la sans rien faire.
*/
UFUNCTION()
void HandleSessionTravelPending();
/** L'ecran de reglages demande a se fermer : on rend la main au menu de pause. */
UFUNCTION()
void HandleSettingsCloseRequested();
@@ -469,6 +574,18 @@ private:
FTimerHandle RespawnTimerHandle;
/** Le fondu d'arrivée attend le pawn. */
bool bStartupFadePending = false;
FTimerHandle StartupFadeTimeoutHandle;
/** Réessai tant que la caméra n'est pas sur le pawn. */
FTimerHandle StartupFadeRetryHandle;
/** Période de réessai, en secondes. Assez court pour ne pas se voir. */
static constexpr float StartupFadeRetry = 0.05f;
/** Surveillance de l'eloignement, actif seulement pendant qu'un coffre est ouvert. */
FTimerHandle StorageDistanceTimerHandle;