Aller au contenu

GPVars & Conditions

Les GPVars (variables de gameplay) permettent aux programmeurs et aux designers de stocker l'état du jeu sur la GameInstance et de le lire ou l'écrire de façon fiable depuis n'importe où — en C++ comme en Blueprint. Comme elles résident sur la GameInstance, une GPVar survit au changement de niveau et reste accessible sans conserver de référence vers l'objet qui l'a créée.

Une GPVar est un fragment d'état minimal et adressable : un booléen, un entier ou un compteur « combien de fois ». Chacune est identifiée par un chemin (voir GPTagPath), ce qui maintient l'ensemble du store bien rangé et parcourable dans le widget de debug runtime.

Le système de GPVars est également ce sur quoi s'appuie le système de dialogues pour mémoriser ce que le joueur a fait — quels dialogues ont été joués, quels choix ont été retenus — et pour piloter le branchement conditionnel.

En bref

Un GPTagPath nomme une variable. Un GPFlag est un booléen, un GPCounter est un entier, et un CountedGPFlag est un flag qui résiste au fait d'être levé depuis plusieurs endroits à la fois. Le ULBRGPVarSubsystem possède l'intégralité du store ; les listeners et les delegates vous notifient lorsqu'une valeur change ; un widget de debug vous permet d'inspecter et d'éditer le tout en direct.

Un exemple concret

Imaginez une valeur de Karma propre à chaque personnage. Vous créez un seul GameplayTag, Stats.Karma, et vous stockez la valeur par personnage en le combinant à un chemin de personnage :

  • Characters.Player / Stats.Karma — le karma du joueur
  • Characters.NPC1 / Stats.Karma — le karma de NPC1

À partir de là, chaque discipline lit et écrit la même variable via le même chemin :

  • un narrative designer ajoute +3 lorsqu'un choix de dialogue console un personnage ;
  • un quest designer verrouille un sanctuaire derrière « karma ≥ 15 » ;
  • un game designer retranche 2 pour une action peu recommandable ;
  • un sound designer déclenche un effet lorsque le karma devient négatif ;
  • un programmeur fournit « karma ≥ 25 » à une Animation Blueprint pour choisir un cycle de marche ;
  • un testeur QA modifie directement le karma dans le widget de debug pour vérifier tout ce qui précède d'un seul coup.

GameplayTags

Les GPVars reposent sur les GameplayTags, une fonctionnalité native d'Unreal : des identifiants textuels enregistrés dans une liste sous Project Settings ▸ GameplayTags. Vous choisissez un tag dans une liste déroulante plutôt que de taper une chaîne, ce qui évite les fautes de frappe et rend les renommages sûrs.

Warning

Lorsque vous ajoutez un Gameplay Tag via les Project Settings, pensez à committer le fichier Config/DefaultGameplayTags.ini qui en résulte.

GPTagPath

Un GPTagPath (abréviation de « gameplay tag path ») est un chemin, à la manière d'un chemin de fichier sur disque. Au lieu de dossiers et d'un nom de fichier, c'est une séquence de GameplayTags, par exemple :

Characters.Galeb / Inventory.SC01.JaraReport

Chaque GPVar est identifiée par un GPTagPath. C'est ce qui permet au widget de debug de les disposer en un arbre bien rangé — « Galeb possède l'objet JaraReport » est classé sous la branche du personnage Galeb.

Vous composez un GPTagPath de la même façon que vous choisissez un GameplayTag : en sélectionnant plusieurs tags à la suite. Le sélecteur affiche le chemin en cours de construction en haut, chaque partie étant étiquetée selon sa position dans le chemin.

Composition d'un GPTagPath dans le sélecteur

Des fonctions utilitaires se trouvent sous Gameplay Tag ▸ Utils ▸ GPTagPath.

Chemin unique ou flags groupés

La plupart des fonctions prennent un seul GPTagPath pour identifier une seule GPVar. Les fonctions qui opèrent sur plusieurs flags à la fois (celles dont le nom contient Flags, au pluriel — par exemple RaiseGPFlags) prennent en revanche le chemin parent partagé et un conteneur des flags feuilles à modifier ensemble.

Types de GPVar

GPFlag 🏳️

Un GPFlag est un booléen.

Action Node
Mettre à true RaiseGPFlag
Mettre à false LowerGPFlag
Lire CheckGPFlag

Nodes Blueprint GPFlag

GPCounter 🔢

Un GPCounter est un entier (non décimal).

