Quand et comment utiliser les fonctions Update(), FixedUpdate() et LateUpdate() de Unity 3D

Dans Unity 3D, si vous créez un script C# et que l’ouvrez pour l’éditer, le contenu par défaut du script est ainsi :

public class GameCodeClubBot : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

On a deux fonctions bien commodes et aux noms évocateurs : Start() qui se déclenche au début du script, et Update() qui se déclenche à chaque « mise à jour » du jeu.

C’est extrêmement pratique de disposer de Update(), une fonction « clé en main » qui s’exécute à chaque mise à jour. Les tutoriaux sur Unity que j’ai pu moi-même lire l’utilisent généralement sans parcimonie, et pour cause : comme Update() donne un accès direct au cycle de mise à jour du jeu, c’est une fonction très pratique… Mais très puissante.

Du coup, c’est un cadeau un peu empoisonné : on est tenté d’y mettre une grosse partie du code du jeu, d’y vérifier tout ce qui s’y passe. Mais ça peut créée plusieurs problèmes potentiels :

  1. Si des calculs gourmands sont exécutés à cet endroit, ils le seront en permanence, avec des effets sur les performances au bout du compte.
  2. Si certains effets sont appliqués dans une fonction inadaptée, le résultat dans le jeu pourra être incohérent, voire bloquant. Source potentielle de bugs dans votre jeu.
  3. Il y a de la concurrence entre les fonctions Update et FixedUpdate (et entre LateUpdate et FixedUpdate) :

J’ai fait un petit test avec les 3 fonctions Update d’Unity : je leur ai demandé d’écrire dans la console, avec un petit compteur qui va bien, et j’ai laissé tourner 10 secondes. En 10 secondes, Update() et LateUpdate() sont appelées 95001 fois chacune, tandis que FixedUpdate() est appelée 544. Bien sûr le nombre d’appels dépend de la machine, de ce qui est présent sur la scène, etc. Mais imaginez que vous demandiez à votre programme de faire mille fois par secondes des vérifications ou calculs importants,

Unity3D : occurrence des fonctions Update(), LateUpdate() et FixedUpdate() en 10 secondes

Un cycle fixe et un cycle de rendu

La logique Unity fonctionne en fait sur deux cycles concurrents.

Leur fonctionnement est tel qu’il peut y avoir zéro ou plusieurs Update() qui s’exécutent le temps d’une seule FixedUpdate(), ou inversement.

Savoir comment elles s’exécutent, c’est la clé pour pouvoir les faire fonctionner en harmonie.

Le cycle fixe / FixedUpdate()

C’est dans ce cycle qu’est calculée et appliquée toute la physique du jeu (gestion des collisions, de la gravité…). Ce cycle s’exécute de manière régulière, déterministe, en suivant un minuteur fixe de temps réel, et selon la valeur de la variable statique Time.timeScale.

C’est à chaque mise à jour de ce cycle que se déclenche FixedUpdate(). C’est aussi dans ce cycle que se déclenchent les évènements OnTriggerXXX() et OnCollisionXXX(), puisqu’ils dépendent du moteur physique.

Le cycle de rendu (ou de frames) / Update() + LateUpdate()

Ce cycle s’exécute à intervalles réguliers, en fonction des images à calculer et à montrer à l’utilisateur. Dès que le moteur calcule ce qu’il doit montrer à l’écran. Ainsi, il peut y avoir plus ou moins de répétitions de ce cycle selon la puissance de la machine, selon la quantité de choses à calculer pour le rendu, etc.

C’est aussi là qu’est calculée toute la logique du jeu. Il y a ici deux fonctions : Update() suivie par LateUpdate().

L’utilité de LateUpdate() est qu’elle s’exécute juste avant le rendu de la frame. Donc elle peut être utile pour faire les opérations finales avant le rendu, typiquement modifier la position d’un objet affecté par une animation, ou caler la caméra du jeu.

