Física de Jogos no Godot: Tutorial Completo de CharacterBody e RigidBody

Diagrama explicativo de física e colisões no Godot Engine

Domine física de jogos no Godot Engine. Tutorial completo sobre CharacterBody2D/3D, RigidBody, colisões, raycasting e otimização de performance.

Física de Jogos no Godot: Tutorial Completo de CharacterBody e RigidBody

A física é o coração de como jogos se sentem ao jogar. Pulo responsivo, colisões satisfatórias e movimento natural separam jogos amadores de experiências polidas e profissionais. No Godot Engine, o sistema de física oferece poder e flexibilidade incríveis - mas também pode confundir iniciantes.

Com o Godot 4.x, o sistema de física foi significativamente melhorado, trazendo interpolação nativa, melhor performance e APIs mais intuitivas. Nodes como CharacterBody2D/3D e RigidBody2D/3D fornecem fundação sólida para implementar movimento, colisões e interações físicas.

Neste tutorial completo, vou te guiar desde os conceitos fundamentais de física no Godot até implementações avançadas. Você aprenderá a diferença entre CharacterBody e RigidBody, como implementar movimento suave, gerenciar colisões, usar raycasting e otimizar performance. Ao final, você terá conhecimento profundo para criar qualquer tipo de movimento e interação física no seu jogo.

Fundamentos de Física no Godot

Antes de escrever código, é essencial entender como o Godot estrutura física.

O Sistema de Physics Engine

Godot usa engines de física separadas para 2D e 3D:

2D Physics (GodotPhysics2D):

  • Engine própria otimizada para jogos 2D
  • Eixos X e Y apenas
  • Gravity padrão: (0, 980) pixels/s²

3D Physics (GodotPhysics3D ou Jolt Physics):

  • Godot 4.5 introduziu Jolt Physics como alternativa
  • GodotPhysics3D mantido por compatibilidade
  • Jolt oferece melhor performance e estabilidade
  • Gravity padrão: (0, -9.8, 0) metros/s²

Physics Process vs Regular Process:

func _process(delta):
    # Roda cada frame (variável)
    # Use para visual updates, input, etc.
    pass

func _physics_process(delta):
    # Roda em fixed timestep (padrão: 60 FPS)
    # SEMPRE use para física e movimento
    pass

Por que _physics_process?

  • Timestep fixo garante comportamento consistente
  • Physics engine sincronizado com este loop
  • Movimentos em _process() podem causar jittering

Tipos de Physics Bodies

Godot oferece vários tipos de bodies para diferentes propósitos:

StaticBody2D/3D

  • Não se move (paredes, chão, obstáculos fixos)
  • Colide com outros bodies mas permanece imóvel
  • Performance máxima (engine otimiza agressivamente)

CharacterBody2D/3D

  • Movimento controlado via script (player, AI enemies)
  • Método
    move_and_slide()
    para movimento com colisão
  • Use para qualquer personagem controlável

RigidBody2D/3D

  • Simulação física completa (caixas, barris, ragdolls)
  • Engine calcula movimento baseado em forças
  • Use para objetos que respondem a física realista

AnimatableBody2D/3D

  • Kinematic bodies com animação
  • Para plataformas móveis, elevadores
  • Movem-se via animation ou script mas não respondem a forças

Area2D/3D

  • Não colide fisicamente
  • Detecta overlaps e triggers
  • Use para zones, triggers, pickups

Shapes de Colisão

Bodies precisam de shapes para definir área de colisão:

Tipos de shapes:

  • RectangleShape2D/BoxShape3D: Retângulos/caixas (mais comum)
  • CircleShape2D/SphereShape3D: Círculos/esferas (bom para projéteis)
  • CapsuleShape2D/3D: Cápsula (ideal para personagens)
  • ConvexPolygonShape/ConcavePolygonShape: Shapes customizadas

Dica importante: Shapes simples são muito mais performáticas. Use combinação de shapes simples em vez de uma shape complexa quando possível.

Descubra Seu Potencial em Game Development

Física de jogos é apenas uma das muitas habilidades em game dev. Você prefere trabalhar com sistemas técnicos como física e IA, ou é mais criativo focando em arte e design? Descubra seu perfil ideal.

Fazer Teste Vocacional

CharacterBody2D: Movimento de Personagem

CharacterBody é o node mais usado para personagens controláveis. Vou guiar você pela implementação completa.

Setup Básico

Estrutura de nodes:

Player (CharacterBody2D)
├── CollisionShape2D
└── Sprite2D

Script básico:

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

