Par défaut, lorsque vous créez un nouveau script (quel que soit le langage), deux méthodes sont générées : Start et Update. Si on reste simple, Start pour l’initialisation du script et Update pour la boucle de jeu. Dans la plupart des cas, la méthode Update conviendra parfaitement et il est inutile d’aller chercher plus loin.
En revanche, il existe d’autres moyens de « faire tourner » votre logique de jeu : la méthode FixedUpdate, LateUpdate et l’utilisation de Coroutines. Je ne traiterai pas des Coroutines ici car elle méritent un article complet. Ces autres possibilités doivent être utilisées avec précaution car mal utilisées, elles peuvent induire des comportements inexplicables, des bugs, et la chute de cheveux du développeur qui va essayer de démêler tout ça…
Disclaimer
Cet article a été originalement écrit le 03/03/2014 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.
Le contexte
Commençons par expliquer le contexte. Unity est une boite noire dans laquelle nous allons placer des scènes dans lequelles évolueront des GameObjects. Ces GameObjects, on va leur attacher des scripts personnalisés qui vont constituer le coeur du jeu, la façon dont il se comportera. Pour les traiter, le moteur va parcourir la liste des GameObjects sur la scène courante, et va appeler des méthodes particulières sur tous les scripts attachés à ces derniers, à des moments précis : Start est appelé au début et une seule fois, alors qu’Update sera appelée régulièrement. Il en existe pas mal, la liste complète est disponible sur la documentation.
Parlons d’Update, de FixedUpdate et de LateUpdate. Mais pourquoi avoir plusieurs méthodes, une seule ça devrait suffire non ? Eh bien… non. Ce découpage vous sera peut-être inutile mais c’est indispensable dans certains cas que nous allons voir ensemble.
Update
Update est la méthode la plus utilisée, c’est pour ça qu’elle est générée par défaut lors de la création d’un nouveau script. Il faut noter que cette méthode est dépendante du framerate. Pour faire simple, plus la machine sur laquelle va tourner le jeu sera puissante, plus le nombre d’appels par seconde sera élevé. A l’inverse, sur une machine moins puissante, le nombre d’appels sera réduit.
C’est un détail qui passe tout à fait inaperçu sur un prototype, mais est crucial dans le cadre d’une production réelle (même petite). Attention, crucial ne veut pas dire qu’il ne faut pas utiliser cette méthode. Au contraire, utiliser autre chose (comme FixedUpdate) pourrait avoir des effets désastreux sur les performances. Il faut simplement l’utiliser en gardant ça en tête.
Dans quel cadre l’utiliser : la gestion de la logique de jeu. Oui c’est très vague. Considérez cette méthode comme le choix par défaut lorsqu’il y a un traitement à effectuer « régulièrement ».
FixedUpdate
Contrairement à son homologue, FixedUpdate n’est pas dépendante du framerate. Que votre jeu tourne sur une bête de course ou un Pentium 4, le nombre d’appels par seconde sera toujours le même. D’où le nom de FixedUpdate. Bon, ne vous faites pas d’illusions, mettre tout votre code dans FixedUpdate ne fera pas que votre jeu gourmand tournera partout. Au contraire, vous arriverez juste à faire freezer la pauvre machine un peu vieillotte.
Cette méthode est à utiliser pour les calculs de physique. En effet, si vous ne voulez pas avoir des comportements différents entre machines de différentes puissance, il est important de faire les calculs de physique à la même vitesse, quelle que soit la machine. Peu importe si le framerate est moins rapide, la physique, elle, doit être impeccable. Je cite la documentation :
This makes the simulation more consistent across different computers or when changes in the frame rate occur.
Ce temps fixe entre chaque appel est paramétrable dans l’éditeur, via la fenêtre Time :
Edit > Project Settings > Time
L’inspecteur vous affichera 3 champs :
- Fixed Timestep
- Maximum Allowed Timestep
- Time Scale
Le Time Scale est la vitesse d’écoulement du temps : à 1, le temps s’écoule « normalement », à 0 tout est arrêté… Et à 2 tout va 2 fois plus vite que la « normale ». Je mets ces termes entre guillemets car c’est une convention. « 1 » est la référence par défaut, mais vous pouvez très bien choisir que la vitesse de référence est 10 afin de pouvoir effectuer des slow motion plus précis par exemple.
C’est un nombre flottant, vous pouvez y mettre ce que vous voulez, y compris un nombre négatif. Je vous laisse imaginer ce que ça implique.
Nom de Zeus ! Marti ! Le convecteur Temporel !
Ce n’est en revanche pas magique. Le Time Scale influe sur la valeur qu’aura le temps écoulé depuis la dernière boucle d’Update, celui accessible depuis Time.deltaTime. Il est recommandé d’utiliser cette variable dans tout calcul d’animation / physique, afin de prendre en compte la vitesse de la machine. Sinon, vous aurez affaire à un cas d’école : le saut dans Quake. Si votre machine est plus puissante que celle de votre voisin, vous pourrez sauter plus haut. Eh ouais, c’est moche. Si vous voulez plus d’infos là dessus, jetez un oeil à cet article.
Le Fixed Timestep est directement lié à la méthode FixedUpdate. Il définit le temps entre deux appels à cette dernière. Plus le nombre sera petit, plus le nombre d’appels par seconde sera grand. Par défaut, il est à 0.02, ce qui équivaut à 50 appels par seconde (1 / 0.02). Attention, c’est un paramétrage avancé, ne changez pas cette valeur sans savoir ce que vous faites. Si le nombre d’appel par seconde devient très élevé, le jeu deviendra très gourmand. Notez que cette valeur influe également sur le nombre de fois que les calculs de physique interne au moteur sont effectués chaque seconde.
Enfin, la dernière valeur : Maximum Allowed Timestep. Cette valeur est encore plus touchy que le Fixed Timestep. C’est une sécurité permettant de réduire l’impact de calculs de physique très long sur les performances. Vous ne devriez pas en avoir besoin, mais dans le cas où ça arriverait, la documentation est ici.
Dans quel cadre l’utiliser : pour tout code impactant la physique : si vous déplacez le personnage du joueur via un Rigidbody par exemple.
LateUpdate
LateUpdate, comme son nom l’indique, est appelée… tard. Expliquons. Le moteur d’Unity mouline dans un ordre précis, comme expliqué au début de cet article. Pour l’appel des méthodes d’Update, c’est assez simple :
- Update est appelée en première.
- LateUpdate est appelée ensuite.
LateUpdate est appelée une fois que toutes les méthodes Update des GameObjects de la scène auront été exécutées. L’intérêt de placer du code dans cet méthode est de pouvoir exécuter des traitements qui nécessite que d’autres traitements aient été effectués au préalable. Par exemple pour un contrôleur TPS comme donné sur la documentation. Votre personnage sera sans doute composé d’une hiérarchie de GameObjects avec des traitements spécifiques. Les méthodes d’Update va bouger le personnage, le faire réagir, et LateUpdate va déplacer la caméra. Comme LateUpdate est appelée après Update, on est certain que le mouvement est terminé, dans tous les GameObjects.
LateUpdate fonctionne sur le même schéma qu’Update : elle est dépendante du framerate.
Dans quel cadre l’utiliser : Lorsque vous avez besoin d’exécuter un traitement après la boucle d’Update de plusieurs GameObjects. Si un seul est impacté, ce n’est pas la peine d’utiliser LateUpdate. L’ordre de vos traitements dans la méthode d’Update fera ce travail.
Crédits image : Doug88888