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.

Ferramenta:Godot

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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalĂ­cioGarantia de 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. E quando o item coletado for uma chave, feche o ciclo dando uso a ela numa porta trancada que so abre com chave.

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.

Perguntas frequentes

Como funciona um ima de itens em um jogo 2D?

O player carrega uma area de deteccao circular (o campo do ima). Quando um coletavel entra nesse raio, ele troca para um estado atraido e passa a se mover em direcao ao player a cada quadro. Ao encostar de fato no corpo do player, o item e coletado. A logica tem duas partes que conversam: a area que detecta e o coletavel que se move.

Qual node usar para detectar itens no raio do ima no Godot 4?

Use uma Area2D filha do player com um CollisionShape2D circular representando o alcance do ima. A Area2D dispara os sinais body_entered ou area_entered quando um coletavel entra no raio, e e nesse momento que voce marca o item como atraido. O raio do circulo define o tamanho do campo de atracao.

Como fazer o item acelerar conforme chega perto do player?

Em vez de mover o coletavel a velocidade fixa, aumente a velocidade conforme ele se aproxima do alvo. Voce calcula a direcao ate o player e aplica uma aceleracao progressiva a cada quadro, usando delta para ficar independente de FPS. Isso cria a sensacao de sucçao que torna a coleta gostosa.

O ima de itens funciona com moedas e loot ao mesmo tempo?

Sim. Qualquer coletavel que compartilhe o mesmo script de atracao (ou herde dele) responde ao ima, sejam moedas, gemas ou loot. O segredo e padronizar como o coletavel entra no estado atraido e como ele se move, deixando o tipo de item apenas como detalhe visual e de pontuacao.

Da para ajustar o alcance do ima durante o jogo?

Da. Como o alcance e definido pelo raio do CollisionShape2D da Area2D, basta alterar esse raio por codigo para o ima ficar maior ou menor, por exemplo ao pegar um power-up. Exportar o valor do raio como variavel no Inspector tambem deixa voce calibrar o alcance sem mexer no codigo.