Scénarios runtime — configuration, réponses et évaluation
ButRôle dedu cettesystème pageScenario
Le projetsystème contientScenario troisgère systèmesle prochescontenu visuellementd'introduction/contexte, maisles techniquementquestions, distinctsles réponses de segments et leur évaluation. Il ne doit pas être confondu avec :
- Tutoriels —
SystèmeguidesResponsabilitéinteractifsSourcequidemontrentvérité
evaluation.scenariodes ScenarioPagesuserDetails.scenarioStepuserDetails.
.playableTimelineBaseNe pas utiliser un ScenarioStep comme stockage d'une réponse métier et ne pas utiliser une Timeline comme mécanisme de progression principal. Chaque système a son lifecycle propre.
Architecture Scenario
flowchart LR
SD[Static data / ScenarioPages] --> UI[ScenarioPanelControlBase]
UI --> Q[Questions / Segment grid]
Q --> SAVE[SaveScenarioAnswers / SaveEvalSegment]
SAVE --> EVAL[(evaluation.scenario)]
EVAL --> COMPUTE[compute_scenario_score / compute_segment_score]
COMPUTE --> SCORE[evaluation.scores]
SCORE --> CORRECT[ScenarioEval / corrected UI]
UI --> STEP[eFinishQuestions]
STEP --> USER[(userDetails.scenarioStep)]
Modèles principaux
Assets/Src/Game/Data/Scenario.cs
ScenarioAnswer.cs
ScenarioEval.cs
goodScenarioAnswersnombre goodSegmentAnswersScenarioPages.cs
ScenarioPanelControlBase.cs
ScenarioSegGridControlBase.cs
EvalCorrectedPopupControl.cs
ScenarioPages : configuration des pages
ScenarioPages est un ScriptableObject. Chaque PagesDefinition contient :
headerText— UID/base du titre;;segGridUid— segment grid associé si applicable;;pages[]— sous-pages de contenu;;questionsStartIndex;numQuestions.
Chargement By Year
StaticDataByYearController.Initialize() charge :
ScenarioPages = Resources.Load<ScenarioPages>(
$"data_{GameUid}/ScenarioPages"
);
LeCe nomchargement etest lefait chemindans de l'asset sont donc contractuels.StaticDataByYearController.Initialize().
Chargement By Module
StaticDataByModuleController construit un asset par module :
string moduleUid = moduleByUid.Key.SnakeCaseToPascalCase();
ScenarioPages scenarioPages =
Resources.Load<ScenarioPages>(
$"data_{GameUid}/{moduleUid}ScenarioPages"
);
Exemple : un module bank_reconciliationStaticDataByModuleControllerchercheracharge donc un asset dontScenarioPages ledistinct nom suit la conversion PascalCase du module. Si l'asset n'est pas trouvé, lepar module n'estlorsqu'il simplement pas ajouté à ScenarioPages.existe.
Créer ou modifier un Scenario de A à Z
1. Définir le contenu et les réponses attendues
Les réponses des questions utilisentsont stockées sous des UIDs du typecomme question_Nquestion_0., Lesquestion_1, bonnesetc. Vérifier que questionsStartIndex et numQuestions correspondent aux réponses viennentstatiques de la static data chargée par le jeu. Si une question est ajoutée, vérifier que son UID est cohérent avec l'index utilisé par PagesDefinition.questionsStartIndex/numQuestions.attendues.
2. Modifier le bon ScenarioPages asset
Ajouter
ScenarioPagesdata_<gameUid>/ScenarioPagesdata_<gameUid>/<ModuleUid>ScenarioPages.
L'inspecteur custom ScenarioPagesEditor.cs masque la référence de grillesegment grid lorsque useSegmentsInScenario est désactivé.false.
3. Ajouter les textes
Ajouter les textestitres, deparagraphes, page, questions, choixquestions et titreschoix via le pipeline de localisation. Ne pas hardcoder le textecontenu dans ScenarioPanelControlBase.
4. Ajouter un renducomposant spécifiquegame-specific si nécessaire
ByLe Yearsocle possèdeScenario est commun, mais certains jeux possèdent des contrôlesrendus game-specific commepropres :
- MK1/MK2/HR1 :
ScenarioSegGridControldans MK1/MK2/HR1;; ScenarioSegmentsTableMgt1,MGT1ScenarioEmployeesTableMgt1,:ScenarioFeatureTableMgt1,tablesScenarioDisciplinaryTableMgt1.scenario spécialisées pour segments, employés, features et discipline.
SiUn lenouveau scénariorendu nemétier peutreste pas être exprimé avec les composants communs, créer le contrôle danssous Assets/GameSpecific/<gameUid>/Src/UI, pas dans Commun..
5. Vérifier la génération runtime des pagesInitRuntimePages()
ScenarioPanelControlBase.InitRuntimePages() combineconstruit les pages pagesDefinitionsavecruntime. En By Year, il peut insérer les segments dudans jeula séquence lorsque useSegmentsInScenario est actif. LoadPageDefInContent() instancie ensuite les éléments UI.activé.
6. Sauvegarder les réponses
Lors de la validation, OnConfirmValidateAnswers() construit les nouveaux question_N puis appelle :
flowchart LR
UI[ScenarioPanelControlBase] --> STUD[Student.SaveScenarioAnswers]
STUD --> BE[BackEnd.SaveScenarioAnswers]
BE --> CMD[_command/save_scenario_answers]
CMD --> E[(evaluation.scenario.answers)]
save_scenario_answers.js merge uniquement les réponsesclés reçues dans evaluation.scenario.answers, puis sauvegarde le document evaluation.
7. Sauvegarder les segments
Les réponses de segment passent par SaveEvalSegment →appelle _command/save_eval_segment. CeLe script :
evaluation.scenario[segmentUid]evaluation;
envoie au professeur un message get_data qui devient update_eval.
8. Calculer leles scorescores
Le client appelle les commandes game-specific :
<gameUid>_compute_scenario_score;<gameUid>_compute_segment_score.
Ces wrappersElles délèguent au moteur Eval<Game> correspondant,, sauvegardent le score dans evaluation.scores, puis retournent unles stubinformations utilisées par ScenarioEval.
9. Marquer la fin du Scenario
Une fois la séquence terminée, le client appelle :
Game.Student.SetScenarioStep(
Scenario.eScenarioStep.eFinishQuestions
);
eFinishQuestions doit rester la valeur 1.1. Le commentaire dansdu code indique que le serveur suppose explicitement cette valeur.Scenario.cs
10. Tester la correction
Tester GetScenarioEvaluation, ScenarioEvalScenarioEval.Deserialize() et l'UI corrigée. Une nouvelle question n'est pas terminée tant que, la vue professeur/correctioncorrigée saitet l'afficher.la vue professeur. Ajouter une question sans corriger ce chemin laisse la feature incomplète.
ScenarioStep :— progression,progression pas évaluationpersistée
Scenario.eScenarioStep est un enum de flags.flags stocké sous forme de bitmask. Il suit la progression utilisateur, pas les réponses de Scenario.
Exemples actuels :
eFinishQuestions;eFinishFirstProduct;eStudentAfterFirstResults;eFinishFirstRealLaunch;eStudentFirstModule;eStudentFirstCorrection;eByModuleHomepageTutorial;eStudentFirstFormula;eStudentFirstFormulaActivation.
Ajouter un nouveau ScenarioStep
- Ajouter un nouveau bit libre
dansavecScenario.eScenarioStep1 << n. - Ne jamais
changerrenuméroterla valeur d'un ancien bit déjà persisté. - Tester
IsScenarioStepDone()avantdeledéclencherflowla séquence.concerné. - À la fin, appeler
SetScenarioStep(). - Si la
featureséquence peut être rejouéemanuellement,volontairement, décider siUnSetScenarioStep()est nécessaire.
User.SetScenarioStep() OR le nouveau bit dans le masque local puis appelle _scenarioFlag_command/set_scenario_step, qui persisteécrit le masque complet dans userDetails.scenarioStep.
States liés au Scenario
StudentLoadScenarioStatecharge l'évaluation avant l'entrée dans le scénario.évaluation;StudentPlayScenarioByYearStateattendeFinishQuestionsavantpuisdechoisitpasserleàstatel'équipe / activité.suivant;StudentPlayScenarioByModuleStatedéclenchelance le welcome By Module puis attend le même flag.
Si
Scenario unréouvert nouveau scénario bloqueaprès le flow, debugger d'abord le flag eFinishQuestions et le state courant avant de chercher un problème de navigation UI.
Architecture Timeline
flowchart LR
TRIGGER[Controller / UI / State] --> TL[Timeline subclass]
TL --> BASE[TimelineBase.Play]
BASE --> CAM[Switch cameras + block raycasts]
BASE --> DIR[PlayableDirector.Play]
DIR --> STOP[PlayableDirector.stopped]
STOP --> RESTORE[TimelineBase.Stop]
RESTORE --> CB[onFinish]
TimelineBase : contrat commundébut
TimelineBaseScenarioPopupControlgère :
uiCameraoffscreenCamerascene3dRootScenarioPanelControl. En By Module, ScenarioWindowControl adapte les titres selon le PlayableDirector.stoppedStop()onFinishTimelines existantes
CreateProductTimelineYearResultsTimelineFirstModuleTimelineModuleResultsTimelineTimelinesMk1ENT1 LaunchProductTimelineCréer une nouvelle Timeline de A à Z
1. Décider si une Timeline est réellement nécessaire
Utiliser Timeline pour une séquence audiovisuelle qui doit bloquer temporairement l'UI. Pour un guide interactif qui montre des contrôles et attendses des clics, utiliser le système Tutoriels.
2. Créer l'asset .playable
Créer/configurer l'asset Unity Timeline dans le dossier Resources approprié, puis créer les tracks/animations nécessaires.
3. Créer une classe héritant TimelineBase
public class MyTimeline : TimelineBase
{
public static MyTimeline Instance;
[SerializeField] private PlayableDirector director;
private void Awake()
{
Instance = this;
}
public void PlaySequence(string message, Action onFinish)
{
character3dText.text = message;
Play(onFinish, director);
}
}4. Brancher toutes les références Unity
Le prefab/scene doit fournir au minimum les caméras, scene3dRoot, canvas group, screen material/transform, texte 3D et le PlayableDirector.
5. Choisir le déclencheur
Exemples réels :
DecisionsControlByModuleFirstModuleTimelineDecisionsControlByModuleModuleResultsTimelineStudentStateYearResultsTimelineCreateProductTimeline6. Décider comment empêcher le replay
Une Timeline n'a pas elle-même de persistance. Utiliser selon le cas :
ScenarioStepSetYearSequenceSeen(year)UserState.ModuleUserState.STUDENT_RESULTS7. Toujours terminer le flow dans onFinish
La logique qui ouvre l'écran suivant, change le state ou marque la progression doit être appelée après la fin de la Timeline. Ne pas supposer que Play() est synchrone.
Cas spécial ModuleResultsTimeline
ModuleResultsTimeline peut être déclenchée avant que les résultats soient arrivés. Elle s'abonne alors à Game.CurrentTeamByModule.onNewTeamModuleResults, filtre sur le module/type, puis joue la séquence lorsque les données existent.
Si une nouvelle Timeline attend un RTT ou une donnée backend, reprendre ce pattern : abonnement temporaire, filtre strict, désabonnement dès réception.settings.
Debug Scenario
| Symptôme | Vérifier |
|---|---|
| Page absente | Nom/path ScenarioPages, pagesDefinitions, |
| Question |
question_N → SaveScenarioAnswers → evaluation.scenario.answers. |
| Segment non mis à jour chez le prof | save_eval_segment → get_data/update_eval. |
| Score |
Eval<Game>. |
| Étudiant bloqué après |
userDetails.scenarioStep eFinishQuestions |
| Correction vide | get_scenario_evaluation, , corrected popup. |
Debug Timeline
TimelineBase.Stop()stoppedUICanvasGroup.blocksRaycastsdirector.stoppedRankingsDatascale_16_9scale_4_3Checklist avant merge
Scenario : asset ResourcesScenarioPages au bonnom.nom/path;Scenario :textes FR/EN présents.EN;Scenarioréponses: réponse sauvegardéesauvegardées puisreluerelues aprèsrelog.relog;Scenariosegments:synchronisésscore game-specific correct.professeur;Scenarioscore:scenariocorrection professeur/étudiant correcte.correct;ScenarioStepscore:segmentsaucuncorrectbitsihistorique renuméroté.utilisé;Timelinecorrection: prefab/director/caméras référencés.correcte;TimelineeFinishQuestions:déclencheUIlerestauréebonà la fin.state;Timelineaucun:ancienconditionScenarioStepanti-replay persistée.renuméroté;TimelineBy:YearonFinish/continueBy Module testés selon leflow.