# Get the gravity from the project settings
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta):
    # Apply gravity
    if not is_on_floor():
        velocity.y += gravity * delta

    # Handle jump
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get input direction
    var direction = Input.get_axis("ui_left", "ui_right")

    # Apply movement
    if direction:
        velocity.x = direction * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)

    move_and_slide()

Entendendo move_and_slide()

move_and_slide()
é mágico. Ele:

  • Move o body baseado em
    velocity
  • Detecta e resolve colisões automaticamente
  • Slides ao longo de superfícies
  • Retorna true se houve colisão

Parâmetros importantes:

# Godot 4.x sintaxe (simplificada vs 3.x)
move_and_slide()

# Acesse informações após o movimento:
is_on_floor()  # Está no chão?
is_on_ceiling()  # Bateu no teto?
is_on_wall()  # Tocando parede?
get_slide_collision_count()  # Número de colisões
get_slide_collision(index)  # Detalhes da colisão

Movimento Suave e Aceleração

Movimento instantâneo parece robótico. Adicione aceleração:

extends CharacterBody2D

const SPEED = 300.0
const ACCELERATION = 2000.0  # Aceleração
const FRICTION = 1500.0  # Desaceleração

func _physics_process(delta):
    var direction = Input.get_axis("ui_left", "ui_right")

    if direction:
        # Acelera gradualmente
        velocity.x = move_toward(velocity.x, direction * SPEED, ACCELERATION * delta)
    else:
        # Desacelera com fricção
        velocity.x = move_toward(velocity.x, 0, FRICTION * delta)

    move_and_slide()

Dica: Valores maiores de ACCELERATION = resposta mais rápida. Ajuste ao sentir o jogo.

Pulo Variável (Jump Buffering)

Players esperam controle preciso sobre altura do pulo:

const JUMP_VELOCITY = -400.0
const JUMP_CUT_MULTIPLIER = 0.5  # Reduz velocidade ao soltar

func _physics_process(delta):
    # Gravity normal
    if not is_on_floor():
        velocity.y += gravity * delta

    # Pulo normal
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Cortar pulo se soltar botão
    if Input.is_action_just_released("jump") and velocity.y < 0:
        velocity.y *= JUMP_CUT_MULTIPLIER

    move_and_slide()

Coyote Time e Jump Buffering

Faça pulo sentir mais responsivo:

Coyote Time: Player pode pular poucos frames após sair da plataforma Jump Buffering: Pressionar pulo poucos frames antes de pousar funciona

var coyote_time = 0.1  # Segundos de "grace period"
var coyote_timer = 0.0
var jump_buffer_time = 0.1
var jump_buffer_timer = 0.0

func _physics_process(delta):
    # Atualiza coyote timer
    if is_on_floor():
        coyote_timer = coyote_time
    else:
        coyote_timer -= delta

    # Atualiza jump buffer
    if Input.is_action_just_pressed("jump"):
        jump_buffer_timer = jump_buffer_time
    else:
        jump_buffer_timer -= delta

    # Pulo com coyote time e buffer
    if jump_buffer_timer > 0 and coyote_timer > 0:
        velocity.y = JUMP_VELOCITY
        jump_buffer_timer = 0
        coyote_timer = 0

    move_and_slide()

Essas pequenas features fazem diferença ENORME no game feel.

CharacterBody3D: Movimento First-Person

Movimento 3D adiciona dimensão Z e rotação de câmera.

FPS Controller Básico

extends CharacterBody3D

const SPEED = 5.0
const JUMP_VELOCITY = 4.5
const SENSITIVITY = 0.003  # Mouse sensitivity

@onready var camera = $Camera3D

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _ready():
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event):
    # Mouse look
    if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
        rotate_y(-event.relative.x * SENSITIVITY)
        camera.rotate_x(-event.relative.y * SENSITIVITY)
        camera.rotation.x = clamp(camera.rotation.x, -PI/2, PI/2)

func _physics_process(delta):
    # Gravity
    if not is_on_floor():
        velocity.y -= gravity * delta

    # Jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Movement
    var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)

    move_and_slide()

RigidBody: Simulação Física

Para objetos que respondem a física realista, use RigidBody.

Setup de RigidBody2D

extends RigidBody2D

func _ready():
    # Propriedades físicas
    mass = 10.0
    gravity_scale = 1.0  # 1.0 = gravity normal
    linear_damp = 0.1  # Air resistance
    angular_damp = 0.5  # Rotational resistance

func apply_force_example():
    # Aplicar força
    apply_central_impulse(Vector2(500, -500))  # Impulso instantâneo
    apply_force(Vector2(100, 0))  # Força contínua

