Voltar para o Blog
Quest Log

Ima de itens: atracao de coletaveis no Godot 4

Player rodeado por moedas sendo puxadas em sua direcao num cenario 2D

Aprenda a criar um ima de itens godot que atrai moedas e loot ate o player com aceleracao progressiva. Tutorial pratico de GDScript em Godot 4.

Poucos detalhes deixam a coleta de itens tao gostosa quanto ver as moedas voarem ate o personagem. Aquele efeito de varrer o chao sem precisar passar exatamente por cima de cada peca de loot e o que chamamos de ima de itens, e nesse tutorial voce vai montar um ima de itens godot do zero, com raio ajustavel e aceleracao que cresce conforme o coletavel se aproxima. A ideia e simples de implementar e rende um ganho enorme de game feel.

Ima de itens: atracao de coletaveis no Godot 4

A mecanica tem duas partes que conversam entre si. De um lado, o player carrega uma area de deteccao (o campo do ima). Do outro, cada coletavel sabe entrar em estado "atraido" e se mover em direcao ao alvo. Quando o coletavel encosta de fato no corpo do player, ele e coletado. Vamos construir isso em camadas para ficar facil de ajustar depois.

Montando a area de ima no player

O player precisa de uma Area2D extra, separada da hitbox de dano ou de colisao fisica. Essa area representa o alcance do ima. Use um CollisionShape2D com um CircleShape2D para que o raio seja redondo e previsivel. A grande vantagem de expor o raio como variavel e poder mexer nele em tempo de execucao, por exemplo quando o jogador pega um upgrade que aumenta o alcance da coleta.

extends CharacterBody2D

@export var magnet_radius: float = 120.0

@onready var magnet_area: Area2D = $MagnetArea
@onready var magnet_shape: CollisionShape2D = $MagnetArea/CollisionShape2D

func _ready() -> void:
    _apply_magnet_radius()
    magnet_area.body_entered.connect(_on_magnet_area_body_entered)
    magnet_area.area_entered.connect(_on_magnet_area_area_entered)

func _apply_magnet_radius() -> void:
    var shape := magnet_shape.shape as CircleShape2D
    if shape:
        shape.radius = magnet_radius

func set_magnet_radius(value: float) -> void:
    magnet_radius = value
    _apply_magnet_radius()

Aqui o magnet_radius controla o tamanho do CircleShape2D diretamente. Quando o player pega um upgrade, basta chamar set_magnet_radius(180.0) e o campo do ima cresce na hora. Note que conectamos tanto area_entered quanto body_entered. Isso porque os coletaveis podem ser Area2D (mais comum para itens que so detectam, sem fisica) ou corpos. Vamos focar no caso dos itens como Area2D, que e o mais leve e o mais usado para moedas e gemas.

O coletavel como Area2D

Cada item e uma Area2D independente. Ela guarda uma referencia ao alvo (o player) e um booleano que diz se ja foi atraida. Enquanto nao estiver atraida, fica parada no chao. Quando o campo do ima a alcanca, ela liga o estado e comeca a perseguir o player todo frame.

extends Area2D
class_name Collectible

@export var base_speed: float = 60.0
@export var max_speed: float = 480.0
@export var acceleration: float = 6.0

var target: Node2D = null
var is_attracted: bool = false
var current_speed: float = 0.0

func _ready() -> void:
    current_speed = base_speed
    body_entered.connect(_on_body_entered)

func start_attraction(player: Node2D) -> void:
    if is_attracted:
        return
    target = player
    is_attracted = true
    current_speed = base_speed

O start_attraction e o gatilho. Ele recebe o player como alvo e marca o item como atraido. A guarda if is_attracted evita que o item reinicie a velocidade caso entre e saia do campo varias vezes. Sem ela, um coletavel na borda do raio poderia ficar "engasgando" entre parado e atraido, perdendo a aceleracao acumulada.

Movimento com aceleracao progressiva

O coracao do game feel esta no _physics_process. A cada frame, calculamos a direcao ate o player, aumentamos a velocidade um pouco e movemos o item. O segredo e que a velocidade nao e constante. Ela parte de um valor baixo e cresce ate um teto, dando aquela sensacao de que o item "ganha impulso" conforme vai chegando perto.

func _physics_process(delta: float) -> void:
    if not is_attracted or target == null:
        return

    var to_target: Vector2 = target.global_position - global_position
    var distance: float = to_target.length()

    # acelera ate o teto, sem nunca passar de max_speed
    current_speed = move_toward(current_speed, max_speed, acceleration * max_speed * delta)

    var direction: Vector2 = to_target.normalized()
    global_position += direction * current_speed * delta