Action Node
Définir la valeur SetGPCounter
Lire la valeur GetGPCounter

Nodes Blueprint GPCounter

CountedGPFlag 🎌

Un CountedGPFlag se comporte presque comme un GPFlag, mais il tolère d'être levé depuis plusieurs endroits sans écraser les autres. Si vous levez le même CountedGPFlag n fois, le checker renvoie true jusqu'à ce qu'il ait été abaissé ces mêmes n fois.

Les nodes appelables en Blueprint sont RaiseCountedGPFlag, LowerCountedGPFlag et ClearCountedGPFlag (plus un check).

Info

En interne, il n'existe pas de stockage « CountedGPFlag » distinct. C'est la combinaison d'un GPFlag et d'un GPCounter classés au même GPTagPath — reflétée par les champs de FGPVarNode, qui contient à la fois un conteneur GPFlags et une map GPCounters. Ne triturez pas ces deux moitiés à la main si vous les utilisez comme un CountedGPFlag, sous peine de désynchroniser le compteur.

Nodes 🪢

Les Nodes sont les dossiers de ce système : ils sont générés automatiquement à la création d'une GPVar et existent uniquement pour organiser l'arbre. Un node n'est pas une GPVar. Par exemple, dans Characters.Emem / Gameplay.Attributes.Mental, le compteur est la GPVar, tandis que Characters.Emem n'est qu'un node de stockage.

Valeurs par défaut

Vous pouvez définir des GPFlags et des GPCounters qui sont positionnés automatiquement au premier lancement du jeu et à chaque démarrage PIE.

  • Valeurs par défaut à l'échelle du projet : Project Settings ▸ Libretto - GPVars ▸ Default GPVarsNodes (adossées à ULBRGPVarSettings, sauvegardées dans la config Libretto).
  • Valeurs par défaut par machine, rien que pour vous : Editor Preferences ▸ Libretto - GPVars ▸ Default GPVarsNodes (adossées à ULBRGPVarLocalSettings).

Chaque entrée est une paire FGPTagPath → FGPVarNode : la clé est le chemin parent (elle peut être vide si le chemin entier tient en un seul tag), et la valeur liste les GPFlags à lever et les GPCounters à initialiser.

Default GPVarsNodes dans les Project Settings

Le store : ULBRGPVarSubsystem

L'ensemble du store de GPVars du jeu appartient au ULBRGPVarSubsystem (module LBRTags), accessible depuis le C++ comme depuis le Blueprint. Tous les nodes ci-dessus sont de fines enveloppes autour de ses méthodes Static_*, si bien que vous n'avez jamais besoin d'une référence au subsystem pour les appeler.

Sauvegarde

