Logo du site

Jeu Vidéo 3

Techniques d'intégration multimédia

Mettre un jeu en pause dans Unity

Ce qu'on veut accomplir par exemple: arrêter le jeu pour afficher un message ou accéder à un inventaire, ou encore aux réglages du jeu, etc.

Quelle est la meilleure méthode pour y parvenir?

L'approche la plus simple consiste à régler l'échelle du temps du jeu à zéro (Time.timeScale = 0), ce qui met en pause toutes les opérations qui dépendent du passage du temps (tout ce qui se passe dans Update, la physique, les animations. etc.)

Si on remet par la suite Time.TimeScale à 1, le jeu n'est plus en pause et "joue" à sa vitesse normale.

Habituellement, cette approche est suffisante pour mettre un jeu en pause.

Il faut par contre considérer que tout dans le jeu n'est pas affecté par cette mise en pause simple...

Parfois c'est souhaitable (Ex: on veut que des éléments du UI continuent de s'animer même quand le jeu est en pause).

Mais dans d'autres cas il se peut que certains objets du jeu échappent à notre mise en pause et produisent des effets indésirables.

Il faut donc comprendre ce qui est affecté ou pas par Time.TimeScale afin de prendre des décisions éclairées selon nos besoins.

Exemple d'utilisation de Time.TimeScale pour mettre en pause:

public void JeuEnPause()
{
    Time.timeScale = 0;
}

public void JeuActif()
{
    Time.timeScale = 1;
}

Modifier l'échelle du temps produit son effet sur les variables qui servent à tenir compte du temps dans Unity (comme Time.deltaTime par exemple).

Avec Time.timeScale = 0, le temps ne s'écoule plus. Avec Time.TimeScale = 1, le flot du temps est normal. Et oui, à 0.5f le temps s'écoule 50% plus lentement et à 2f il s'écoule 2 fois plus vite: Il est donc possible d'accélerer et de ralentir le jeu.

Mais cela n'affecte pas tout dans le projet.

Ce qui est mis en pause concrètement et ce qui n'est pas affecté

Comme les déplacements utilisent habituellement Time.deltaTime, cette méthode permet généralement de les mettre en pause. Les animations et la physique dépendent aussi du passage du temps et seront arrêtés avec un Time.TimeScale = 0.

Ce qu'il faut savoir c'est que Update et FixedUpdate continuent d'être appelées. Donc tout ce qui s'y passe continue de s'exécuter, indépendamment de votre réglage de Time.TimeScale. Même chose pour les Coroutines.

Tout ce qui dépend directement de la mesure du temps sera arrêté cependant. Par exemple, les instructions Invoke.

En pratique, cela signifie que, dans la plupart des cas, tout sera arrêté lorsque le jeu est mis en pause et que, même si Update continue d'être appelé, rien ne se produit concrètement.

Remarquez que Time.time, qui correspond au temps écoulé en secondes depuis le démarrage du jeu, est arrêté quand Time.TimeScale = 0.

Généralement, c'est bien, mais quoi faire si par exemple vous voulez continuer à mesurer les secondes écoulées pendant que le jeu est sur Pause?

Il y a quelques solutions dans ce cas:

Time.realtimeSinceStartup et Time.unscaledTime ne sont pas affectés par la valeur de Time.TimeScale et continuent de mesurer le temps normallement même quand le jeu est en pause.

Les déplacements basés sur deltaTime sont arrêtés

Lorsque Time.TimeScale = 0, les déplacements calculés avec Time.deltaTime sont arrêtés.

Dans Unity, on multiplie habituellement les calculs de déplacement par Time.deltaTime, qui correspond à la durée écoulée depuis la dernière image. Le but est de conserver un déplacement régulier, même lorsque la fréquence d'affichage varie d'un ordinateur à l'autre.

Par exemple :

void Update()
{
    transform.Translate(Vector3.right * 6f * Time.deltaTime, Space.World);
}

Lorsque le jeu est mis en pause avec Time.TimeScale = 0, Time.deltaTime devient égal à zéro aussi. Tout déplacement multiplié par ce 0 devient nul et il n'y a plus de mouvement. Update continue de se répéter, mais le déplacement est toujours égal à 0 et l'objet ne bouge plus.

Bien entendu, si on déplaçait un objet à l'aide d'un nombre fixe, comme ici...

void Update()
{
    // À ne pas faire !
    transform.Translate(Vector3.down * 0.02f, Space.World);
}

...l'objet continuerait à se déplacer, même lorsque le jeu est en pause!

Mettre tout le jeu en pause, à l'exception de certains objets

Parfois, on souhaite que le jeu soit en pause, mais que certains objets continuent à se déplacer sans être affectés par la modification de Time.TimeScale.

