Voltar para o Blog
Quest Log

Slow motion (camera lenta) com time_scale no Godot 4

Cena de combate 2D no Godot 4 desacelerada no momento do impacto, com rastro de movimento lento

Aprenda slow motion godot com Engine.time_scale: bullet time num hit de combate, audio compensado e retorno suave com Tween. Tutorial pratico Godot 4.

Ferramenta:Godot

Quando um golpe acerta e o tempo parece travar por uma fracao de segundo, o jogador sente o peso do impacto antes mesmo de processar o que aconteceu. Esse efeito tem nome e ferramenta: slow motion godot com Engine.time_scale. Neste tutorial a gente vai do conceito ate um sistema reaproveitavel que aplica camera lenta num hit de combate e volta ao normal de forma suave, sem deixar o audio agudo nem o jogo travado em metade da velocidade. Nada de magica, so entender o que time_scale realmente faz e o que ele nao faz.

Slow motion (camera lenta) com time_scale no Godot 4

O que e Engine.time_scale na pratica

Engine.time_scale e um multiplicador global que afeta a passagem do tempo do jogo. O valor padrao e 1.0, ou seja, tempo normal. Se voce coloca 0.5, tudo que depende de tempo roda na metade da velocidade: fisica, animacoes via AnimationPlayer, Timer, e o delta que chega em _process e _physics_process. Se coloca 2.0, tudo acelera.

O ponto chave e esse: ele nao para a engine, ele desacelera a percepcao de tempo dela. O loop continua rodando na mesma taxa de quadros, mas cada quadro avanca menos tempo simulado. Por isso o efeito fica suave e nao vira um congelamento de frames.

Aplicar e tao simples quanto isso:

extends Node

func _ready() -> void:
    Engine.time_scale = 0.3

Coloque esse script numa cena de teste, rode, e veja tudo se arrastar. O problema e que assim ele fica lento para sempre. Slow motion bom e temporario e controlado, entao precisamos de mais estrutura.

Slow motion godot temporario com retorno automatico

A versao mais ingenua usa um Timer para voltar ao normal depois de um tempo. So que tem uma armadilha enorme aqui: um Timer comum tambem e afetado por time_scale. Se voce desacelera o jogo para 0.3 e pede um Timer de 0.2 segundos, ele vai levar 0.2 dividido por 0.3, ou seja, mais de meio segundo de tempo real. Quanto mais lento o jogo, mais demora o timer que deveria desligar a lentidao. Resultado: a camera lenta dura muito mais do que voce pediu.

A solucao e fazer o Timer ignorar o time_scale. No Godot 4 isso se resolve com process_mode. Um node configurado como PROCESS_MODE_ALWAYS continua processando mesmo com a arvore pausada, e seu tempo nao escala junto. Mas o jeito mais limpo e usar Timer com ignore_time_scale quando disponivel, ou medir tempo real manualmente. Vamos ao codigo que de fato funciona:

extends Node

# Quanto de tempo REAL a camera lenta deve durar.
@export var slowmo_duration: float = 0.15
@export var slowmo_scale: float = 0.25

var _slowmo_timer: float = 0.0
var _is_slowmo: bool = false

func _process(_delta: float) -> void:
    if not _is_slowmo:
        return
    # Tempo real ignora o time_scale do jogo.
    _slowmo_timer -= get_process_delta_time() / Engine.time_scale
    if _slowmo_timer <= 0.0:
        _end_slowmo()

func start_slowmo() -> void:
    Engine.time_scale = slowmo_scale
    _slowmo_timer = slowmo_duration
    _is_slowmo = true

func _end_slowmo() -> void:
    Engine.time_scale = 1.0
    _is_slowmo = false

A divisao get_process_delta_time() / Engine.time_scale desfaz o efeito da lentidao na contagem, fazendo o timer correr em segundos reais. Assim, slowmo_duration de 0.15 sempre significa 0.15 segundos de relogio de parede, nao importa o quanto o jogo esteja arrastado.

Voltando ao normal de forma suave com Tween

Cortar de 0.25 direto para 1.0 funciona, mas o retorno fica brusco. Uma transicao curta deixa o efeito mais agradavel e e onde o Tween brilha. O detalhe critico: um Tween tambem obedece ao time_scale por padrao, entao precisamos dizer para ele ignorar isso, senao o proprio tween que deveria acelerar o tempo vai rodar em camera lenta.

No Godot 4 voce usa set_ignore_time_scale(true) no tween:

extends Node

@export var slowmo_scale: float = 0.2
@export var hold_time: float = 0.08
@export var recover_time: float = 0.25

func hit_stop() -> void:
    # Entra na lentidao instantaneamente.
    Engine.time_scale = slowmo_scale

    var tween := create_tween()
    tween.set_ignore_time_scale(true)
    # Segura o slow motion por um instante de tempo real.
    tween.tween_interval(hold_time)
    # Sobe time_scale de volta a 1.0 com suavizacao.
    tween.tween_property(Engine, "time_scale", 1.0, recover_time) \
        .set_ease(Tween.EASE_OUT) \
        .set_trans(Tween.TRANS_QUAD)

