Timelines Unity — architecture et création de séquences

Rôle des Timelines Unity

Les Timelines sont des séquences audiovisuelles Unity basées sur PlayableDirector. Elles servent à présenter une animation de résultats, de première ouverture ou de lancement produit tout en bloquant temporairement l'interaction normale avec l'UI.

Elles sont différentes des Tutoriels : un tutoriel guide activement l'utilisateur dans l'interface et attend souvent ses clics; une Timeline joue une séquence Unity et appelle un callback lorsqu'elle se termine.

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 commun

Le socle est Assets/Src/Game/Timeline/TimelineBase.cs.

TimelineBase gère :

Ce que Play() fait réellement

  1. active scene3dRoot;
  2. désactive la caméra UI;
  3. active la caméra offscreen;
  4. redirige le Canvas vers cette caméra;
  5. copie le viewport de la caméra UI;
  6. calcule le scale horizontal entre les valeurs 4:3 et 16:9;
  7. bloque les raycasts UI;
  8. lance le PlayableDirector;
  9. s'abonne à director.stopped.

Lorsque le director s'arrête, OnPlayFinish() se désabonne, appelle Stop(), puis déclenche le callback.

Timelines communes existantes

Classe Responsabilité
CreateProductTimeline Séquence générique après création/lancement d'un premier objet métier.
YearResultsTimeline Animation de résultats annuelle By Year avec graph, profit/perte et anti-bankruptcy.
FirstModuleTimeline Première ouverture d'un module By Module.
ModuleResultsTimeline Animation de correction/résultats d'un module summative.

Timelines game-specific

Les jeux peuvent spécialiser le socle :

Une Timeline qui dépend d'une structure game-specific doit rester dans le dossier du jeu, pas dans le socle commun.

Assets .playable

Une Timeline Unity est composée de deux parties :

Changer uniquement le C# sans vérifier l'asset peut laisser des bindings cassés. Changer uniquement le .playable nécessite aussi de vérifier que le prefab/scene fournit toutes les références utilisées par les tracks.

Créer une Timeline de zéro — procédure complète

1. Vérifier qu'une Timeline est le bon outil

Utiliser une Timeline si le besoin est principalement une séquence visuelle/animée. Pour guider un utilisateur vers un bouton, attendre un clic réel ou surligner une zone UI, utiliser le système Tutoriels.

2. Créer l'asset Timeline

Dans Unity :

  1. créer un asset Timeline .playable dans le dossier Resources approprié;
  2. ajouter les tracks nécessaires;
  3. configurer clips, animations et bindings;
  4. tester la séquence directement dans l'éditeur.

3. Créer la classe C#

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 les références du prefab/scene

Le composant hérité de TimelineBase doit avoir des références valides vers :

5. Choisir le bon point de déclenchement

Exemples réels :

6. Préparer les données avant Play()

Une Timeline ne devrait pas aller chercher arbitrairement toutes ses données elle-même. Le trigger prépare les valeurs nécessaires, puis les passe à la Timeline.

Exemple YearResultsTimeline :

7. Gérer la fin dans onFinish

Le code qui ouvre l'écran suivant ou change la progression doit être exécuté dans le callback.

MyTimeline.Instance.PlaySequence(
    message,
    () =>
    {
        OpenNextPanel();
    }
);

Ne pas écrire :

MyTimeline.Instance.PlaySequence(message, null);
OpenNextPanel(); // s'exécute immédiatement

Empêcher une Timeline de rejouer

TimelineBase ne persiste aucun état par lui-même. Le mécanisme anti-replay dépend du contexte.

Cas Mécanisme
Première ouverture de module Scenario.eScenarioStep.eStudentFirstModule.
Résultats annuels SetYearSequenceSeen(yearId).
Résultats d'un module UserState.ModuleUserState.STUDENT_RESULTS.
Premier produit / premier launch Condition métier + éventuellement ScenarioStep.

FirstModuleTimeline