func _integrate_forces(state):
    # Para controle manual avançado
    # state.linear_velocity = ...
    # state.transform.origin = ...
    pass

Diferença: Impulse vs Force

Impulse:

  • Mudança instantânea de velocidade
  • Use para: explosões, pulo, colisões

Force:

  • Aplicada continuamente
  • Use para: motores, vento, magnetismo
# Impulse - instantâneo
apply_central_impulse(Vector2(500, 0))

# Force - contínuo (chamar em _physics_process)
func _physics_process(delta):
    apply_central_force(Vector2(100, 0))

Lock de Rotação

Às vezes você quer física mas sem rotação:

extends RigidBody2D

func _ready():
    lock_rotation = true  # Impede rotação

Sleeping e Continuous CD

Sleeping: RigidBodies "dormem" quando param de se mover (otimização). Acordam quando interagem.

# Forçar acordar
sleeping = false

# Prevenir dormir
can_sleep = false  # Use com cautela (impacto performance)

Continuous Collision Detection: Para objetos rápidos que atravessam paredes:

continuous_cd = CCD_MODE_CAST_RAY  # Previne tunneling

Colisões Avançadas

Layers e Masks

Controle o que colide com o quê:

Collision Layer: "Eu estou nesta(s) layer(s)" Collision Mask: "Eu colido com esta(s) layer(s)"

Exemplo:

Layer 1: Player
Layer 2: Enemies
Layer 3: Walls
Layer 4: Pickups

Player:
  - Layer: 1
  - Mask: 2, 3, 4 (colide com enemies, walls, pickups)

Enemy:
  - Layer: 2
  - Mask: 1, 3 (colide com player e walls)

Pickup:
  - Layer: 4
  - Mask: nenhum (não colide fisicamente, usa Area2D)

Em código:

# Setar layer (bit shifting)
collision_layer = 1  # Layer 1
collision_mask = 6  # Layers 2 e 3 (2 + 4 = 6)

# Verificar layer
if collision_layer & (1 << 0):  # Está na layer 1?
    pass

Detecting Colisões

CharacterBody:

move_and_slide()

for i in get_slide_collision_count():
    var collision = get_slide_collision(i)
    print("Colidiu com: ", collision.get_collider().name)
    print("Normal: ", collision.get_normal())
    print("Posição: ", collision.get_position())

RigidBody:

func _on_body_entered(body):
    print("Colidiu com: ", body.name)

func _on_body_exited(body):
    print("Parou de colidir com: ", body.name)

Area2D (triggers):

func _on_area_entered(area):
    print("Overlap com area: ", area.name)

func _on_body_entered(body):
    if body.name == "Player":
        # Player entrou na trigger zone
        trigger_event()

Raycasting

Raycasts detectam objetos em linha reta sem física real.

RayCast2D Node

extends Node2D

@onready var raycast = $RayCast2D

func _physics_process(delta):
    if raycast.is_colliding():
        var collider = raycast.get_collider()
        var point = raycast.get_collision_point()
        var normal = raycast.get_collision_normal()

        print("Raycast detectou: ", collider.name, " em ", point)

Configuração no Inspector:

  • Enabled: true
  • Target Position: Para onde o ray aponta (relativo)
  • Collision Mask: O que detectar

Raycast via Código (PhysicsRayQueryParameters)

Mais flexível que RayCast2D node:

func raycast_from_point(from: Vector2, to: Vector2) -> Dictionary:
    var space_state = get_world_2d().direct_space_state
    var query = PhysicsRayQueryParameters2D.create(from, to)
    query.collision_mask = 1  # Layer 1

    var result = space_state.intersect_ray(query)

    if result:
        print("Hit: ", result.collider.name)
        print("Position: ", result.position)
        print("Normal: ", result.normal)

    return result

Casos de uso:

  • Line of sight: Inimigo vê player?
  • Shooting: Onde bala acerta?
  • Ground check: Player está no chão? (alternativa a is_on_floor)

ShapeCast (Raycast com Shape)

Godot 4.x introduziu ShapeCast - raycast com forma geométrica:

@onready var shapecast = $ShapeCast2D

func _physics_process(delta):
    if shapecast.is_colliding():
        # Detecta colisões ao longo de um shape móvel
        pass

Útil para detectar objetos em área enquanto se move.

Domine Godot e Construa Jogos Profissionais

Física é apenas uma peça do puzzle. Jogos completos requerem arte, áudio, UI, design de levels e muito mais. Aprenda todas as disciplinas necessárias para criar e publicar jogos de sucesso.

Candidate-se Agora

One-Way Platforms (Plataformas Descíveis)

