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.

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.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 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.