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 comovelocidade * Time.deltaTimepara 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:
- No Canvas do HUD, crie
PainelPausa, esticado na tela inteira, com uma Image preta com alpha em torno de 160 para escurecer o jogo. - Dentro dele, um título e três botões:
BotaoContinuar,BotaoOpcoes,BotaoMenu. - 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 (timeScale0.3), voltar para 1 quebraria o efeito. Guardar e restaurar o valor resolve.VoltarAoMenuforça 1. Trocar de cena não reseta otimeScale. Se você sair da pausa direto para o menu sem isso, o menu abre com o tempo parado e qualquer animação dele congela.JogoPausadoestá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 rodando | Para |
|---|---|
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á tocando | Corrotinas com WaitForSeconds |
| UI: cliques, hover e transição Color Tint dos botões | Invoke com atraso |
| Animator em Unscaled Time | Partículas em tempo normal |
Time.unscaledDeltaTime e WaitForSecondsRealtime | Tudo 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.
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:
- 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. - Scripts de UI: troque
Time.deltaTimeporTime.unscaledDeltaTimeem qualquer animação feita à mão. - Corrotinas da UI: troque
new WaitForSeconds(0.2f)pornew 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.



