Voltar para o Blog
Quest Log

Virar o Sprite na Direcao do Movimento no Godot 4

Personagem 2D virando o sprite para a esquerda e para a direita enquanto corre na tela do Godot.

Aprenda a fazer flip sprite godot conforme a direcao do movimento, usar flip_h sem quebrar a hitbox e espelhar nos nos filhos como braco e arma.

Fazer o personagem olhar para onde ele anda parece um detalhe pequeno, mas é uma das primeiras coisas que quebra a sensação de controle quando falta. Se o sprite fica sempre virado para a direita enquanto o jogador corre para a esquerda, o movimento parece errado mesmo que a física esteja perfeita. Neste post vamos resolver o flip sprite godot do jeito certo, cobrindo Sprite2D, AnimatedSprite2D, os problemas de usar scale.x negativo e como espelhar nós filhos como hitbox, braço e o ponto de spawn do tiro.

Virar o Sprite na Direcao do Movimento no Godot 4

A ideia central é simples: ler a direção horizontal do movimento e decidir para que lado o sprite aponta. O que muda é a técnica que você usa para virar, porque cada uma tem consequências diferentes no resto da cena.

A forma mais simples: flip_h

Tanto Sprite2D quanto AnimatedSprite2D têm a propriedade booleana flip_h. Quando ela é true, a textura é desenhada espelhada na horizontal, sem mexer em nada mais da cena. É a abordagem que você deve preferir na maioria dos casos.

O ponto de atenção é decidir quando virar. Se você atualizar flip_h direto pela velocidade, o sprite vai virar mesmo por causa de uma fração de pixel ou de uma força residual. Por isso vale checar um limiar pequeno e, principalmente, preservar a última direção quando a velocidade horizontal chega a zero.

extends CharacterBody2D

@export var speed: float = 200.0

@onready var sprite: Sprite2D = $Sprite2D

func _physics_process(delta: float) -> void:
    var direction := Input.get_axis("move_left", "move_right")
    velocity.x = direction * speed
    move_and_slide()
    update_facing()

func update_facing() -> void:
    # So vira quando ha movimento horizontal real.
    if velocity.x > 0.1:
        sprite.flip_h = false
    elif velocity.x < -0.1:
        sprite.flip_h = true
    # Quando velocity.x esta perto de zero, mantemos o flip_h atual.

Repare que não há else. Esse detalhe é o que preserva a última direção: ao soltar o controle, o personagem continua olhando para onde estava indo, e não volta para um lado padrão. Com AnimatedSprite2D o código é idêntico, porque a propriedade flip_h existe nos dois nós.

Por que velocity.x e nao a tecla pressionada

Usar velocity.x em vez de ler a tecla diretamente tem uma vantagem prática. Em jogos de plataforma com aceleração, knockback ou esteiras, o personagem pode estar se movendo sem você estar apertando nada. Olhar para a velocidade real faz o sprite respeitar o que está acontecendo na física, não só o input.

Se você ainda não montou a base de deslocamento do personagem, o post sobre movimento de personagem em plataforma 2D cobre o CharacterBody2D e o move_and_slide que estamos usando aqui como ponto de partida.

O problema de usar scale.x negativo

Uma solução que aparece muito é inverter o nó inteiro com scale.x = -1. Funciona para virar a imagem, mas é uma armadilha. O scale é aplicado a todos os filhos do nó, não só ao sprite. Se o seu CharacterBody2D tem uma CollisionShape2D, uma Area2D de hitbox e um Marker2D que marca de onde o tiro sai, todos eles são espelhados junto.

Na prática isso significa que o ponto de spawn do tiro, que estava à direita do personagem, passa para a esquerda quando você vira. Hitboxes assimétricas mudam de lado. E se algum filho também tiver scale ajustado, os valores se multiplicam e bagunçam de vez. Por isso, para virar só a aparência, flip_h é mais seguro do que scale.x no nó raiz.

Existe um caso legítimo para o scale.x negativo, mas ele é controlado: aplicar em um Node2D que serve de pivô só para os elementos que realmente devem espelhar. É o que vamos ver a seguir.

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

Virando os nos filhos com um Node2D pivot

Quando o personagem segura uma arma ou tem um braço que aponta, virar só a textura não basta. O braço precisa ir para o outro lado também. A solução limpa é agrupar esses elementos sob um Node2D que funciona como pivô e espelhar apenas esse pivô.

Monte a árvore assim:

Player (CharacterBody2D)
├── Sprite2D
├── CollisionShape2D
└── FlipPivot (Node2D)
    ├── ArmSprite (Sprite2D)
    └── MuzzleMarker (Marker2D)

Tudo que deve acompanhar a direção vai dentro de FlipPivot. A CollisionShape2D do corpo fica de fora, porque normalmente é simétrica e não deve se mexer. O código vira o pivô e o sprite principal de forma coordenada:

extends CharacterBody2D

@export var speed: float = 200.0

@onready var sprite: Sprite2D = $Sprite2D
@onready var flip_pivot: Node2D = $FlipPivot

var facing: int = 1  # 1 para direita, -1 para esquerda.

func _physics_process(delta: float) -> void:
    var direction := Input.get_axis("move_left", "move_right")
    velocity.x = direction * speed
    move_and_slide()
    update_facing()

