(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
@@ -8,6 +8,7 @@
#include "EnhancedInputComponent.h"
#include "FpsPlayer.h"
#include "GameFramework/GameModeBase.h"
#include "SurvivalGameInstance.h"
#include "SurvivalStatsComponent.h"
#include "HotbarWidget.h"
#include "InputActionValue.h"
@@ -83,6 +84,7 @@ void AFpsPlayerController::BeginPlay()
{
Super::BeginPlay();
// Le mode d'input vit sur le VIEWPORT, pas sur le controller. OpenLevel
// detruit le controller du menu principal mais pas la fenetre : son
// GameAndUI et son curseur visible restent en place, et la camera ne
@@ -115,16 +117,135 @@ void AFpsPlayerController::BeginPlay()
// bQuittingToMainMenu qui distingue le fondu de sortie du reste.
ScreenFade->OnFadeFinished.AddDynamic(this, &AFpsPlayerController::HandleFadeFinished);
if (bFadeInOnStart)
// Une invitation peut tomber en pleine partie : sans ce fondu, l'ecran
// sauterait directement dans le monde de l'autre hote.
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
{
ScreenFade->FadeIn(StartupFadeDuration);
Sessions->OnTravelPending.AddDynamic(this, &AFpsPlayerController::HandleSessionTravelPending);
}
// IsLocalController est INDISPENSABLE ici : sur la machine de l'hote, chaque
// joueur distant a AUSSI un controller cote serveur. Sans cette garde, le
// controller d'un ami qui se connecte irait poser un fondu et rallumer le
// rendu du viewport de l'HOTE, a un moment totalement arbitraire.
if (bFadeInOnStart && IsLocalController())
{
// On NOIRCIT et on attend que la camera soit sur notre corps, au lieu
// d'eclaircir tout de suite. Un client demarre sur un SpectatorPawn ne
// au centre du monde -- sous le terrain -- et y reste encore un instant
// apres l'acquittement de la possession. C'est TryStartupFadeIn qui
// decidera du bon moment.
ScreenFade->SnapToBlack();
bStartupFadePending = true;
// Filet : si la possession n'arrivait jamais, l'ecran resterait noir a
// vie et le joueur croirait le jeu plante.
// Le filet appelle CompleteStartupFadeIn et non TryStartupFadeIn : sans
// ca il retomberait dans la boucle de reessai et l'ecran resterait noir
// pour toujours.
GetWorldTimerManager().SetTimer(
StartupFadeTimeoutHandle, this, &AFpsPlayerController::CompleteStartupFadeIn,
FMath::Max(StartupFadeTimeout, 1.f), /*bLoop=*/false);
// L'hote possede deja son pawn ET sa camera est dessus : il n'attendra
// rien. Le test reel est dans TryStartupFadeIn.
TryStartupFadeIn();
}
else if (IsLocalController())
{
// Mode sans fondu : personne ne rallumera le rendu, il faut le faire ici
// sous peine d'ecran noir jusqu'au filet de securite.
if (USurvivalGameInstance* SurvivalGameInstance = GetGameInstance<USurvivalGameInstance>())
{
SurvivalGameInstance->ResumeWorldRendering();
}
}
}
void AFpsPlayerController::TryStartupFadeIn()
{
if (!bStartupFadePending)
{
return;
}
// Un controller distant vu depuis le serveur n'a pas d'ecran a eclaircir, et
// surtout pas celui de l'hote.
if (!IsLocalController())
{
return;
}
// La camera doit deja regarder par NOS yeux. Un client demarre sur un
// SpectatorPawn ne au centre du monde -- sous le terrain -- et y reste
// encore un instant apres l'acquittement de la possession. Eclaircir a cet
// instant montre exactement ca.
const APawn* OwnPawn = GetPawn();
if (!OwnPawn || GetViewTarget() != OwnPawn)
{
GetWorldTimerManager().SetTimer(
StartupFadeRetryHandle, this, &AFpsPlayerController::TryStartupFadeIn,
StartupFadeRetry, /*bLoop=*/false);
return;
}
CompleteStartupFadeIn();
}
void AFpsPlayerController::CompleteStartupFadeIn()
{
if (!bStartupFadePending)
{
return;
}
bStartupFadePending = false;
GetWorldTimerManager().ClearTimer(StartupFadeTimeoutHandle);
GetWorldTimerManager().ClearTimer(StartupFadeRetryHandle);
// Le rendu du monde est suspendu depuis le chargement de la map : on le
// rend AVANT le fondu, alors que le voile est encore parfaitement noir.
// L'ordre inverse laisserait passer les images qu'on cherche a cacher.
USurvivalGameInstance* SurvivalGameInstance = GetGameInstance<USurvivalGameInstance>();
if (!SurvivalGameInstance)
{
StartStartupFade();
return;
}
SurvivalGameInstance->ResumeWorldRendering();
// On laisse le monde rendre quelques images DERRIERE le voile noir avant de
// l'eclaircir : c'est ce qui donne a Nanite, aux ombres virtuelles et a
// Lumen le temps de converger sans que personne le voie.
SurvivalGameInstance->BeginSceneWarmup(
FSimpleDelegate::CreateUObject(this, &AFpsPlayerController::StartStartupFade));
}
void AFpsPlayerController::StartStartupFade()
{
ScreenFade->FadeIn(StartupFadeDuration);
}
void AFpsPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
SetupForPossessedPawn(InPawn);
}
void AFpsPlayerController::AcknowledgePossession(APawn* P)
{
Super::AcknowledgePossession(P);
// Le chemin CLIENT. Sans lui, un joueur qui rejoint n'a aucun HUD : le
// serveur a bien appele OnPossess, mais chez lui, pas chez le client.
SetupForPossessedPawn(P);
}
void AFpsPlayerController::SetupForPossessedPawn(APawn* InPawn)
{
// On cree les widgets ici et pas dans BeginPlay : a ce stade le pawn est
// garanti possede, et le prompt comme l'inventaire ont besoin de lui pour
// trouver leurs composants des leur NativeConstruct.
@@ -132,7 +253,6 @@ void AFpsPlayerController::OnPossess(APawn* InPawn)
// Apres un respawn les widgets existent deja, mais pointent vers les
// composants du cadavre. On les rebranche sur le nouveau pawn.
if (Crosshair) { /* pas d'abonnement */ }
if (InteractionPrompt) { InteractionPrompt->BindToOwningPawn(); }
if (Hotbar) { Hotbar->BindToOwningPawn(); }
if (InventoryScreen) { InventoryScreen->BindToOwningPawn(); }
@@ -142,9 +262,16 @@ void AFpsPlayerController::OnPossess(APawn* InPawn)
{
if (USurvivalStatsComponent* Stats = PlayerPawn->GetSurvivalStats())
{
Stats->OnDied.AddDynamic(this, &AFpsPlayerController::HandlePawnDied);
// AddUnique et non Add : chez l'hote, OnPossess ET
// AcknowledgePossession passent tous les deux ici. Un double
// abonnement declencherait deux fondus de mort et deux respawns.
Stats->OnDied.AddUniqueDynamic(this, &AFpsPlayerController::HandlePawnDied);
}
}
// Le corps est la : on peut enfin montrer le monde. Sans effet si le fondu
// d'arrivee a deja eu lieu, ce qui est le cas a chaque respawn.
TryStartupFadeIn();
}
void AFpsPlayerController::HandlePawnDied()
@@ -1091,8 +1218,88 @@ void AFpsPlayerController::RequestQuitToMainMenu()
void AFpsPlayerController::HandleQuitToMenuConfirmed()
{
bQuitToMenuDialogOpen = false;
// L'hote emmene tout le monde avec lui : sa partie EST la partie. On
// previent avant de fondre, pour que les quatre fondus tournent ensemble et
// que personne ne voie l'ecran se figer.
if (HasAuthority())
{
KickRemoteClientsToMainMenu();
}
BeginQuitToMainMenu();
}
void AFpsPlayerController::HandleSessionTravelPending()
{
USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr;
if (!Sessions || bQuittingToMainMenu)
{
return;
}
// Les ecrans ouverts resteraient affiches par-dessus le noir, curseur
// compris, et arriveraient tels quels dans la partie de l'ami.
if (bInventoryOpen)
{
SetInventoryScreenOpen(false, /*bShowCraftingTab=*/false);
}
if (bPauseMenuOpen)
{
ResumeGame();
}
ScreenFade->FadeOut(Sessions->GetTravelFadeDuration());
}
void AFpsPlayerController::Client_ReturnToMainMenu_Implementation()
{
UE_LOG(LogTemp, Log, TEXT("AFpsPlayerController : l'hote a quitte la partie, retour au menu."));
BeginQuitToMainMenu();
}
void AFpsPlayerController::KickRemoteClientsToMainMenu()
{
UWorld* World = GetWorld();
if (!World || !HasAuthority())
{
return;
}
for (FConstPlayerControllerIterator It = World->GetPlayerControllerIterator(); It; ++It)
{
AFpsPlayerController* Other = Cast<AFpsPlayerController>(It->Get());
// Soi-meme exclu : on a notre propre chemin, et l'hote n'est de toute
// facon pas joignable par une RPC Client_ depuis lui-meme.
if (Other && Other != this)
{
Other->Client_ReturnToMainMenu();
}
}
}
void AFpsPlayerController::BeginQuitToMainMenu()
{
// Un client peut avoir clique Quitter juste avant de recevoir l'ordre de
// l'hote : deux fondus simultanes se marcheraient dessus.
if (bQuittingToMainMenu)
{
return;
}
bQuittingToMainMenu = true;
// Le GameInstance doit savoir que ce depart est VOULU : sans ca, la
// connexion qui tombe pendant le fondu declencherait son filet de secours,
// qui chargerait le menu immediatement et couperait le fondu en deux.
if (USurvivalGameInstance* SurvivalGameInstance = GetGameInstance<USurvivalGameInstance>())
{
SurvivalGameInstance->BeginReturnToMainMenu();
}
if (QuitToMenuDialog)
{
QuitToMenuDialog->Close();
@@ -1103,10 +1310,13 @@ void AFpsPlayerController::HandleQuitToMenuConfirmed()
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.
// L'ecran d'inventaire ou de coffre resterait affiche par-dessus le noir,
// curseur compris. Sa fermeture rend le coffre au passage.
if (bInventoryOpen)
{
SetInventoryScreenOpen(false, /*bShowCraftingTab=*/false);
}
ScreenFade->FadeOut(QuitToMenuFadeDuration);
}
@@ -1132,6 +1342,17 @@ void AFpsPlayerController::HandleFadeFinished()
return;
}
// Fermer la session AVANT de voyager. Chez l'hote c'est ce qui la retire de
// la liste de tes amis a l'instant ou tu quittes, plutot qu'a l'arrivee sur
// le menu ; chez un client c'est ce qui libere sa place.
//
// Les autres joueurs, eux, perdront la connexion : c'est
// USurvivalGameInstance::HandleNetworkFailure qui les ramene a leur menu.
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
{
Sessions->LeaveSession();
}
UGameplayStatics::OpenLevelBySoftObjectPtr(this, MainMenuLevel);
}
@@ -55,6 +55,15 @@ void AMainMenuPlayerController::BeginPlay()
UE_LOG(LogTemp, Warning, TEXT("AMainMenuPlayerController : aucune AMenuCameraSpot active dans la map, le menu s'affichera sur la vue par defaut."));
}
// APRES SetViewTarget, jamais avant : les images de chauffe doivent etre
// rendues depuis l'angle qu'on va reellement montrer, sinon elles raffinent
// un decor qu'on ne verra pas. Le voile est deja noir, donc rien ne fuit.
USurvivalGameInstance* SurvivalGameInstance = GetGameInstance<USurvivalGameInstance>();
if (SurvivalGameInstance)
{
SurvivalGameInstance->ResumeWorldRendering();
}
if (MainMenuClass)
{
MainMenu = CreateWidget<UMainMenuWidget>(this, MainMenuClass);
@@ -78,12 +87,31 @@ void AMainMenuPlayerController::BeginPlay()
SetShowMouseCursor(true);
ScreenFade->OnFadeFinished.AddDynamic(this, &AMainMenuPlayerController::HandleFadeFinished);
// Meme raison qu'en jeu : on laisse le decor rendre quelques images derriere
// le voile noir avant de l'eclaircir, le temps que Nanite et les textures
// virtuelles se raffinent. Sans ca on voit le terrain passer de sa version
// grossiere a la vraie, et les modeles apparaitre un a un.
if (SurvivalGameInstance)
{
SurvivalGameInstance->BeginSceneWarmup(
FSimpleDelegate::CreateUObject(this, &AMainMenuPlayerController::StartOpeningFade));
}
else
{
StartOpeningFade();
}
}
void AMainMenuPlayerController::StartOpeningFade()
{
ScreenFade->FadeIn(OpeningFadeDuration);
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
{
Sessions->OnHostFailed.AddDynamic(this, &AMainMenuPlayerController::HandleHostFailed);
Sessions->OnJoinFailed.AddDynamic(this, &AMainMenuPlayerController::HandleJoinFailed);
Sessions->OnTravelPending.AddDynamic(this, &AMainMenuPlayerController::HandleTravelPending);
// Revenir au menu depuis une partie laisse la session ouverte : elle
// resterait annoncee a tes amis alors que plus personne n'est dedans.
@@ -91,6 +119,14 @@ void AMainMenuPlayerController::BeginPlay()
UE_LOG(LogTemp, Log, TEXT("AMainMenuPlayerController : service en ligne [%s]."), *Sessions->GetOnlineServiceName());
}
// On est arrive : le filet de secours reseau redevient actif pour la
// prochaine partie. Sans cette remise a zero, un hote qui relance apres
// avoir quitte ne serait plus jamais ramene au menu en cas de coupure.
if (USurvivalGameInstance* SurvivalGameInstance = GetGameInstance<USurvivalGameInstance>())
{
SurvivalGameInstance->ClearReturnToMainMenu();
}
}
AMenuCameraSpot* AMainMenuPlayerController::PickCameraSpot()
@@ -185,6 +221,31 @@ void AMainMenuPlayerController::HandleHostFailed(FText Reason)
UGameplayStatics::OpenLevelBySoftObjectPtr(this, GameLevel, /*bAbsolute=*/true, TEXT("listen"));
}
void AMainMenuPlayerController::HandleTravelPending()
{
USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr;
if (!Sessions)
{
return;
}
// Le menu reste lisible pendant le fondu mais cesse de repondre : cliquer
// Jouer alors qu'on part deja rejoindre un ami serait un piege.
if (MainMenu)
{
MainMenu->SetVisibility(ESlateVisibility::HitTestInvisible);
}
// Exactement la duree que le sous-systeme attend : fondre plus lentement
// laisserait voyager avant que l'ecran soit noir, plus vite laisserait un
// ecran noir immobile.
ScreenFade->FadeOut(Sessions->GetTravelFadeDuration());
// PendingAction reste a None VOLONTAIREMENT : ce n'est pas nous qui
// chargerons la map, c'est le sous-systeme. HandleFadeFinished doit donc ne
// rien faire quand ce fondu-la se termine.
}
void AMainMenuPlayerController::HandleJoinFailed(FText Reason)
{
UE_LOG(LogTemp, Error, TEXT("AMainMenuPlayerController : connexion impossible -- %s"), *Reason.ToString());
@@ -59,6 +59,9 @@ void UScreenFadeComponent::EnsureWidget()
// Un widget n'existe que sur la machine du joueur local : inutile et
// incorrect d'en creer un sur un serveur dedie.
// ATTENTION : chez un client, ce test est FAUX au BeginPlay du controller --
// son ULocalPlayer ne lui est attache qu'ensuite, dans SetPlayer(). Un
// SnapToBlack() fait trop tot noircit donc un voile inexistant, en silence.
if (!Controller->IsLocalController())
{
return;
@@ -380,6 +380,37 @@ void USessionSubsystem::HandleJoinSessionComplete(FName SessionName, EOnJoinSess
return;
}
// On ne voyage PAS tout de suite : on annonce, on laisse le controller
// courant fondre au noir, puis on part. Sans ce delai, accepter une
// invitation depuis le menu coupe l'image d'un coup -- le seul endroit du
// jeu ou une transition n'etait pas fondue.
PendingTravelURL = ConnectString;
OnTravelPending.Broadcast();
UGameInstance* OwningGameInstance = GetGameInstance();
if (TravelFadeDuration <= 0.f || !OwningGameInstance)
{
CommitPendingTravel();
return;
}
// Le TimerManager du GameInstance et non celui du monde : le monde courant
// est justement celui qu'on s'apprete a quitter.
OwningGameInstance->GetTimerManager().SetTimer(
TravelTimer, this, &USessionSubsystem::CommitPendingTravel,
TravelFadeDuration, /*bLoop=*/false);
}
void USessionSubsystem::CommitPendingTravel()
{
if (PendingTravelURL.IsEmpty())
{
return;
}
const FString TravelURL = MoveTemp(PendingTravelURL);
PendingTravelURL.Reset();
APlayerController* Controller = GetGameInstance() ? GetGameInstance()->GetFirstLocalPlayerController() : nullptr;
if (!Controller)
{
@@ -387,8 +418,8 @@ void USessionSubsystem::HandleJoinSessionComplete(FName SessionName, EOnJoinSess
return;
}
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : voyage vers [%s]."), *ConnectString);
Controller->ClientTravel(ConnectString, TRAVEL_Absolute);
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : voyage vers [%s]."), *TravelURL);
Controller->ClientTravel(TravelURL, TRAVEL_Absolute);
}
void USessionSubsystem::HandleInviteAccepted(const bool bWasSuccessful, const int32 ControllerId, FUniqueNetIdPtr UserId, const FOnlineSessionSearchResult& InviteResult)
@@ -3,9 +3,15 @@
#include "SurvivalGameInstance.h"
#include "ContentStreaming.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/Engine.h"
#include "Engine/GameViewportClient.h"
#include "Engine/LocalPlayer.h"
#include "GameMapsSettings.h"
#include "InputMappingContext.h"
#include "Kismet/GameplayStatics.h"
#include "SessionSubsystem.h"
#include "SurvivalUserSettings.h"
#include "UserSettings/EnhancedInputUserSettings.h"
@@ -32,6 +38,175 @@ void USurvivalGameInstance::Init()
{
UE_LOG(LogTemp, Error, TEXT("USurvivalGameInstance : GameUserSettingsClassName n'est pas configure dans DefaultEngine.ini, aucun reglage ne sera pris en compte."));
}
// Filet de securite reseau. On s'abonne ici, une fois pour toutes : ces
// evenements arrivent au pire moment, typiquement quand le monde est en
// train d'etre detruit, et un abonnement porte par un acteur mourrait
// justement avec lui.
if (GEngine)
{
NetworkFailureHandle = GEngine->OnNetworkFailure().AddUObject(this, &USurvivalGameInstance::HandleNetworkFailure);
TravelFailureHandle = GEngine->OnTravelFailure().AddUObject(this, &USurvivalGameInstance::HandleTravelFailure);
}
PreLoadMapHandle = FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &USurvivalGameInstance::HandlePreLoadMap);
}
void USurvivalGameInstance::HandlePreLoadMap(const FString& MapName)
{
// On coupe AVANT que quoi que ce soit du nouveau monde n'existe. C'est le
// premier controller pret qui rallumera, une fois son voile noir en place --
// et il naitra forcement apres nous, ce qui garantit le bon ordre.
SetWorldRenderingSuspended(true);
UE_LOG(LogTemp, Log, TEXT("USurvivalGameInstance : chargement de [%s], rendu du monde suspendu."), *MapName);
// Le timer du GameInstance et non celui du monde : celui-ci est justement en
// train d'etre detruit.
GetTimerManager().SetTimer(
RenderingResumeTimeout, this, &USurvivalGameInstance::ResumeWorldRendering,
20.f, /*bLoop=*/false);
}
void USurvivalGameInstance::BeginSceneWarmup(FSimpleDelegate OnComplete)
{
SceneWarmupCompleted = OnComplete;
RemainingWarmupFrames = FMath::Max(SceneWarmupFrames, 0);
WarmupStartTime = FPlatformTime::Seconds();
if (RemainingWarmupFrames <= 0)
{
SceneWarmupCompleted.ExecuteIfBound();
SceneWarmupCompleted.Unbind();
return;
}
TickSceneWarmup();
}
void USurvivalGameInstance::TickSceneWarmup()
{
const double Elapsed = FPlatformTime::Seconds() - WarmupStartTime;
const bool bCapReached = Elapsed >= SceneWarmupMaxSeconds;
if (RemainingWarmupFrames > 0 && !bCapReached)
{
--RemainingWarmupFrames;
// SetTimerForNextTick et non un timer en secondes : une image rendue,
// un tick. C'est l'unite dans laquelle Nanite et Lumen progressent.
GetTimerManager().SetTimerForNextTick(this, &USurvivalGameInstance::TickSceneWarmup);
return;
}
UE_LOG(LogTemp, Log, TEXT("USurvivalGameInstance : chauffe terminee -- %d images rendues en %.0f ms%s."),
SceneWarmupFrames - RemainingWarmupFrames, Elapsed * 1000.0,
bCapReached ? TEXT(" (plafond atteint)") : TEXT(""));
// Copie avant execution : le rappel declenche un fondu, qui pourrait
// theoriquement relancer une chauffe et ecraser le delegue en pleine main.
FSimpleDelegate Completed = SceneWarmupCompleted;
SceneWarmupCompleted.Unbind();
Completed.ExecuteIfBound();
}
void USurvivalGameInstance::SetWorldRenderingSuspended(bool bSuspended)
{
if (UGameViewportClient* Viewport = GetGameViewportClient())
{
Viewport->bDisableWorldRendering = bSuspended;
}
}
void USurvivalGameInstance::ResumeWorldRendering()
{
GetTimerManager().ClearTimer(RenderingResumeTimeout);
// Streaming vide AVANT de rallumer. UpdateResourceStreaming(0, true) traite
// tout sans avoir besoin d'un point de vue -- ce qui tombe bien, le rendu
// est encore coupe donc personne ne lui en a fourni -- puis on bloque le
// temps que les requetes aboutissent.
//
// Ce blocage a lieu ECRAN NOIR : c'est du temps de chargement, pas une
// saccade. C'est exactement ce que le moteur fait lui-meme a l'ouverture
// d'une map, et c'est ce qui evite de voir le terrain se raffiner et les
// modeles apparaitre un a un.
const double StartTime = FPlatformTime::Seconds();
const int32 PendingRequests = IStreamingManager::Get().StreamAllResources(FMath::Max(StreamingFlushTimeLimit, 0.f));
const double ElapsedMs = (FPlatformTime::Seconds() - StartTime) * 1000.0;
SetWorldRenderingSuspended(false);
UE_LOG(LogTemp, Log, TEXT("USurvivalGameInstance : rendu du monde repris apres %.0f ms de streaming (%d requete(s) restante(s))."),
ElapsedMs, PendingRequests);
}
void USurvivalGameInstance::Shutdown()
{
if (GEngine)
{
GEngine->OnNetworkFailure().Remove(NetworkFailureHandle);
GEngine->OnTravelFailure().Remove(TravelFailureHandle);
}
FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadMapHandle);
Super::Shutdown();
}
void USurvivalGameInstance::HandleNetworkFailure(UWorld* World, UNetDriver* NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString)
{
UE_LOG(LogTemp, Warning, TEXT("USurvivalGameInstance : echec reseau [%s] -- %s"),
ENetworkFailure::ToString(FailureType), *ErrorString);
// Depart deja en cours : c'est le cas NORMAL quand l'hote quitte, puisqu'il
// a prevenu tout le monde avant de couper. On laisse les fondus finir.
if (bReturningToMainMenu)
{
UE_LOG(LogTemp, Log, TEXT("USurvivalGameInstance : depart deja en cours, echec ignore."));
return;
}
// Ici en revanche personne n'a prevenu : hote qui plante, cable arrache,
// relais Steam qui lache. C'est le filet de secours, sans fondu -- mieux
// vaut un retour brutal au menu qu'un monde mort.
ReturnToMainMenu();
}
void USurvivalGameInstance::HandleTravelFailure(UWorld* World, ETravelFailure::Type FailureType, const FString& ErrorString)
{
UE_LOG(LogTemp, Warning, TEXT("USurvivalGameInstance : echec de voyage [%s] -- %s"),
ETravelFailure::ToString(FailureType), *ErrorString);
if (bReturningToMainMenu)
{
return;
}
ReturnToMainMenu();
}
void USurvivalGameInstance::ReturnToMainMenu()
{
bReturningToMainMenu = true;
// La session est morte avec la connexion : la detruire ici evite qu'elle
// reste annoncee a tes amis, et surtout qu'elle bloque la creation de la
// suivante.
if (USessionSubsystem* Sessions = GetSubsystem<USessionSubsystem>())
{
Sessions->LeaveSession();
}
// GetGameDefaultMap et pas un chemin en dur : c'est deja MenuScene dans
// DefaultEngine.ini, et une seconde copie du chemin finirait par diverger.
const FString MenuMap = UGameMapsSettings::GetGameDefaultMap();
if (MenuMap.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("USurvivalGameInstance : aucune GameDefaultMap, impossible de revenir au menu."));
return;
}
UGameplayStatics::OpenLevel(this, FName(*MenuMap), /*bAbsolute=*/true);
}
void USurvivalGameInstance::RegisterMappableContexts()