Tutoriels — création de A à Z et architecture
ScénariosBut de cette page
LeCette projetpage possèdeexplique descomment structurescréer, déclencher, persister, tester et déboguer un tutoriel Unity dans UnikSim. Elle ne décrit pas Timeline : ce sont deux systèmes séparés dans le code.
Choisir le bon type de scénariotutoriel communesavant dansde coder
Assets/Src/Game/Data/Scenario*GenericTutorial. Ne pas créer une nouvelle classe.
Forcer l'utilisateur à cliquer sur un vrai bouton/toggle et enchaîner plusieurs étapes
Créer une Assets/Src/UI/Scenario*ScenarioPages
Les contrôleurshérite de staticMenuTutorialSequence.
ScenarioPagescommun StaticDataByModuleController.Initialize()data_<GameUid>/<ModuleUid>ScenarioPagesTutoriels
Assets/Src/Game/Tutorial/.
Assets/Src/UI/SimByModule/Tutorial/.
Assets/GameSpecific/<gameUid>/Src/Tutorial/ ou Scenario.eScenarioStep, puis appeler SetScenarioStep() lorsque le tutoriel UserState plutôt qu'ajouter automatiquement un ScenarioStep.
Architecture réelle
flowchart TD
TRIGGER[GameState / UI / événement] --> SEQ[MenuTutorialSequence]
SEQ --> CHARACTER[TutorialCharacter]
SEQ --> LOOKUP[TutorialUIObject.GetUiObject]
LOOKUP --> TARGET[RectTransform actif]
SEQ --> HIGHLIGHT[TutorialSquareHighlight]
SEQ --> ARROW[TutorialArrow]
SEQ --> CLICK{Type d'attente}
CLICK -->|clic overlay| CATCHER[TutorialClickCatcherControl]
CLICK -->|vrai bouton| BUTTON[TutorialUIButton.clicked]
SEQ --> DONE[SetScenarioStep / SetUserState]
DONE --> STOP[Status.Stop]
STOP --> CLEAN[cleanup automatique]
Composants à connaître
TutorialControl
Singleton contenant character, highlight, blocker, click catcher, arrow et bouton Skip.
MenuTutorialSequence
Classe de base des tutoriels complexes. Gère Start/Stop, nettoyage et helpers.
GenericTutorial
Tutoriel simple “message + highlight éventuel + clic pour continuer”.
TutorialUIObject
Enregistre un RectTransform actif sous un UID afin qu'un tutoriel puisse le retrouver.
TutorialUIButton
Étend TutorialUIObject et détecte les clics d'un Button, BasicButton ou Toggle.
TutorialSquareHighlight
Cadre/masque visuel autour d'une cible.
TutorialArrow
Place la flèche relativement à la cible.
TutorialClickCatcherControl
Overlay cliquable utilisé par WaitForClick().
TutorialInputBlockerControl
Permet de bloquer une partie ou la totalité des inputs.
Créer un tutoriel de zéro — procédure complète
1. Décider ce qui déclenche le tutoriel
Commencer par le trigger, pas par la classe du tutoriel. Exemples réels :
StudentState.TransitionIn() lance FirstDecisionTutorial selon l'état de l'TeacherCreateSimState lance le tutoriel de création de simulation;
TeacherChooseTutorialPopupControl permet de rejouer manuellement certains tutoriels;
une validation UI peut lancer directement un GenericTutorial.
Déterminer également si le tutoriel doit être joué une seule fois. Si oui, choisir son mécanisme de persistance avant d'unécrire prefab,le d'uncontenu.
2. Rendre les éléments UI trouvables par le tutoriel
Pour chaque élément à surligner ou d'unsuivre, state.
Timelines
Assets/Src/Game/Timeline/ contientouvrir le socle,prefab tandis que des jeux fournissent leurs propres assets Timelinecorrespondant dans Unity et ajouter au GameObject possédant le GameSpecific/<gameUid>/Resources/TimelineTutorialUIObjectRectTransform. MK1/MK2Dans utilisentl'Inspector, remplir le champ uiObejctName avec un UID stable, par exemple des:
create_product_button
calculator_button
cash_label
my_new_feature_button
Le tutoriel pourra ensuite récupérer la cible avec :
RectTransform target = TutorialUIObject.GetUiObject("my_new_feature_button");
Important : TutorialUIObject s'enregistre dans le dictionnaire statique uniquement dans OnEnable(). Si le GameObject/prefab/panel n'est pas actif, GetUiObject() retourne null. Il faut alors ouvrir le panel avant le lookup ou attendre jusqu'à ce que la cible existe.
Si plusieurs variantes d'UI peuvent représenter la même étape, utiliser :
string[] candidates = { "button_practice", "button_summative", "button_consultation" };
RectTransform target = TutorialUIObject.GetFirstUiObject(candidates);
Éviter les UID dupliqués sans raison : le dictionnaire garde une liste mais GetUiObject() retourne le premier élément actif. Deux objets actifs portant le même UID peuvent donc viser la mauvaise cible.
3. Si l'utilisateur doit cliquer sur la vraie cible, utiliser TutorialUIButton
Un simple TutorialUIObject permet de création/lancementtrouver/surligner l'objet, mais il ne sait pas si l'utilisateur l'a activé. Pour une étape qui doit attendre un vrai clic, utiliser TutorialUIButton.
Dans l'Inspector, il faut assigner explicitement l'un des champs correspondant au contrôle réel :
button pour un UnityEngine.UI.Button;
basicButton pour un BasicButton;
toggle pour un Toggle.
La classe ne fait pas automatiquement un GetComponent<Button>(). Si aucune référence n'est assignée, clicked ne passera jamais à true.
Pattern réel :
RectTransform target = TutorialUIObject.GetUiObject("my_new_feature_button");
TutorialUIButton tutoButton = target.GetComponent<TutorialUIButton>();
while (!tutoButton.clicked)
{
ShowSquareHighlight(target);
ShowArrow(target);
yield return Status.Running;
}
HideArrow();
RemoveSquareHighlight();
4. Ajouter les textes
Les textes doivent normalement passer par le système de produitlocalisation existant. Ajouter les UID FR/EN dans la Google Sheet appropriée puis utiliser le pipeline documenté dans Textes, localisation et demise résultats.à jour.
Checklist
Dans le code :
ShowCharacter(
Game.Texts.GetText("my_tutorial_step_1"),
TutorialCharacter.eCharacterPos.eTop,
true
);
GenericTutorial accepte soit un UID existant, soit une chaîne déjà résolue. Il teste Game.Texts.HasText() avant de modificationrésoudre
Identifierle texte.5. Créer la classe pour un tutoriel multi-étapes
Créer une classe qui hérite de
scénario/tutoriel.IdentifierMenuTutorialSequenceet surchargerContent(). Le moteur est une séquence coroutine : tant qu'une étape attend quelque chose, retournerStatus.Running; à la fin, retournerStatus.Stop.using System.Collections.Generic; using UnityEngine; namespace UnikSim { public class MyFeatureTutorial : MenuTutorialSequence { public override IEnumerator<Status> Content() { ShowCharacter( Game.Texts.GetText("my_feature_tuto_1"), TutorialCharacter.eCharacterPos.eTop, true ); WaitForClick(0.1f); while (!_isClicked) { yield return Status.Running; } RectTransform target = null; while (target == null) { target = TutorialUIObject.GetUiObject("my_feature_button"); yield return Status.Running; } TutorialUIButton tutoButton = target.GetComponent<TutorialUIButton>(); ShowCharacter( Game.Texts.GetText("my_feature_tuto_2"), TutorialCharacter.eCharacterPos.eTop, false ); while (!tutoButton.clicked) { ShowSquareHighlight(target); ShowArrow(target); yield return Status.Running; } HideArrow(); RemoveSquareHighlight(); RemoveCharacter(); Game.User.SetScenarioStep(Scenario.eScenarioStep.eMyFeatureTutorial); yield return Status.Stop; } } }6. Comprendre WaitForClick : ce n'est PAS un clic sur la cible
WaitForClick()activeTutorialClickCatcherControl, un overlay global. L'utilisateur clique sur l'overlay pour continuer. Ce helper est donc parfait pour une étape informative, mais pas pour exiger qu'un bouton métier soit réellement pressé.Comportement voulu À utiliser “Lis ce message puis clique n'importe où pour continuer”
WaitForClick()+_isClicked“Clique exactement sur ce bouton pour continuer”TutorialUIButton.clicked“Attends quelques secondes”WaitForTime()+TimeIsReached7. Choisir la bonne caméra
Les helpers utilisent une caméra pour convertir le
prefabRectTransformen position écran.- UI normale :
Game.Instance.UICamera— utilisé par défaut;
Game.Instance.PopupCamera;
certains cas game-specific peuvent fournir explicitement une autre caméra.
ShowSquareHighlight(target, Game.Instance.PopupCamera);
ShowArrow(target, Game.Instance.PopupCamera);
Un highlight décalé ou assetune ScriptableObject/Timelineflèche associé.
8. Déclencher le tutoriel
Le lancement standard est :
new MyFeatureTutorial().Start();
Pour un tutoriel one-shot :
if (!Game.User.IsScenarioStepDone(Scenario.eScenarioStep.eMyFeatureTutorial))
{
new MyFeatureTutorial().Start();
}
Le meilleur endroit est généralement l'endroit où l'état nécessaire vient d'être atteint : un GameState, l'ouverture d'un panel, la création d'une donnée ou la réception d'un résultat. Ne pas lancer un tutoriel dans Update() sans garde : il serait recréé continuellement.
9. Ajouter un ScenarioStep si le tutoriel doit être mémorisé
Scenario.eScenarioStep est un enum [Flags]. Chaque valeur représente un bit. Les valeurs existantes utilisent 1 << n.
Pour ajouter une nouvelle étape persistante :
eMyFeatureTutorial = 1 << 18
Prendre le prochain bit libre et ne jamais réutiliser un bit existant. eFinishQuestions = 1 doit rester égal à 1 : le commentaire du code indique que le serveur dépend de cette valeur.
À la fin réelle du tutoriel :
Game.User.SetScenarioStep(Scenario.eScenarioStep.eMyFeatureTutorial);
SetScenarioStep() ajoute le bit au masque local puis, par défaut, appelle Game.Backend.SetScenarioStep() pour sauvegarder le masque côté backend.
Si le statut doit être propre à une simulation plutôt qu'au compte utilisateur global, examiner User.SetUserState(simUid, stateUid) / IsUserStateDone() au lieu d'utiliser un nouveau ScenarioStep.
10. Cas simple : ne PAS créer une nouvelle classe
Pour un message de validation ou une aide one-shot sans workflow :
new GenericTutorial(
"not_enough_budget_body",
"games_budget",
TutorialCharacter.eCharacterPos.eDown
).Start();
Avec popup :
new GenericTutorial(
"loan_impossible",
"loan_max",
TutorialCharacter.eCharacterPos.eTop,
Game.Instance.PopupCamera
).Start();
Pour chaîner les options de GenericTutorial, utiliser StartGeneric() :
new GenericTutorial(
"not_enough_budget_body",
"games_budget",
TutorialCharacter.eCharacterPos.eDown
).StartGeneric().ShowFlashing();
Ce que MenuTutorialSequence fait automatiquement
Start() arrête le tutoriel actuellement actif avant de démarrer le nouveau. Stop() nettoie character, square highlight, arrow, input blocker, click catcher et bouton Skip.
Le bouton Skip est activé par défaut pour le professeur via Game.User.IsTeacher. Certains tutoriels “force watch”, comme la création initiale de simulation, le désactivent explicitement.
Exemples du code à copier selon le besoin
Assets/Src/Game/Tutorial/Student/FirstDecisionTutorial.cs
Tutoriel By Module avec plusieurs cibles alternatives
FirstDecisionByModuleTutorial.cs
Long tutoriel professeur composé de sous-séquences
TeacherCreateSimTutorial.cs
Extension spécifique By Module
TeacherCreateSimTutorialByModule.cs
Tutoriel game-specific complexe
GameSpecific/mk1/Src/Tutorial/CreateFirstProductTutorial.cs
Tutoriel Accounting complexe
GameSpecific/acc1/src/AccGeneralTutorials.cs et GenericAcc1Tutorial.cs
Aide/validation très simple
Rechercher new GenericTutorial dans les contrôles UI.
Checklist avant de considérer le tutoriel terminé
TutorialUIObject stable.
Chaque étape qui exige un vrai clic possède un TutorialUIButton correctement branché au Button/BasicButton/Toggle.
Les cibles existent réellement au moment du lookup; sinon le code attend leur apparition.
La bonne caméra est utilisée pour chaque popup/panel.
Le trigger ne peut pas relancer le tutoriel en boucle.
Le ScenarioStep/UserState n'est marqué terminé qu'après la Debug — symptômes fréquents
GetUiObject() retourne null
Prefab/panel inactif, UID erroné, ou composant non enregistré.
Le tutoriel attend éternellement un clic
TutorialUIButton absent ou sa référence button/basicButton/toggle n'est pas assignée.
Highlight/flèche décalé
Mauvaise caméra, souvent UICamera utilisée sur une popup.
Le clic sur la cible ne fait rien
Le code utilise WaitForClick() alors qu'il devrait attendre TutorialUIButton.clicked.
Le tutoriel rejoue à chaque login
ScenarioStep/UserState jamais sauvegardé.
Le tutoriel est SetScenarioStep() est appelé au démarrage au lieu de la fin.
La mauvaise cible est surlignée
Plusieurs objets actifs utilisent le même uiObejctName.
Un nouveau tutoriel coupe l'ancien
Comportement normal : MenuTutorialSequence.Start() arrête le tutoriel courant.
Règle de conception
Un tutoriel ne devrait pas dupliquer la logique métier. Il doit observer l'UI et repriseguider del'utilisateur. simulation.