Slow motion (camera lenta) com time_scale no Godot 4

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.
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.


