(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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user