Voltar para o Blog
Quest Log

Menu de Pausa na Unity com Time.timeScale

Cena de ação congelada em tons frios com um painel de pausa translúcido no centro da tela

Unity pause menu com Time.timeScale = 0: script com tecla e botão e a lista do que continua rodando pausado (Update, input, animação de UI e áudio).

Ferramenta:UnityC#

Menu de Pausa na Unity com Time.timeScale

O unity pause menu mais comum funciona com uma linha: Time.timeScale = 0f congela a física e tudo que usa Time.deltaTime, e Time.timeScale = 1f devolve o jogo ao normal. Junto vai um painel de UI que aparece e some. O que os tutoriais rápidos não contam é que muita coisa continua rodando com o tempo em zero: o Update, o input, o áudio, a animação do próprio menu. É isso que faz o jogador atirar com o jogo pausado.

Aqui você monta o menu de pausa com tecla e botão no Unity 6.3 LTS, entende exatamente o que para e o que não para, e sai com a lista do que precisa ser tratado à parte.

Como pausar o jogo na Unity com Time.timeScale

Time.timeScale é o multiplicador da passagem do tempo no jogo. Em 1, o tempo corre normal. Em 0.5, câmera lenta. Em 0, parado. Ele afeta:

  • Time.deltaTime, que vira zero. Todo movimento escrito como velocidade * Time.deltaTime para sozinho.
  • FixedUpdate, que deixa de ser chamado. Rigidbody, colisão e gravidade congelam.
  • Animator em modo Normal e Fixed.
  • Corrotinas esperando com WaitForSeconds, que não avançam.
  • Partículas, a menos que estejam configuradas para tempo não escalado.

É por isso que o post de delta time e movimento por frame insiste em multiplicar tudo por Time.deltaTime: além de deixar o movimento igual em qualquer taxa de quadros, é o que faz a pausa funcionar de graça. Código que move objeto com valor fixo por frame (transform.position += Vector3.right * 0.1f) ignora a pausa.

Script de pausa com tecla e botão

Monte a UI primeiro:

  1. No Canvas do HUD, crie PainelPausa, esticado na tela inteira, com uma Image preta com alpha em torno de 160 para escurecer o jogo.
  2. Dentro dele, um título e três botões: BotaoContinuar, BotaoOpcoes, BotaoMenu.
  3. Desligue o PainelPausa.

Agora o script. A tecla de pausa é uma InputAction criada no código, com Esc no teclado e Start no controle, para não depender de editar o arquivo de ações do projeto.

Arquivo MenuDePausa.cs:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class MenuDePausa : MonoBehaviour
{
    [SerializeField] private GameObject painelPausa;
    [SerializeField] private Button botaoContinuar;
    [SerializeField] private Button botaoMenu;
    [SerializeField] private string cenaDoMenu = "MenuPrincipal";

    public static bool JogoPausado { get; private set; }

    private InputAction acaoPausar;
    private float escalaAntesDaPausa = 1f;

    private void Awake()
    {
        JogoPausado = false;
        acaoPausar = new InputAction("Pausar", InputActionType.Button);
        acaoPausar.AddBinding("<Keyboard>/escape");
        acaoPausar.AddBinding("<Gamepad>/start");
    }

    private void OnEnable()
    {
        acaoPausar.performed += AoApertarPausar;
        acaoPausar.Enable();
        botaoContinuar.onClick.AddListener(Continuar);
        botaoMenu.onClick.AddListener(VoltarAoMenu);
    }

    private void OnDisable()
    {
        acaoPausar.performed -= AoApertarPausar;
        acaoPausar.Disable();
        botaoContinuar.onClick.RemoveListener(Continuar);
        botaoMenu.onClick.RemoveListener(VoltarAoMenu);
    }

    private void OnDestroy()
    {
        acaoPausar.Dispose();
    }

    private void AoApertarPausar(InputAction.CallbackContext contexto)
    {
        if (JogoPausado)
        {
            Continuar();
        }
        else
        {
            Pausar();
        }
    }

    public void Pausar()
    {
        escalaAntesDaPausa = Time.timeScale;
        Time.timeScale = 0f;
        AudioListener.pause = true;
        JogoPausado = true;
        painelPausa.SetActive(true);
        EventSystem.current.SetSelectedGameObject(botaoContinuar.gameObject);
    }

    public void Continuar()
    {
        Time.timeScale = escalaAntesDaPausa;
        AudioListener.pause = false;
        JogoPausado = false;
        painelPausa.SetActive(false);
    }

    private void VoltarAoMenu()
    {
        Time.timeScale = 1f;
        AudioListener.pause = false;
        JogoPausado = false;
        SceneManager.LoadScene(cenaDoMenu);
    }
}

