(Feat) Fix Fade Loading
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user