// Fill out your copyright notice in the Description page of Project Settings. #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" void USurvivalGameInstance::Init() { Super::Init(); // Pas d'enregistrement des IMC ici : a ce stade il n'y a ni joueur local ni // PlayerController, donc pas encore de UserSettings a qui parler. // RegisterMappableContexts() est appelee par l'ecran de reglages. // Les reglages sont deja LUS depuis le .ini par le moteur, mais rien ne les // a encore APPLIQUES : sans cet appel, la resolution et la qualite // choisies au dernier lancement resteraient lettre morte jusqu'au premier // passage dans le menu. // // C'est le bon endroit : le GameInstance est le seul objet qui naisse avant // la premiere map et survive a tous les changements de niveau. if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get()) { Settings->ApplySettings(/*bCheckForCommandLineOverrides=*/false); } else { 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()) { 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() { ULocalPlayer* LocalPlayer = GetFirstGamePlayer(); if (!LocalPlayer) { UE_LOG(LogTemp, Warning, TEXT("USurvivalGameInstance : aucun joueur local, enregistrement des touches remis a plus tard.")); return; } if (MappableContexts.IsEmpty()) { UE_LOG(LogTemp, Warning, TEXT("USurvivalGameInstance : MappableContexts est vide, l'onglet Touches affichera 'Non assignee' partout. Ajoute IMC_Default dans le Blueprint de GameInstance.")); return; } UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem(); UEnhancedInputUserSettings* InputSettings = Subsystem ? Subsystem->GetUserSettings() : nullptr; if (!InputSettings) { UE_LOG(LogTemp, Error, TEXT("USurvivalGameInstance : reglages d'input introuvables. Coche 'Enable User Settings' dans Project Settings > Enhanced Input.")); return; } for (const TObjectPtr& Context : MappableContexts) { if (Context) { // Idempotent : le moteur ignore un contexte deja connu, on peut donc // rappeler cette fonction sans precaution. InputSettings->RegisterInputMappingContext(Context); } } }