Le store n'est pas sauvegardé pour vous. C'est un subsystem engine : il survit au changement de niveau, mais il repart vide à chaque session de jeu (et, dans l'éditeur, à chaque session PIE).

Pour le sauvegarder, sérialisez le subsystem (ULBRGPVarSubsystem::GetGPVarSubsystem()) avec une archive SaveGame (ArIsSaveGame = true) dans vos propres données de sauvegarde, et relisez-le de la même façon. Ses nodes de GPVars sont des propriétés SaveGame. Les GPVars sous le tag racine Transient sont supprimés au chargement d'une sauvegarde : placez-y ce qui ne doit jamais survivre à la session.

Sauvegardez l'historique des conditions (ULBRConditionHistorySubsystem) dans la même sauvegarde : les GPVars et les résultats de conditions en cache vont ensemble. La recette complète, avec le code, est dans la FAQ : Je veux sauvegarder et restaurer l'état de Libretto avec ma sauvegarde.

Réagir aux changements

Vous voulez souvent exécuter une vérification une seule fois, juste après le changement d'une valeur, plutôt que de la sonder à chaque frame. Deux mécanismes vous permettent de vous brancher immédiatement après la modification d'une GPVar.

Listeners 👂

Un listener est un objet qui filtre les GPVars qui vous intéressent et déclenche un événement à chaque changement. Le plugin fournit les classes de base natives UGPFlagListener et UGPCounterListener ; un projet les enveloppe généralement dans des acteurs listeners que vous déposez dans un niveau, auxquels vous donnez un GPTagPath et que vous câblez dans le Level Blueprint :

  • OnFlagChanged
    • GPFlagPaths — tous les flags modifiés dans le même lot.
    • Changed — true si le flag a été levé, false s'il a été abaissé.
  • OnCounterChanged
    • GPCounterPath — le compteur qui a changé.
    • PreviousValue / NewValue — les valeurs avant et après.

Listeners dans le niveau et leurs événements

Tip

Vous n'êtes pas limité à un chemin par acteur. Les listeners peuvent surveiller plusieurs variables à la fois, ou fonctionner sans aucun acteur — prenez les listeners fournis comme modèle, ou demandez à un programmeur.

Delegates 🗳️

Vous pouvez aussi vous lier directement aux delegates du subsystem — par exemple BindEventToOnGPFlagChangedPerPaths / BindEventToOnGPCounterChangedPerPaths (et leurs homologues Unbind). C'est plus bas niveau : c'est vous qui gérez l'enregistrement et le désenregistrement, et vous filtrez les chemins vous-même. Les paramètres reflètent les événements des listeners ci-dessus.

Le widget de debug runtime

Un widget de debug (le GPVarEditor) affiche l'état vivant du store : quels flags sont levés et ce qu'indique chaque compteur, avec la possibilité d'ajouter, de supprimer et d'éditer des GPVars pendant que le jeu tourne. Il est précieux pour les tests :

  • un narrative designer ajuste les stats d'un personnage pour atteindre une option de dialogue précise ;
  • un testeur QA dépose un objet dans un inventaire pour vérifier son utilisation ;
  • un programmeur bascule un état pour confirmer qu'un pouvoir se débloque au bon moment.

L'ouvrir

En cours de partie (PIE ou jeu packagé), ouvrez la console et lancez :

Libretto.GPVar.OpenDebugWidget

Relancez-la pour la fermer — la commande bascule le widget. Elle affiche le widget défini dans Project Settings ▸ Libretto – GPVars ▸ Debug ▸ Debug Widget Class, qui pointe par défaut sur celui livré avec le plugin (WBP_GPVarEditor, sous Content/GPVars/GPTagEditor/, adossé aux classes natives UGPVarCreatorWidget / SGPVarCreator du module LBRTags). Pointez ce réglage vers votre propre widget pour personnaliser l'inspecteur.

Associez-le à une touche

Pour un usage fréquent, associez le même comportement à une touche dans un Level ou player Blueprint (créez le widget et ajoutez-le au viewport) plutôt que de taper la commande console à chaque fois.

Le widget de debug GPVarEditor

Disposition

Le widget comporte trois parties :

  1. un contrôle pour ajouter de nouvelles GPVars (« Create new GPVar », avec Expand all / Collapse all) ;
  2. les contrôles de la TreeView ;
  3. la TreeView du store proprement dit.

Lire l'arbre

La hiérarchie est construite à partir des GPTagPaths des GPVars. Le symbole de gauche indique l'état du node :

Symbole Signification
> replié, peut être déplié
v déplié
- node feuille, ne contient aucune GPVar

Un GPFlag levé n'affiche que son GameplayTag ; un GPCounter affiche en plus sa valeur à droite, dans un champ éditable.

Édition

  • + en haut ajoute une GPVar à la racine du store.
  • + sur la ligne d'un node ajoute une GPVar à l'intérieur de ce node de stockage.
  • x sur une ligne supprime cette GPVar ou ce node.

Appuyer sur + ouvre la popup GPVarCreator, qui affiche le chemin en cours de construction, une liste déroulante de type (type de GPVar, ou Node pour un simple node de stockage — plus un champ de valeur lors de la création d'un GPCounter), un bouton Add, et le même sélecteur de GPTagPath utilisé partout ailleurs.

La popup GPVarCreator

Le système de dialogues utilise les GPVars comme mémoire. Trois éléments les relient : les tags par asset générés automatiquement, les flags et compteurs runtime écrits pendant la partie, et les conditions sur les lignes qui relisent ces valeurs.

Tags de dialogue générés automatiquement

Plutôt que de vous faire taper des chaînes, le système de dialogues génère un GameplayTag unique pour chaque Dialogue, Subdialogue, Choice et Reply (voir Dynamic tag identification dans le Dialogue Editor) :

Libretto.Dialogues.DialogueName[.SubdialogueName]
Libretto.Dialogues.DialogueName.Choice_ID[.Reply_ID]

Ils sont produits par le LBRDialogueTagsSubsystem au chargement du projet (désactivez-le via sa valeur bUseSubsystem dans le .ini, auquel cas les tags sont écrits dans Config/Tags/Dialogues/…​.ini en solution de repli). Ils vous permettent de suivre chaque choix que fait un joueur et de piloter la sélection de subdialogue, le tout sans chaînes tapées à la main. L'info-bulle qui identifie les sélecteurs de subdialogue d'un dialogue est portée par le sous-tag <dialogue>.SubdialogueSelectors.

Tags de dialogue générés automatiquement dans le Tag Manager

Flags & compteurs runtime écrits pendant la partie

À mesure qu'un dialogue se joue, le système enregistre la progression dans le store de GPVars :

Ce qui est enregistré Comment
Un dialogue a été joué Un CountedGPFlag sous la catégorie Done (flag + compteur) — le compteur = combien de fois il a été joué. Levé à la fin du dialogue.
Un subdialogue a été joué Idem, indexé par le tag du subdialogue. Levé à la fin de chaque subdialogue.
Une reply a été choisie Un CountedGPFlag écrit à plat à la racine (sans catégorie), indexé par le tag de la reply — levé à chaque fois que cette reply est retenue.

Jouez un dialogue une fois, ouvrez le widget de debug, et vous verrez ces entrées apparaître d'elles-mêmes — exactement ce que vous auriez créé à la main.

GPVars runtime après avoir joué un dialogue

La disposition du tag Done

Le chemin du tag Done est construit comme Libretto.RuntimeFlags.Done + <tag de dialogue ou de subdialogue>, de sorte que Done est la catégorie parente et le tag de dialogue/subdialogue est la feuille flag+compteur (ULBRDialogue::Static_GetDialogueDoneGPTagPath, ULBRSubdialogue::GetSubdialogueDoneGPTagPath).

Pour tester si un dialogue est terminé, utilisez la condition dédiée ULBRDialogueDoneCondition — elle prend directement les tags de dialogue / subdialogue (avec une bascule bCheckAll pour tous vs au moins un) et connaît cette disposition. Il existe aussi ULBRDialogueDoneCountedCondition (dérivée de ULBRCheckGPCounter) pour comparer combien de fois un dialogue a été joué. Préférez celles-ci aux conditions sur chemin de GPVar écrites à la main.

La liste déroulante des conditions

Sur une Reply, un Subdialogue ou une branche If/ElseIf/Else, vous pouvez attacher une condition qui lit un état dynamique — y compris les GPVars ci-dessus. La liste déroulante des conditions propose des combinateurs logiques et une famille de checks de GPVar (module LBRConditions) :

Condition Ce qu'elle vérifie
None, AND, OR, XOR, NOT, FALSE, Empty Structure logique / combinateurs.
Check All / Any / Not GPFlags Si les flags feuilles donnés sont (tous/au moins un/aucun) levés au sein d'un chemin partagé.
Check All / Any / Not GPPaths Le même test, mais chaque entrée est un chemin complet donné directement.
Count GPFlags / Count GPPaths Compter combien des flags/chemins donnés sont levés et comparer.
Check GPCounter Comparer un compteur à un chemin par rapport à une valeur.
Dialogue Done (2026) Les dialogues/subdialogues donnés ont-ils été joués ? — le remplacement ci-dessus, conscient de la disposition.
Dialogue Done Counted (2026) Comparer combien de fois un dialogue/subdialogue a été joué.

La liste déroulante des conditions sur une ligne de Reply

GPFlags vs. GPPaths

Les deux familles font le même travail avec une ergonomie différente. Les conditions GPFlags prennent un chemin parent partagé plus un conteneur de tags feuilles — pratique pour tester plusieurs valeurs sous le même chemin en une seule condition. Les conditions GPPaths prennent un tableau de chemins complets — pratique lorsque les éléments que vous testez résident dans des endroits sans lien. Les deux exposent une bascule Exact Match (activée par défaut) et, héritées de la classe de base ULBRCondition, un Comment en texte libre ainsi que Cache Success / Cache Failure (un résultat positif/négatif court-circuite la suite de l'évaluation).

Condition d'affichage vs. de disponibilité

Une Reply expose deux emplacements de condition, et la différence compte :

Emplacement Effet lorsque la condition est fausse
Display Condition La reply n'est pas du tout proposée — ni affichée, ni sélectionnable.
Availability Condition La reply est affichée, mais grisée et non sélectionnable.

Une condition Dialogue Done sur une Reply

Verrouiller une reply derrière une première partie

Placez une ULBRDialogueDoneCondition sur la Display Condition d'une reply, en visant le tag du dialogue courant. Au premier passage, le dialogue n'est pas encore Done, donc la reply est masquée ; rejouez le dialogue et — maintenant que Done est levé — la reply apparaît.