func update_facing() -> void:
    if velocity.x > 0.1:
        facing = 1
    elif velocity.x < -0.1:
        facing = -1
    # Espelha o sprite principal.
    sprite.flip_h = facing < 0
    # Espelha apenas os filhos do pivot (braco e muzzle).
    flip_pivot.scale.x = facing

Aqui o scale.x negativo é seguro porque só afeta o que está dentro de FlipPivot. O MuzzleMarker acompanha o personagem, então o tiro sempre sai da frente, independente do lado para onde ele olha. A variável facing guarda a direção atual como inteiro, o que deixa o cálculo da posição do tiro trivial mais adiante.

Spawn do tiro respeitando a direcao

Com o Marker2D dentro do pivô, o ponto global já vem espelhado de graça. Na hora de disparar, use a posição global do marcador e oriente o projétil pelo facing.

@export var bullet_scene: PackedScene

@onready var muzzle: Marker2D = $FlipPivot/MuzzleMarker

func shoot() -> void:
    if bullet_scene == null:
        return
    var bullet := bullet_scene.instantiate()
    get_tree().current_scene.add_child(bullet)
    bullet.global_position = muzzle.global_position
    # A direcao do tiro segue o lado do personagem.
    bullet.set("direction", Vector2(facing, 0))

Como o MuzzleMarker é filho de FlipPivot, sua global_position já reflete o lado correto quando o pivô tem scale.x igual a -1. Você não precisa recalcular offsets na mão.

Quando a animacao precisa ser espelhada

Em AnimatedSprite2D, o flip_h espelha qualquer frame da animação atual, então uma corrida desenhada para a direita serve para os dois lados sem trabalho extra. Esse é o caminho mais econômico e funciona bem para a maioria dos personagens.

@onready var anim: AnimatedSprite2D = $AnimatedSprite2D

func update_facing() -> void:
    if velocity.x > 0.1:
        anim.flip_h = false
    elif velocity.x < -0.1:
        anim.flip_h = true

    if absf(velocity.x) > 0.1:
        anim.play("run")
    else:
        anim.play("idle")

Há casos em que espelhar não basta. Se a arte tem sombreamento direcional, uma cicatriz num lado específico do rosto ou uma arma que deveria ficar sempre na mão direita, o flip horizontal vai mostrar a versão invertida e o erro aparece. Nessas situações você mantém conjuntos de frames separados, por exemplo run_right e run_left, e troca a animação em vez de usar flip_h.

func update_facing() -> void:
    var dir := "right"
    if velocity.x > 0.1:
        dir = "right"
    elif velocity.x < -0.1:
        dir = "left"
    else:
        dir = "right" if anim.animation.ends_with("right") else "left"

    if absf(velocity.x) > 0.1:
        anim.play("run_" + dir)
    else:
        anim.play("idle_" + dir)

O custo aqui é dobrar a quantidade de frames e nomear tudo com sufixo de direção. Vale o esforço só quando o espelhamento simples realmente estraga a leitura do personagem. Para entender melhor como organizar esses estados de animação, o post sobre animação de sprite 2D no jogo ajuda a montar o AnimatedSprite2D por trás dessa lógica.

Lidando com AnimationPlayer

Se em vez de AnimatedSprite2D você usa AnimationPlayer controlando um Sprite2D, o flip_h continua sendo seu aliado, mas com um cuidado: evite animar a propriedade flip_h dentro das próprias animações de movimento, senão o valor que você define por código entra em conflito com a track e fica piscando.

A regra prática é separar responsabilidades. Deixe o AnimationPlayer cuidar dos quadros e do timing, e deixe o código cuidar do flip_h baseado na direção. Assim as duas coisas não brigam pelo mesmo valor. Se quiser aprofundar nas tracks e na máquina de estados, vale conferir o material sobre AnimationPlayer no Godot.

Juntando tudo num CharacterBody2D

Por fim, um exemplo completo que combina movimento, flip do sprite e pivô para os filhos. É a base que você pode copiar e adaptar.

extends CharacterBody2D

@export var speed: float = 220.0
@export var gravity: float = 980.0
@export var jump_force: float = 380.0

@onready var sprite: Sprite2D = $Sprite2D
@onready var flip_pivot: Node2D = $FlipPivot

var facing: int = 1

func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta

    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = -jump_force

    var direction := Input.get_axis("move_left", "move_right")
    velocity.x = direction * speed

    move_and_slide()
    update_facing()

func update_facing() -> void:
    if velocity.x > 0.1:
        facing = 1
    elif velocity.x < -0.1:
        facing = -1

    sprite.flip_h = facing < 0
    flip_pivot.scale.x = facing

Note que facing só muda quando há velocidade horizontal real, então pular parado ou ser empurrado verticalmente não altera o lado para onde o personagem olha. Esse é exatamente o comportamento que dá sensação de controle.

Resumo das decisoes

Para a maioria dos personagens, flip_h resolve o flip sprite godot com uma linha e zero efeito colateral. Reserve o scale.x negativo para um Node2D pivô que agrupa só o que precisa espelhar, como braço, arma e o Marker2D de spawn do tiro, nunca no nó raiz com a colisão dentro. Use velocity.x com um limiar pequeno para decidir a direção e omita o else para preservar a última direção quando o personagem para. E só parta para conjuntos de frames _left e _right quando o espelhamento simples atrapalhar a leitura da arte. Com essas regras, virar o personagem deixa de ser uma fonte de bugs e vira só mais um detalhe que funciona.