Dans ce cas, utilisez le temps non mis à l'échelle (Time.unscaledDeltaTime) dans votre mouvement.

  • Au lieu de Time.deltaTime, utilisez Time.unscaledDeltaTime
  • Au lieu de Time.fixedDeltaTime, utilisez Time.fixedUnscaledDeltaTime
  • Au lieu de Time.time, utilisez Time.unscaledTime

La plupart des variables de la classe Time possèdent une version non mise à l'échelle, donc non affectée par Time.TimeScale.

Et les coroutines?

La plupart des coroutines vont être figées lorsque le jeu est mis en pause. Notez que la coroutine en tant que telle n'est pas arrêtée. Ce sont plutôt les instructions qu'elle contient, comme les boucles while ou encore WaitForSeconds, qui ne peuvent plus évoluer et empêchent la coroutine de terminer.

Si vous voulez qu'une coroutine continue de s'exécuter normallement quand Time.TimeScale = 0, utilisez Time.unscaledDeltaTime dans la coroutine. Il existe aussi un remplacement pour WaitForSeconds: WaitForSecondsRealtime

Animer les menus et autres objets quand le jeu est en pause

Encore une fois, utilisez le temps non mis à l'échelle pour animer les menus lorsque le jeu est en pause.

Dans un jeu, les élémets animés du UI utilisent la plupart du temps des animations. Or, les animations sont basées sur le temps. Avec Time.TimeScale = 0, toutes ces animations seront sur pause et vos éléments UI ne seront plus animés.

Il y a une solution à cela: sur le composant Animator d'un objet, réglez Update Mode à Unscaled Time.

Empêcher les saisies du joueur lorsque le jeu est en pause

Quand un jeu est en pause, Update continue d'être appelée. Les instruction liées aux commandes du joueur, souvent placées dans Update, vont continuer activées même lorsque le jeu est arrêté. Dans bien des cas, cela ne pose pas problème. Souvent parce que le déplacement basé sur Time.deltaTime ou même la physique dans un FixedUpdate ne s'exécutera pas avec une Time.TimeScale = 0.

Mais on peut tout de même avoir certains problèmes. Par exemple, dans un jeu 2D, les saisies au clavier pourraient continuer de modifier l'orientation du sprite du personnage... On pourrait aussi continuer à tirer même si le jeu est en pause...

Donc, il faut alors empêcher les saisies de l'usager d'affecter ces actions pendant que le jeu est en pause.

Une bonne approche pour y parvenir est d'utiliser une variable booléenne statique pour mémoriser aisément si le jeu est en pause au pas.

public class PauseManager : MonoBehaviour
{
    private static bool _jeuEnPause;
	
	public static bool jeuEnPause
	{
		get{ return _jeuEnPause; }
	}

    public void PauserJeu()
    {
        if (_jeuEnPause)
        {
			_jeuEnPause = false;
            Time.timeScale = 0f;
        }
        else
        {
			_jeuEnPause = true;
            Time.timeScale = 1;
        }
    }
}

Cette variable static signifie qu'elle est facilement accessible, donc chaque script dans le projet peut savoir en tout temps si le jeu est en pause ou pas et réagir en conséquence.

Par exemple, si on ne veut pas que le joueur puisse tirer quand le jeu est en pause:

public class Perso : MonoBehaviour
{
    public void OnTirer(InputValue value)
    {
        if (PauseManager.jeuEnPause == false)
        {
            // Faire tirer le joueur !
        }
    }
}

Mettre les sons en pause

Pas défaut, les sons continuent de jouer quand Time.TimeScale = 0. C'est habituellement ce que l'on souhaite. Mais si on veut mettre l'ensemble des sons en pause, passer par le composant AudioListener est la méthode la plus simple.

Dans Unity, un gameObject dans la scène doit être doté du composant AudioListener pour que les sons soient audibles dans le projet. C'est souvent la caméra qui est doté de ce composant.

Si on met cet AudioListener en pause, tous les sons vont être arrêtés (en pause).

public void JeuEnPause()
{
    Time.timeScale = 0;
	AudioListener.pause = true;
}

public void JeuActif()
{
    Time.timeScale = 1;
	AudioListener.pause = false;
}

Les sons mis en pause de cette façon vont reprendre là où ils s'étaient arrêtés quand l'Audio Listener sera réactivé.

Mais supposons qu'on souhaite que certains son continuent à jouer même si on a mis l'AudioListener en pause...

Il faudra alors régler les AudioSource spécifiques de façon à ce qu'ils ignorent l'état de pause du AudioListener avec cette instruction:

_referenceAuAudioSource.ignoreListenerPause = true;


Médiagraphie:
The right way to pause a game in Unity, Auteur John French, https://gamedevbeginner.com/the-right-way-to-pause-the-game-in-unity/