256 lines
10 KiB
C++
256 lines
10 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "SurvivalGameInstance.generated.h"
|
|
|
|
class UInputMappingContext;
|
|
class ULocalPlayer;
|
|
|
|
/**
|
|
* Le seul objet qui survit aux changements de map.
|
|
*
|
|
* C'est l'equivalent le plus proche d'un singleton DontDestroyOnLoad d'Unity :
|
|
* il naît au lancement du jeu et meurt a sa fermeture. Tout le reste --
|
|
* GameMode, GameState, PlayerController, pawns, widgets -- est detruit et
|
|
* recree a chaque OpenLevel.
|
|
*
|
|
* Y mettre : les reglages, la sauvegarde en cours, et plus tard l'hebergement
|
|
* et le rejoindre-une-partie. Surtout pas d'etat de gameplay, qui appartient au
|
|
* monde et doit disparaitre avec lui.
|
|
*/
|
|
UCLASS()
|
|
class EMBERWILD_API USurvivalGameInstance : public UGameInstance
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
virtual void Init() override;
|
|
virtual void Shutdown() override;
|
|
|
|
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
|
|
UFUNCTION(BlueprintPure, Category = "Menu")
|
|
FName GetLastMenuCameraName() const { return LastMenuCameraName; }
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Menu")
|
|
void SetLastMenuCameraName(FName SpotName) { LastMenuCameraName = SpotName; }
|
|
|
|
/**
|
|
* Declare les IMC remappables aupres des reglages d'input du joueur.
|
|
*
|
|
* Appelee A LA DEMANDE, par l'ecran de reglages avant de lire les touches, et
|
|
* non depuis un evenement de demarrage. Raison : UserSettings n'est cree que
|
|
* dans UEnhancedInputLocalPlayerSubsystem::PlayerControllerChanged, et
|
|
* ULocalPlayer diffuse ses evenements AVANT de prevenir ses sous-systemes --
|
|
* tout hook precoce arrive donc trop tot, quel qu'il soit.
|
|
*
|
|
* Idempotente : le moteur ignore un contexte deja connu.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
void RegisterMappableContexts();
|
|
|
|
/**
|
|
* Traduit les touches par defaut vers la disposition clavier du joueur.
|
|
*
|
|
* Les IMC sont ecrits en QWERTY US -- EKeys::W pour avancer -- et Windows livre
|
|
* au moteur une touche DEJA traduite par la disposition : en AZERTY, la touche
|
|
* du haut du bloc de deplacement arrive comme EKeys::Z et le mapping "W" ne se
|
|
* declenche jamais. On repose donc chaque touche a la position equivalente.
|
|
* Le detail du mecanisme est dans KeyboardLayout.h.
|
|
*
|
|
* Ne touche QUE les mappings que le joueur n'a pas remappes lui-meme : un
|
|
* choix explicite l'emporte toujours sur une deduction. Et sur un clavier
|
|
* QWERTY la conversion rend la touche d'origine, donc rien n'est ecrit du tout
|
|
* et le profil reste vierge.
|
|
*
|
|
* Declenchee UNE FOIS par RegisterMappableContexts(), au premier enregistrement
|
|
* des contextes ; ensuite seul "Reinitialiser" la redemande. Elle appartient a
|
|
* l'initialisation, pas au rafraichissement d'ecran -- l'onglet Touches se
|
|
* rafraichit apres chaque remappage, et repasser dessus reprendrait sa touche
|
|
* au joueur qui aurait justement choisi celle de l'IMC (la lettre W sur un
|
|
* AZERTY, par exemple : identique au defaut, donc indiscernable d'un mapping
|
|
* jamais touche).
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
void ApplyKeyboardLayoutToDefaultKeys();
|
|
|
|
/**
|
|
* Un depart VOULU vers le menu commence : le filet de secours reseau doit
|
|
* se taire.
|
|
*
|
|
* Sans ce drapeau, la connexion qui tombe pendant le fondu de sortie
|
|
* declencherait HandleNetworkFailure, qui chargerait le menu sur-le-champ
|
|
* et couperait le fondu en plein milieu -- exactement le saut d'image qu'on
|
|
* cherchait a eviter en fondant.
|
|
*/
|
|
void BeginReturnToMainMenu() { bReturningToMainMenu = true; }
|
|
|
|
/** On est arrive au menu : le filet redevient actif pour la partie suivante. */
|
|
void ClearReturnToMainMenu() { bReturningToMainMenu = false; }
|
|
|
|
/**
|
|
* Rend la main au rendu du monde, coupe depuis le chargement de la map.
|
|
*
|
|
* A n'appeler QUE lorsque le voile de fondu est deja noir : le monde
|
|
* reapparait alors derriere lui, invisible.
|
|
*
|
|
* Pourquoi couper le rendu plutot que poser un widget. Mesure sur une vraie
|
|
* connexion : 2,4 secondes s'ecoulent entre `LoadMap` chez le client et la
|
|
* naissance de son PlayerController, que le serveur lui envoie. Pendant ce
|
|
* temps la map est chargee et le moteur la rend depuis une vue par defaut --
|
|
* sous le terrain. Il n'existe alors AUCUN acteur a qui accrocher un voile,
|
|
* et le composant de fondu vit justement sur ce controller absent. Le
|
|
* viewport, lui, survit aux changements de map et n'appartient a personne.
|
|
*
|
|
* Attend aussi la fin du streaming avant de rendre la main : c'est le seul
|
|
* instant du chargement ou l'on sait que rien ne sera montre tant qu'on n'a
|
|
* pas fini. Sans ca, le joueur voit le terrain passer de sa version
|
|
* grossiere a la vraie, et les modeles apparaitre un a un.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Chargement")
|
|
void ResumeWorldRendering();
|
|
|
|
/**
|
|
* Laisse le monde rendre SceneWarmupFrames images derriere le voile noir,
|
|
* puis rappelle OnComplete.
|
|
*
|
|
* Compte des IMAGES et non des secondes, et c'est tout l'interet. Ce qui
|
|
* reste a se raffiner apres le flush de streaming -- Nanite, les ombres
|
|
* virtuelles, la convergence de Lumen -- progresse a chaque image RENDUE,
|
|
* pas avec le temps qui passe. Une duree fixe donnerait quarante images sur
|
|
* une machine rapide et cinq sur une machine lente : exactement celle qui en
|
|
* aurait eu le plus besoin.
|
|
*/
|
|
void BeginSceneWarmup(FSimpleDelegate OnComplete);
|
|
|
|
protected:
|
|
/**
|
|
* Temps maximal accordé au streaming avant de montrer le monde, en secondes.
|
|
*
|
|
* Ce blocage se produit ECRAN NOIR, donc il ne se voit pas -- c'est du temps
|
|
* de chargement, pas une saccade. Le dépasser vaut mieux que de montrer un
|
|
* terrain qui se raffine sous les yeux du joueur, mais on le borne quand
|
|
* même : une machine lente ne doit pas rester bloquée indéfiniment.
|
|
*
|
|
* Réglable sur BP_SurvivalGameInstance.
|
|
*/
|
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0", Units = "s"))
|
|
float StreamingFlushTimeLimit = 5.f;
|
|
|
|
/**
|
|
* Nombre d'images rendues derriere le voile noir avant de l'eclaircir.
|
|
* Voir BeginSceneWarmup(). Reglable sur BP_SurvivalGameInstance.
|
|
*/
|
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0"))
|
|
int32 SceneWarmupFrames = 45;
|
|
|
|
/**
|
|
* Plafond de la chauffe, en secondes.
|
|
*
|
|
* Le compte d'images reste la mesure PRINCIPALE -- c'est la bonne unite.
|
|
* Ceci n'est qu'un garde-fou : sur une machine tres lente, 45 images
|
|
* pourraient prendre plusieurs secondes d'ecran noir, et mieux vaut un
|
|
* decor pas tout a fait converge qu'une attente qui inquiete.
|
|
*/
|
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Chargement", meta = (ClampMin = "0.1", Units = "s"))
|
|
float SceneWarmupMaxSeconds = 2.f;
|
|
|
|
/**
|
|
* Les mapping contexts dont les touches sont remappables. Mets-y IMC_Default.
|
|
*
|
|
* Ici et PAS sur le pawn : le menu principal n'a aucun pawn, et l'ecran de
|
|
* reglages doit pourtant afficher les touches. C'est le critere habituel --
|
|
* "est-ce que ca doit survivre au changement de map ?" -- qui envoie cette
|
|
* liste sur le GameInstance.
|
|
*/
|
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
|
TArray<TObjectPtr<UInputMappingContext>> MappableContexts;
|
|
|
|
private:
|
|
/**
|
|
* L'hote a quitte, ou la connexion est tombee.
|
|
*
|
|
* Sans ce filet, un client dont l'hote ferme le jeu reste planté dans un
|
|
* monde qui ne repond plus : le moteur coupe la connexion, mais rien ne le
|
|
* ramene nulle part. C'est ici et pas sur le PlayerController parce que
|
|
* celui-ci est justement detruit par l'echec -- le GameInstance est le seul
|
|
* objet dont on soit certain qu'il vive encore a cet instant.
|
|
*/
|
|
void HandleNetworkFailure(UWorld* World, UNetDriver* NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString);
|
|
|
|
/** Meme filet pour un voyage qui echoue en route. */
|
|
void HandleTravelFailure(UWorld* World, ETravelFailure::Type FailureType, const FString& ErrorString);
|
|
|
|
/**
|
|
* Ferme la session en cours et rouvre le menu principal.
|
|
*
|
|
* La map du menu n'est PAS une propriete de plus : c'est deja la
|
|
* GameDefaultMap du projet. En dupliquer le chemin ici donnerait deux
|
|
* valeurs a tenir en phase, et celle-ci ne serait jamais testee.
|
|
*/
|
|
void ReturnToMainMenu();
|
|
|
|
/**
|
|
* Une map va etre chargee : on coupe le rendu jusqu'a ce qu'un controller
|
|
* soit pret.
|
|
*
|
|
* PreLoadMap et surtout PAS PostLoadMapWithWorld : ce dernier est diffuse
|
|
* APRES le BeginPlay des acteurs. Sur une map chargee localement -- le menu,
|
|
* ou la partie chez l'hote -- le controller rendait donc la main au rendu,
|
|
* et on la lui reprenait juste apres. Ecran noir definitif, jusqu'au filet
|
|
* de securite. Chez un client ca passait par accident, son controller
|
|
* arrivant plus tard.
|
|
*/
|
|
void HandlePreLoadMap(const FString& MapName);
|
|
|
|
void SetWorldRenderingSuspended(bool bSuspended);
|
|
|
|
/** Une image de chauffe vient de passer : on decompte, ou on rappelle. */
|
|
void TickSceneWarmup();
|
|
|
|
/** Images restantes a rendre avant d'eclaircir. */
|
|
int32 RemainingWarmupFrames = 0;
|
|
|
|
/** Debut de la chauffe, pour appliquer le plafond en secondes. */
|
|
double WarmupStartTime = 0.0;
|
|
|
|
/** Ce qu'on fait une fois la chauffe finie -- eclaircir, cote appelant. */
|
|
FSimpleDelegate SceneWarmupCompleted;
|
|
|
|
FDelegateHandle NetworkFailureHandle;
|
|
FDelegateHandle TravelFailureHandle;
|
|
FDelegateHandle PreLoadMapHandle;
|
|
|
|
/**
|
|
* Filet : si aucun controller ne rend la main -- connexion qui n'aboutit
|
|
* pas, spawn rate -- on rallume quand meme. Un ecran noir definitif se lit
|
|
* comme un plantage, et c'est pire que le defaut qu'on corrige.
|
|
*/
|
|
FTimerHandle RenderingResumeTimeout;
|
|
|
|
/** Un retour au menu est deja en cours, voulu ou non. */
|
|
bool bReturningToMainMenu = false;
|
|
|
|
/**
|
|
* La disposition clavier a deja ete appliquee dans cette session.
|
|
*
|
|
* Pas de UPROPERTY(config) : ce n'est PAS "l'a-t-on deja fait un jour". Les
|
|
* surcharges posees, elles, sont persistees par Enhanced Input, et elles
|
|
* suffisent a rendre l'operation sans effet au lancement suivant. Ce drapeau
|
|
* ne protege que du re-declenchement en cours de session.
|
|
*/
|
|
bool bKeyboardLayoutApplied = false;
|
|
|
|
/**
|
|
* Stocke un NOM et pas un index : les index changent des qu'on ajoute ou
|
|
* supprime une camera dans la map, et on retomberait sur autre chose.
|
|
*
|
|
* Ne sert qu'a eviter de retirer deux fois de suite le meme angle quand le
|
|
* joueur revient au menu -- d'ou le Transient, ca n'a pas a etre sauvegarde.
|
|
*/
|
|
UPROPERTY(Transient)
|
|
FName LastMenuCameraName = NAME_None;
|
|
};
|