Coloque o script num objeto que não é o PainelPausa (por exemplo, no Canvas). Se ele ficar no painel desligado, o OnEnable nunca roda e a tecla não funciona.

Três decisões importantes no código:

  • escalaAntesDaPausa. Se o jogador pausou no meio de um efeito de câmera lenta (timeScale 0.3), voltar para 1 quebraria o efeito. Guardar e restaurar o valor resolve.
  • VoltarAoMenu força 1. Trocar de cena não reseta o timeScale. Se você sair da pausa direto para o menu sem isso, o menu abre com o tempo parado e qualquer animação dele congela.
  • JogoPausado estático. Outros scripts precisam saber se o jogo está pausado, e é aqui que entra a lista do que não para sozinho.

O que para e o que continua com timeScale 0

Esta é a parte que dá bug. Com Time.timeScale = 0:

Continua rodandoPara
Update e LateUpdate (com deltaTime = 0)FixedUpdate e toda a física
Leitura de input (teclado, mouse, controle)Animator em Normal ou Fixed
Áudio que já está tocandoCorrotinas com WaitForSeconds
UI: cliques, hover e transição Color Tint dos botõesInvoke com atraso
Animator em Unscaled TimePartículas em tempo normal
Time.unscaledDeltaTime e WaitForSecondsRealtimeTudo que multiplica por Time.deltaTime

Leia a coluna da esquerda com atenção. O input continua. O Update continua. Então um script que faz if (atirar) Instantiate(bala) dentro do Update dispara com o jogo pausado: a bala nasce, só não se move. Um pulo que aplica força num Rigidbody acumula a força e o personagem decola quando o jogo volta.

A correção mais limpa para o input é desligar o mapa de ações do jogador durante a pausa. Com as ações do projeto (as que vêm em todo projeto novo do Unity 6, acessadas por InputSystem.actions):

Arquivo BloqueioDeInputNaPausa.cs:

using UnityEngine;
using UnityEngine.InputSystem;

public class BloqueioDeInputNaPausa : MonoBehaviour
{
    private InputActionMap mapaDoJogador;
    private bool estavaPausado;

    private void Start()
    {
        if (InputSystem.actions != null)
        {
            mapaDoJogador = InputSystem.actions.FindActionMap("Player");
        }
    }

    private void Update()
    {
        if (mapaDoJogador == null || MenuDePausa.JogoPausado == estavaPausado)
        {
            return;
        }

        estavaPausado = MenuDePausa.JogoPausado;

        if (estavaPausado)
        {
            mapaDoJogador.Disable();
        }
        else
        {
            mapaDoJogador.Enable();
        }
    }
}

Com o mapa Player desligado, Move, Jump e Attack param de disparar enquanto o mapa UI segue ativo para navegar no menu. Se o seu jogador lê teclado direto (Keyboard.current), a alternativa é checar if (MenuDePausa.JogoPausado) return; no começo do Update do jogador.

Essas exceções são o tipo de coisa que você só descobre jogando o próprio jogo, de ponta a ponta, várias vezes: pausa durante o pulo, pausa durante o diálogo, pausa na tela de morte. Juntar sistemas que cada um funciona sozinho num jogo que funciona inteiro é a etapa em que mais gente desiste no meio.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Animações e tweens da UI com tempo não escalado

O painel de pausa costuma ter uma animação de entrada: um fade, os botões deslizando. Com timeScale em zero, essa animação não toca, e o painel aparece congelado no primeiro quadro (às vezes invisível, se o fade começa em alpha 0).

Três ajustes:

  1. Animator do menu: selecione o objeto com o Animator e mude Update Mode para Unscaled Time. Ele passa a animar em tempo real, ignorando o timeScale.
  2. Scripts de UI: troque Time.deltaTime por Time.unscaledDeltaTime em qualquer animação feita à mão.
  3. Corrotinas da UI: troque new WaitForSeconds(0.2f) por new WaitForSecondsRealtime(0.2f).

Um exemplo de fade feito por script:

Arquivo FadeDoPainel.cs:

using UnityEngine;

[RequireComponent(typeof(CanvasGroup))]
public class FadeDoPainel : MonoBehaviour
{
    [SerializeField] private float duracao = 0.2f;

    private CanvasGroup grupo;

    private void Awake()
    {
        grupo = GetComponent<CanvasGroup>();
    }

    private void OnEnable()
    {
        grupo.alpha = 0f;
    }

    private void Update()
    {
        if (grupo.alpha < 1f)
        {
            grupo.alpha = Mathf.MoveTowards(grupo.alpha, 1f, Time.unscaledDeltaTime / duracao);
        }
    }
}

Partículas do menu (brilho, poeira decorativa) seguem a mesma lógica: no módulo principal do Particle System, marque Use Unscaled Time.