Repare no uso de move_toward para a velocidade. Em vez de somar um valor fixo e checar limites na mao, o move_toward(current_speed, max_speed, passo) cuida disso sozinho, parando exatamente em max_speed. O passo acceleration * max_speed * delta torna a aceleracao proporcional ao teto, entao se voce aumentar max_speed a curva continua coerente. O resultado e um item que sai devagar e chega voando, exatamente o tipo de movimento que o cerebro do jogador acha satisfatorio.

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

Disparando a atracao a partir do player

Falta ligar os dois lados. Quando a area de ima do player detecta um coletavel, ela chama o start_attraction daquele item passando o proprio player como alvo. Como os coletaveis sao Area2D, a deteccao acontece via sinal area_entered.

func _on_magnet_area_area_entered(area: Area2D) -> void:
    if area is Collectible:
        area.start_attraction(self)

func _on_magnet_area_body_entered(body: Node2D) -> void:
    # fallback caso algum coletavel seja um corpo
    if body.has_method("start_attraction"):
        body.start_attraction(self)

A checagem area is Collectible deixa o codigo claro e evita atrair qualquer area que entre no campo, como gatilhos de cenario ou zonas de dano. So o que for da classe Collectible reage ao ima. Se voce tiver tipos diferentes de loot, todos podem herdar dessa mesma classe ou implementar o metodo start_attraction, e o player nao precisa saber os detalhes de cada um.

A coleta no contato

Atrair e metade do trabalho. A outra metade e coletar quando o item finalmente encosta no player. Para isso, o coletavel tambem observa o proprio body_entered. Quando o corpo que entra e o player, o item aplica seu efeito (somar moeda, dar vida, o que for) e se remove da cena.

func _on_body_entered(body: Node2D) -> void:
    if body == target or body.is_in_group("player"):
        _collect(body)

func _collect(player: Node2D) -> void:
    if player.has_method("add_coins"):
        player.add_coins(1)
    # efeito visual ou som entrariam aqui
    queue_free()

Usar o grupo player deixa o coletavel funcionar mesmo antes de ser atraido. Se o jogador simplesmente andar por cima de uma moeda parada, ela e coletada do mesmo jeito, sem depender do ima. O ima e um conforto, nao uma obrigacao. Lembre de adicionar o player ao grupo no editor ou via add_to_group("player") no _ready.

Ajustando o game feel

Com a base pronta, o que separa um ima morno de um ima delicioso sao os numeros. Vale brincar com tres variaveis.

A primeira e o base_speed. Se ele for alto demais, o item ja sai rapido e perde aquele momento de "puxao". Um valor baixo, em torno de 60, da o arranque suave que valoriza a aceleracao.

A segunda e o max_speed. Esse e o teto da velocidade. Itens que precisam chegar quase instantaneamente (moedas em cascata, por exemplo) pedem um teto alto. Loot mais raro, que voce quer que o jogador veja viajando pela tela, pode ter um teto menor para durar mais no ar.

A terceira e a acceleration. Valores baixos deixam a curva longa e dramatica. Valores altos fazem o item disparar quase de imediato. Comece em algo como 6 e ajuste no olho, com o jogo rodando.

Um truque extra de game feel e adicionar um leve atraso na coleta usando lerp na escala do sprite, encolhendo o item conforme ele se aproxima. Isso reforca a leitura de que o coletavel esta sendo "sugado". Outra opcao e tocar um som com pitch que sobe a cada moeda coletada em sequencia, criando aquela escala musical viciante quando o ima varre uma pilha de itens de uma vez.

Cuidados de performance

Com dezenas de itens na tela, o _physics_process de cada um roda todo frame. Na pratica isso e barato, porque a conta e so um vetor e um move_toward. Ainda assim, vale garantir que itens nao atraidos retornem cedo (foi o que fizemos com a guarda no topo do _physics_process). Assim, apenas os coletaveis dentro do campo do ima gastam processamento de movimento. Os outros ficam dormindo ate alguem chegar perto.

Se o seu jogo tiver centenas de itens simultaneos, considere desligar o _physics_process por completo enquanto o item esta parado e religar so no start_attraction com set_physics_process(true). E uma micro otimizacao que raramente importa, mas existe se voce precisar.

Onde levar a partir daqui

O ima que voce montou e a fundacao de varios sistemas de coleta. Ele combina muito bem com power-ups coletaveis, onde um item especial pode aumentar o magnet_radius por alguns segundos, transformando o player num aspirador de loot. Tambem encaixa direto com loot drop de itens, ja que cada peca que cai de um inimigo pode usar exatamente essa mesma classe Collectible e ser atraida sem nenhum codigo extra.

A partir daqui, experimente variar o comportamento por tipo de item. Moedas com ima forte e instantaneo, gemas raras com puxao lento e cinematografico, fragmentos que so reagem ao ima depois de pousar no chao. Cada ajuste muda a sensacao da coleta, e e justamente esse controle fino que faz a diferenca entre um jogo que parece bom e um que parece otimo nas maos do jogador.