FirstModuleTimeline.PlayFirstModule() :

  1. injecte le texte;
  2. joue firstModuleDirector;
  3. à la fin, sauvegarde eStudentFirstModule;
  4. appelle le callback qui ouvre le module.

Le trigger est présent dans les contrôles By Module lorsque l'étudiant n'a jamais vu cette séquence.

ModuleResultsTimeline

Cette Timeline est plus complexe car les données peuvent ne pas être encore disponibles lorsqu'on veut la jouer.

Attente des résultats

Si TryGetTeamByModuleResult() ne trouve pas encore les résultats :

  1. la Timeline s'abonne à onNewTeamModuleResults;
  2. attend le bon actModuleUid / module type;
  3. se désabonne dès réception;
  4. prépare puis joue la séquence.

Ce pattern est à réutiliser lorsqu'une nouvelle Timeline dépend d'une donnée asynchrone : abonnement temporaire + filtre strict + désabonnement immédiat.

Choix du director

Le score moyen du module choisit entre :

Pause et report

ChangePlayableSpeed(false) met le root playable à vitesse 0 et affiche les options. La Timeline peut alors montrer les notes via reportControl.ShowGrades() avant de continuer.

YearResultsTimeline

PlayNewResults() :

  1. récupère le RankingsData de l'année;
  2. met à jour le whiteboard/graph;
  3. compare la valeur actuelle à l'année précédente ou à GraphFirstValue;
  4. détermine profit/perte;
  5. choisit le director;
  6. construit le texte localisé;
  7. marque la séquence annuelle comme vue via SetEndOfYearSequenceSeen(yearId).

Créer une Timeline avec plusieurs variantes

Pour un même événement avec plusieurs résultats visuels, garder une seule classe et plusieurs PlayableDirector lorsque les inputs/lifecycle sont identiques.

PlayableDirector director =
    score >= threshold
        ? goodDirector
        : badDirector;

Play(onFinish, director);

Créer plusieurs classes uniquement si les séquences ont des données et un lifecycle réellement différents.

Interaction avec les caméras et l'UI

TimelineBase.Play() désactive la caméra UI et empêche les raycasts. Un bug pendant la séquence peut donc laisser toute l'application visuellement ou fonctionnellement bloquée si Stop() n'est jamais exécuté.

Stop()

Stop() doit toujours rétablir :

Gestion du ratio d'écran

TimelineBase interpole le scale horizontal entre scale_4_3 et scale_16_9 selon Screen.width / Screen.height, puis clamp le résultat.

Si une séquence est correctement cadrée en 16:9 mais coupée sur un autre ratio, vérifier ces deux valeurs sur le prefab avant de modifier la caméra ou les clips.

Timelines game-specific

MK1

TimelinesMk1 hérite de YearResultsTimeline et ajoute un launchProductDirector ainsi qu'un whiteboard MK1.

ENT1

LaunchProductTimeline gère un whiteboard de lancement produit et ses propres directors de résultats.

Build et assets

Modifier :

nécessite un nouveau build Unity/WebGL.

Debug Timeline

Symptôme À vérifier
Écran noir après la séquence director.stopped a-t-il été déclenché ? Stop() a-t-il tourné ?
UI visible mais non cliquable UICanvasGroup.blocksRaycasts.
Timeline rejouée à chaque ouverture Mécanisme anti-replay non persisté.
Callback plusieurs fois Abonnement director.stopped ou event résultats non désabonné.
Mauvaise variante good/okay/bad Valeur métier passée à la Timeline et seuils de sélection.
Animation avec anciennes données Le trigger passe-t-il les données actuelles avant Play() ?
Résultats module jamais affichés onNewTeamModuleResults, filtres actModuleUid/moduleType.
Layout déformé scale_4_3, scale_16_9, screenTransform.
Playable ne joue pas Director / asset .playable / bindings Unity.

Checklist avant merge


Revision #1
Created 2026-09-18 20:29:34 UTC by Mathis
Updated 2026-09-18 20:29:35 UTC by Mathis