Áudio durante a pausa

Time.timeScale não toca no áudio. Música, passos em loop, motor de carro, tudo continua com o jogo pausado. O script já resolve com AudioListener.pause = true, que pausa todas as AudioSources tocando, e false para retomar de onde parou.

E se você quer a música do menu tocando durante a pausa? A AudioSource dela precisa de ignoreListenerPause = true. Essa propriedade só pode ser definida por script (não aparece no Inspector nem é salva no componente), então coloque num Awake:

Arquivo MusicaDaPausa.cs:

using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class MusicaDaPausa : MonoBehaviour
{
    private void Awake()
    {
        GetComponent<AudioSource>().ignoreListenerPause = true;
    }
}

Os sons de clique dos botões do menu também precisam disso, senão o menu de pausa fica mudo.

Pausar sozinho quando a janela perde o foco

Um menu de pausa completo também abre sem o jogador pedir. No PC, o jogador aperta Alt+Tab para responder uma mensagem e volta com o personagem morto. No celular, chega uma ligação no meio da fase. A Unity avisa o seu script nesses dois casos com métodos que qualquer MonoBehaviour pode ter:

  • OnApplicationFocus(bool temFoco): chamado quando a janela ganha ou perde o foco. No PC é o caso do Alt+Tab.
  • OnApplicationPause(bool pausado): chamado quando o sistema coloca o app em segundo plano, que é o caso típico do celular.

Basta acrescentar ao MenuDePausa um método que pausa quando o foco sai e o jogo não está pausado:

private void OnApplicationFocus(bool temFoco)
{
    if (!temFoco && !JogoPausado)
    {
        Pausar();
    }
}

Repare que ele só pausa, nunca despausa sozinho. Quando o jogador volta, encontra o menu aberto e decide a hora de continuar, com a mão já no controle. Despausar automaticamente ao recuperar o foco devolve o problema original: o jogo volta correndo antes de a pessoa estar pronta.

Em jogo online ou com partida contra outras pessoas, a lógica muda: o tempo do servidor não para porque um jogador abriu o menu. Nesses jogos, o "menu de pausa" é só um painel por cima do jogo, sem mexer no timeScale, e o que você bloqueia é o input do jogador local.

Erros comuns: jogador ainda atira pausado e jogo volta lento

Jogador atira, pula ou gira a câmera pausado. Input e Update continuam. Desligue o mapa Player (script acima) ou cheque JogoPausado. A câmera que segue o mouse também lê input: inclua ela na checagem.

Jogo volta em câmera lenta. Algum script mexeu no timeScale e não devolveu, ou a pausa restaurou um valor errado. Use um lugar só para mexer no timeScale e guarde o valor anterior.

Menu de pausa não abre de novo depois de voltar ao menu. O JogoPausado estático ficou true porque a cena foi trocada no meio da pausa. O Awake do script zera o valor para evitar isso.

Botões do menu não respondem ao controle. Ninguém está selecionado. O SetSelectedGameObject no Pausar resolve.

Timer da fase continua contando. Se o cronômetro usa Time.unscaledDeltaTime ou Time.realtimeSinceStartup, ele ignora a pausa. Para tempo de jogo, use Time.deltaTime.

Próximo passo: menu de opções

O botão Opções do painel de pausa ainda não faz nada. O próximo passo é ligar a ele a mesma tela do menu principal, com volume e resolução salvos: o post de menu de opções na Unity monta essa tela. Se você também usa Godot, o menu de pausa no Godot resolve o mesmo problema com process_mode, e comparar os dois mostra bem como cada engine pensa a pausa.

Perguntas frequentes

Time.timeScale = 0 para o Update na Unity?

Não. O Update continua sendo chamado todo frame, só que Time.deltaTime passa a valer zero. O que para é o FixedUpdate, a física, as animações em modo Normal e as corrotinas que esperam com WaitForSeconds.

Como animar a UI do menu de pausa com o jogo pausado?

No Animator do menu, mude Update Mode para Unscaled Time. Em scripts, use Time.unscaledDeltaTime no lugar de Time.deltaTime, e em corrotinas use WaitForSecondsRealtime.

Como pausar o áudio junto com o jogo na Unity?

Time.timeScale não afeta áudio. Use AudioListener.pause = true para pausar todos os sons. Para a música do menu continuar tocando, marque ignoreListenerPause = true na AudioSource dela, por script.

Por que meu jogo volta em câmera lenta depois da pausa?

Porque o valor restaurado não era 1, ou algum efeito de câmera lenta foi interrompido pela pausa. Guarde o timeScale de antes de pausar e restaure esse valor, e garanta Time.timeScale = 1 ao trocar de cena.