Aqui o tween_interval segura a lentidao por um respiro, e o tween_property anima time_scale de volta ao normal ao longo de recover_time com uma curva EASE_OUT. O set_ignore_time_scale(true) garante que essa animacao rode no relogio real. Sem ele, o tween levaria muito mais que recover_time para terminar, porque ele proprio estaria sendo desacelerado pelo valor baixo de time_scale.

Vale notar que voce pode animar diretamente a propriedade time_scale do singleton Engine. Isso e perfeitamente valido no Godot 4 e poupa voce de criar uma variavel intermediaria.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot 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 Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Compensando o audio durante a camera lenta

Esse e o detalhe que separa um slow motion amador de um caprichado. Sons disparados durante a lentidao nao mudam de pitch sozinhos, porque o sistema de audio do Godot nao escala pelo time_scale. Em muitos jogos isso ate fica bom, mas se voce quer o classico audio grave e arrastado de bullet time, precisa abaixar o pitch dos sons na mao.

A forma direta e ajustar o pitch_scale do AudioStreamPlayer para acompanhar a desaceleracao:

extends Node

@onready var sfx: AudioStreamPlayer = $ImpactSound

func play_impact_slow(scale: float) -> void:
    # Pitch acompanha o time_scale para soar mais grave.
    sfx.pitch_scale = scale
    sfx.play()

func reset_audio_pitch() -> void:
    sfx.pitch_scale = 1.0

Se voce quer que o pitch volte junto com o tempo, da para animar pitch_scale no mesmo tween que recupera o time_scale. Cuidado apenas com sons longos como musica, que voce normalmente quer manter no pitch original. O truque e separar os barramentos: deixe os efeitos de impacto num bus que recebe o ajuste de pitch e mantenha a trilha intocada.

Montando o sistema completo para um hit de combate

Agora junte as pecas num node que voce pluga no seu personagem ou no gerenciador de combate. A ideia e chamar uma unica funcao no frame em que o golpe conecta:

extends Node
class_name HitStop

@export var slowmo_scale: float = 0.18
@export var hold_time: float = 0.07
@export var recover_time: float = 0.22

var _tween: Tween

func trigger() -> void:
    # Cancela um hit stop anterior antes de comecar outro.
    if _tween and _tween.is_valid():
        _tween.kill()

    Engine.time_scale = slowmo_scale

    _tween = create_tween()
    _tween.set_ignore_time_scale(true)
    _tween.tween_interval(hold_time)
    _tween.tween_property(Engine, "time_scale", 1.0, recover_time) \
        .set_ease(Tween.EASE_OUT) \
        .set_trans(Tween.TRANS_CUBIC)

E no script que detecta o acerto, a chamada fica trivial:

extends Area2D

@onready var hit_stop: HitStop = $HitStop

func _on_body_entered(body: Node2D) -> void:
    if body.is_in_group("enemy"):
        body.take_damage(10)
        hit_stop.trigger()

O kill() no inicio do trigger() evita um bug comum: se dois golpes acertam quase juntos, o segundo cancela o tween do primeiro em vez de empilhar dois efeitos brigando pelo time_scale. Sem isso, voce pode ficar preso em lentidao porque um tween mais antigo termina depois e reescreve o valor.

Cuidados e armadilhas comuns

Algumas coisas que valem a atencao quando voce coloca isso num projeto real:

O time_scale e global. Tudo desacelera junto, inclusive menus e elementos de interface que talvez voce queira manter em velocidade normal. Para UI que precisa ignorar a lentidao, use nodes com process_mode = PROCESS_MODE_ALWAYS e mova a logica deles para tempo real, ou anime via tweens com set_ignore_time_scale(true).

Valores muito baixos de time_scale, tipo 0.05, podem fazer a fisica parecer travada e gerar imprecisao em colisoes rapidas. Para a maioria dos jogos 2D de acao, algo entre 0.15 e 0.3 ja entrega o impacto sem efeitos colaterais.

Cuidado ao combinar time_scale com get_tree().paused. Sao mecanismos diferentes: pausa congela a arvore, time_scale apenas desacelera. Se voce pausa o jogo com a lentidao ativa, lembre de reconfigurar Engine.time_scale = 1.0 ao despausar, ou o jogador volta para uma partida arrastada.

Por fim, slow motion raramente vive sozinho. Ele costuma andar junto de outros recursos de impacto, e se voce quer entender a filosofia por tras desses detalhes, vale ler nosso material sobre game feel e juice. Um hit stop combina muito bem com um screen shake de camera curto no mesmo frame do golpe, dobrando a sensacao de peso sem custar quase nada de codigo.

Resumo do que importa

Engine.time_scale desacelera todo o tempo simulado do jogo, e e a base do slow motion no Godot 4. O segredo de um efeito limpo esta em tres pontos: medir a duracao em tempo real para que a lentidao nao se estenda sozinha, usar Tween com set_ignore_time_scale(true) para o retorno suave, e ajustar pitch_scale se voce quer o audio acompanhando a queda de velocidade. Com o node HitStop pronto, qualquer golpe do seu jogo ganha aquele instante de peso que faz o combate parecer que tem consequencia. Comece com valores conservadores, teste no feeling, e ajuste slowmo_scale e recover_time ate o impacto soar certo no seu jogo.