Du bon usage des fonctions Update() et LateUpdate() et FixedUpdate()

Le premier bon réflexe, c’est de ne pas faire de vérification ou d’opérations en permanence dans Update() ou FixedUpdate(). Avant de placer quelque chose dans une de ces fonctions, il faut se poser la question :

À quels moments ai-je besoin de vérifier telle donnée ?
À quelle fréquence mon programme doit-il effectuer telle action ?

Par exemple, la vie du joueur n’est mise à jour que si son personnage prend des dégâts ou utilise un objet de santé. C’est à ces moments là qu’on peut vérifier où en est son niveau de vie, pour déclencher un Game Over le cas échéant.

Pour une sauvegarde, on peut la faire à la demande, dans le menu, et compléter par une sauvegarde automatique qui se déclenche à intervalles réguliers. Intervalles qui peuvent être contrôlés soit dans Update, soit en répétant un comportement (Coroutine, InvokeRepeating()…)

Nous pouvons résumer l’usage des fonctions comme suit :

FonctionCycleQue faut-il y mettre ? (exemples)
Update()Rendu / framesGestion des entrées (input)
Logique « permanente » du jeu
Animation d’éléments de l’UI*
LateUpdate()Rendu / framesGestion des caméras
Déplacement d’éléments sur l’interface
FixedUpdate()Fixe / temporelAppliquer des effets sur la physique du jeu (appliquer une force sur un Rigidbody)
« À la demande »AucunVérification des différentes données du jeu (vie du joueur, vie d’un ennemi…)
Sauvegardes automatique régulières

*La fonction OnGUI() est précisément conçue pour la mise à jour d’éléments de l’interface utilisateur, il est donc conseillé de l’utiliser plutôt que Update()

Il s’agit juste d’indications de base, car comme c’est souvent le cas, il y a plusieurs façons de faire. En gardant à l’esprit ces quelques principes, vous éviterez certains problèmes. Et si vous travaillez à plusieurs sur le code d’un projet, se mettre d’accord sur l’utilisation de ces fonctions vous permettra d’éviter d’obtenir du code qui se contrarie lui-même !

Pour un système de timer qui se met à jour de manière régulière, il peut être appelé dans FixedUpdate() pour profiter du temps fixe, ou alors dans Update(), mais il faudra alors multiplier les valeurs par Time.deltaTime (qui correspond à l’intervalle en secondes entre la dernière frame et la frame actuelle).

Pour aller plus loin : Unity 3D et les fonctions event

Les fonctions event d’Unity fonctionneront dans votre script dès l’instant où il hérite de MonoBehaviour. Le principe de ces fonctions est que Unity donne le contrôle à votre script (donc, il appelle les fonctions et les exécute) selon des critères précis, des events.

Ce n’est pas une particularité d’Unity : beaucoup de programmes fonctionnent sur ce principe d’évènements, et l’usage est de nommer ces fonctions OnQuelqueChose(). Ainsi, un évènement OnMouseClick() se déclenche lorsque l’utilisateur clique.

Certaines fonctions de MonoBehaviour suivent ce format (OnEnable(), OnDisable(), OnTriggerEnter()…), mais les fonctions Start(), Update(), FixedUpdate()… sont aussi des fonctions évènement.

La documentation officielle d’Unity 3D indique l’ordre d’exécution des différents évènements. Outre les fonctions Update() / FixedUpdate(), vous y trouverez un schéma avec tous les évènements liés au moteur physique, au moteur de rendu, et même les yield, qui permettent de faire des Coroutines, c’est à dire fonctions asynchrones qui restent en suspens jusqu’à ce que certains critères soient réalisés (X secondes, fin du rendu d’une frame, fin d’un cycle FixedUpdate…).

C’est une ressource très précieuse, qui peut vous permettre de savoir pourquoi des comportements se déclenchent ou non.

Laisser un commentaire