Plataformas que você pode pular através mas cair em cima:

extends CharacterBody2D

func _physics_process(delta):
    # Movimento normal
    velocity.y += gravity * delta

    # Detectar input para descer
    if Input.is_action_pressed("ui_down") and is_on_floor():
        position.y += 2  # Pequeno deslocamento para baixo

    move_and_slide()

No OneWayCollision node:

  • Configure Collision Layer/Mask
  • Set
    one_way_collision
    property to true
  • Set
    one_way_collision_margin
    (pixels de margem)

Plataformas Móveis

Para plataformas que se movem e carregam o player:

Usando AnimatableBody2D:

extends AnimatableBody2D

@export var move_distance = 200.0
@export var move_speed = 100.0

var start_pos: Vector2
var target_pos: Vector2
var direction = 1

func _ready():
    start_pos = position
    target_pos = start_pos + Vector2(move_distance, 0)

func _physics_process(delta):
    if direction == 1:
        position.x += move_speed * delta
        if position.x >= target_pos.x:
            direction = -1
    else:
        position.x -= move_speed * delta
        if position.x <= start_pos.x:
            direction = 1

Importante: CharacterBody automaticamente "gruda" em AnimatableBody em movimento (desde que is_on_floor() seja true).

Otimização de Performance

Física pode ser cara computacionalmente. Aqui estão técnicas de otimização:

1. Use Shapes Simples

# BOM: Shape simples
var shape = CircleShape2D.new()

# RUIM: Polygon complexo
var shape = ConcavePolygonShape2D.new()

Sempre prefira círculos, retângulos e cápsulas.

2. Collision Layers Inteligentes

Reduza checks desnecessários:

  • Projéteis não precisam colidir entre si
  • Pickups não precisam colidir com pickups

3. Sleeping de RigidBodies

Permita RigidBodies dormirem quando não estão em uso:

can_sleep = true  # Default, mas garanta que está true

4. Reduza Physics Ticks Se Possível

Em Project Settings:

  • Physics/Common/Physics_ticks_per_second
    : Padrão 60
  • Reduzir para 30 ou 45 em jogos mobile menos exigentes

5. Use Area2D Para Triggers

Area2D é mais leve que collision detection física real:

# Use Area2D para:
# - Trigger zones
# - Pickup detection
# - Damage areas

# Use CollisionShape2D para:
# - Actual physical collision
# - Movement blocking

6. Desabilite Physics Quando Fora da Tela

extends RigidBody2D

func _on_visible_on_screen_notifier_screen_exited():
    sleeping = true  # Otimiza quando off-screen

func _on_visible_on_screen_notifier_screen_entered():
    sleeping = false

Debugging de Física

Visible Collision Shapes

No editor: Debug > Visible Collision Shapes (ON)

Mostra todos collision shapes durante gameplay - essencial para debug.

Draw Raycasts

func _process(delta):
    # Draw ray visualmente (debug)
    queue_redraw()

func _draw():
    if raycast.is_colliding():
        draw_line(Vector2.ZERO, raycast.get_collision_point() - global_position, Color.RED, 2)

Performance Monitor

No editor: Debug > Performance Monitor

Monitore:

  • 2D/3D physics time
  • Collision objects count
  • Active bodies

Se physics time > 16ms (60 FPS), há problemas de performance.

Conclusão: Física Sólida = Jogo Satisfatório

Física bem implementada é invisível mas essencial. Players não notam boa física - mas definitivamente notam física ruim.

Recapitulando conceitos essenciais:

  1. ✅ Use
    _physics_process()
    para qualquer código relacionado a física
  2. ✅ CharacterBody para personagens controlados, RigidBody para objetos físicos
  3. move_and_slide()
    é seu melhor amigo para movimento de personagem
  4. ✅ Adicione aceleração, coyote time e jump buffering para game feel excelente
  5. ✅ Use collision layers/masks para controlar o que interage com o quê
  6. ✅ Raycasts são versáteis para line of sight, shooting, ground checking
  7. ✅ Otimize agressivamente - física pode ser cara

Seu próximo passo:

Abra Godot e implemente movimento de personagem do zero. Comece simples:

  • Dia 1: Movimento horizontal básico
  • Dia 2: Adicione pulo e gravidade
  • Dia 3: Implemente aceleração suave
  • Dia 4: Adicione coyote time e jump buffering
  • Dia 5: Polish e teste extensivamente

Física é ciência e arte. A ciência você acabou de aprender. A arte vem de testar, ajustar, e sentir o que funciona.

Agora vá criar movimento que seja satisfatório, responsivo e divertido. Seu jogo merece física excelente.