Gancho (grappling hook) 2D no Godot 4: tutorial passo a passo

Gancho grappling hook 2D no Godot 4: mire com o mouse, detecte a ancora com RayCast2D, puxe o player e desenhe a corda com Line2D em GDScript tipado.
Fazer um gancho (grappling hook) 2D no Godot 4 e uma daquelas mecanicas que parecem complicadas de longe e ficam simples quando voce separa o problema em partes. No fundo sao tres coisas que conversam: atirar uma linha numa direcao, descobrir se essa linha acertou um ponto onde da para se agarrar, e puxar o player ate la enquanto desenha a corda na tela. Neste tutorial a gente monta exatamente isso em cima de um CharacterBody2D, usando um RayCast2D para detectar a ancora e um Line2D para a corda, com tudo tipado em GDScript de Godot 4.
Gancho (grappling hook) 2D no Godot 4: tutorial passo a passo
Vou assumir que voce ja tem um personagem que anda e pula. Se ainda nao chegou nesse ponto, vale comecar pelo tutorial de movimento de personagem em plataforma 2D, porque o gancho entra como uma camada extra de movimento sobre uma base que ja precisa funcionar. Se voce ja domina pulo duplo e pulo de parede, melhor ainda, porque o gancho combina muito bem com as ideias do post de double jump e wall jump: sao todas variacoes de como o player se reposiciona no ar.
A ideia por tras do gancho
Antes de qualquer codigo, vale fixar o fluxo na cabeca. O jogador aperta uma acao (digamos, o botao do mouse). Nesse instante a gente calcula a direcao da mira, que aqui vai ser do player ate a posicao do mouse no mundo. Disparamos um raio nessa direcao e perguntamos: tem algo solido no caminho dentro do alcance? Se nao tem, nada acontece, o tiro errou. Se tem, guardamos o ponto exato onde acertou e marcamos que o gancho esta preso.
Enquanto o gancho esta preso, todo quadro a gente faz duas coisas. Aplica uma velocidade no player em direcao a ancora, puxando ele para la, e atualiza a corda do Line2D para ligar o player ao ponto ancorado. O player solta o gancho em duas situacoes: quando aperta a acao de novo, ou quando chega perto o bastante da ancora (porque continuar puxando depois disso so faria ele tremer colado no ponto).
Essa divisao em estados, solto e preso, e o coracao da mecanica. Quase todo bug de gancho vem de misturar a logica dos dois estados, entao vamos manter eles bem separados.
Montando a cena
A cena do player e um CharacterBody2D com os filhos de sempre (um Sprite2D e um CollisionShape2D) mais dois nodes novos:
- Um
RayCast2D, que vai detectar a ancora. Deixe oenabledligado e, no Inspector, garanta que acollision_maskdele bate com a layer das plataformas onde o gancho pode grudar. A propriedadetarget_positiondefine a ponta do raio em coordenadas locais, e e ela que vamos girar na direcao da mira por codigo. - Um
Line2D, que vai desenhar a corda. Ajuste a largura (width) e a cor no Inspector para o visual que voce quiser. Comece com a lista de pontos vazia, porque a gente preenche em tempo real.
Um detalhe importante de hierarquia: o Line2D sendo filho do player herda a transformacao do player, entao os pontos que voce adicionar nele sao interpretados em coordenadas locais. Por isso, mais para frente, vamos converter as posicoes globais (do player e da ancora) para o espaco local do Line2D antes de desenhar. Esquecer essa conversao e o erro mais comum: a corda aparece, mas torta, em algum canto errado da tela.
Calculando a direcao da mira
A direcao da mira sai de um par de funcoes que o Godot 4 ja oferece. A posicao do mouse no mundo vem de get_global_mouse_position(), e a direcao normalizada do player ate esse ponto vem de global_position.direction_to(...). O resultado e um Vector2 de comprimento 1 apontando para onde o jogador mirou.
var mira: Vector2 = global_position.direction_to(get_global_mouse_position())
Esse vetor serve para dois propositos. Primeiro, para posicionar a ponta do RayCast2D: multiplicando a direcao pelo alcance maximo do gancho, a gente diz ate onde o raio deve enxergar. Segundo, se o gancho acertar, a mesma logica de direcao reaparece (agora do player ate a ancora) para saber para onde puxar.
Disparando o raio e detectando a ancora
Quando o jogador aperta a acao, apontamos o RayCast2D na direcao da mira e pedimos uma atualizacao imediata com force_raycast_update(). Por padrao o RayCast2D so atualiza no proximo passo de fisica, e a gente quer a resposta no mesmo quadro do clique, entao essa chamada e necessaria.
func tentar_lancar_gancho() -> void:
var mira: Vector2 = global_position.direction_to(get_global_mouse_position())
raycast.target_position = mira * ALCANCE_GANCHO
raycast.force_raycast_update()
if raycast.is_colliding():
ponto_ancora = raycast.get_collision_point()
gancho_ativo = true
Repare que target_position e local ao RayCast2D, entao a gente usa a direcao da mira como esta, sem converter. Se houver colisao, get_collision_point() devolve a posicao da ancora ja em coordenadas globais, que e o que precisamos para puxar e desenhar.
Puxando o player em direcao a ancora
Com o gancho preso, a parte de movimento e direta. A cada quadro, calculamos a direcao do player ate a ancora e aplicamos isso como velocidade, multiplicado pela forca de puxao. Aqui o estado preso sobrescreve a velocidade normal, entao a gravidade nao atua durante o puxao e o player viaja em linha reta ate o ponto.
var direcao: Vector2 = global_position.direction_to(ponto_ancora)
velocity = direcao * FORCA_PUXAO
A condicao de soltar tambem mora aqui. Usamos distance_to() para medir o quanto falta ate a ancora: se for menor que um limiar, soltamos. Isso evita o player ficar tremendo colado no ponto, oscilando entre passar e voltar.
if global_position.distance_to(ponto_ancora) < DISTANCIA_SOLTAR:
soltar_gancho()
Soltar o gancho e so voltar ao estado solto e esconder a corda. Um truque de game feel: ao soltar perto da ancora, o player ainda carrega a velocidade do puxao, entao ele sai voando na direcao em que vinha. Isso da aquele momento gostoso de lancamento. Se voce soltar exatamente em cima da ancora, esse impulso some, e por isso o limiar de distancia ajuda a preservar o embalo.
Desenhando a corda com Line2D
A corda e uma linha de dois pontos: a posicao do player e a da ancora. Limpamos os pontos antigos e adicionamos os dois novos, lembrando de converter de global para local porque o Line2D e filho do player.
func atualizar_corda() -> void:
corda.clear_points()
corda.add_point(corda.to_local(global_position))
corda.add_point(corda.to_local(ponto_ancora))
O to_local() faz a conversao do espaco global para o espaco do Line2D. Como o Line2D acompanha o player, o primeiro ponto quase sempre cai perto da origem local, mas usar to_local(global_position) deixa o codigo correto independentemente de offsets que voce ponha na cena. Quando o gancho esta solto, limpamos os pontos para a corda sumir.
Integrando com gravidade e move_and_slide
Falta costurar tudo no _physics_process. A logica se divide pelo estado. No estado solto, a gente aplica gravidade normalmente, le o input de movimento horizontal e deixa o player andar e cair como em qualquer plataforma. No estado preso, ignoramos a gravidade e o movimento normal e mandamos o player rumo a ancora.
A acao de lancar funciona como um botao de liga e desliga. Se o gancho esta solto e o jogador aperta, tentamos lancar. Se ja esta preso e ele aperta de novo, soltamos. Em ambos os estados, no fim, chamamos move_and_slide() uma unica vez para resolver as colisoes com a velocity atual.
O script completo
Aqui esta o script inteiro, comentado e tipado, pronto para colar no CharacterBody2D do player. Ajuste os nomes das acoes (mover_esquerda, mover_direita, lancar_gancho) para baterem com o seu Input Map e confira os caminhos dos nodes RayCast2D e Line2D.
extends CharacterBody2D
# Movimento normal de plataforma.
const VELOCIDADE: float = 220.0
# Ajuste de game feel do gancho.
const ALCANCE_GANCHO: float = 320.0 # alcance maximo do tiro, em pixels
const FORCA_PUXAO: float = 1000.0 # velocidade do puxao ate a ancora
const DISTANCIA_SOLTAR: float = 16.0 # solta o gancho ao chegar perto
# Gravidade vinda das configuracoes do projeto.
var gravidade: float = ProjectSettings.get_setting("physics/2d/default_gravity")
# Estado do gancho.
var gancho_ativo: bool = false
var ponto_ancora: Vector2 = Vector2.ZERO
@onready var raycast: RayCast2D = $RayCast2D
@onready var corda: Line2D = $Line2D
func _ready() -> void:
# Comeca sem corda desenhada.
corda.clear_points()
func _physics_process(delta: float) -> void:
# A acao de lancar liga ou desliga o gancho.
if Input.is_action_just_pressed("lancar_gancho"):
if gancho_ativo:
soltar_gancho()
else:
tentar_lancar_gancho()
if gancho_ativo:
_processar_preso()
else:
_processar_solto(delta)
move_and_slide()
# Movimento normal: gravidade e controle horizontal.
func _processar_solto(delta: float) -> void:
if not is_on_floor():
velocity.y += gravidade * delta
var direcao_input: float = Input.get_axis("mover_esquerda", "mover_direita")
if direcao_input != 0.0:
velocity.x = direcao_input * VELOCIDADE
else:
velocity.x = move_toward(velocity.x, 0.0, VELOCIDADE)
# Preso ao gancho: puxa o player ate a ancora.
func _processar_preso() -> void:
var direcao: Vector2 = global_position.direction_to(ponto_ancora)
velocity = direcao * FORCA_PUXAO
if global_position.distance_to(ponto_ancora) < DISTANCIA_SOLTAR:
soltar_gancho()
return
atualizar_corda()
# Mira na direcao do mouse e checa se o raio acertou uma ancora.
func tentar_lancar_gancho() -> void:
var mira: Vector2 = global_position.direction_to(get_global_mouse_position())
raycast.target_position = mira * ALCANCE_GANCHO
raycast.force_raycast_update()
if raycast.is_colliding():
ponto_ancora = raycast.get_collision_point()
gancho_ativo = true
atualizar_corda()
# Volta ao estado solto e esconde a corda.
func soltar_gancho() -> void:
gancho_ativo = false
corda.clear_points()
# Liga a posicao do player a ancora, convertendo para o espaco local da corda.
func atualizar_corda() -> void:
corda.clear_points()
corda.add_point(corda.to_local(global_position))
corda.add_point(corda.to_local(ponto_ancora))
Ajustando a forca de puxao e o game feel
Os tres numeros que mais mudam a sensacao do gancho sao ALCANCE_GANCHO, FORCA_PUXAO e DISTANCIA_SOLTAR. O alcance decide o quao longe da plataforma o player consegue se prender, e mexer nele muda bastante o ritmo do nivel: alcance curto pede precisao, alcance longo deixa o player atravessar telas inteiras num lancamento.
A FORCA_PUXAO e onde mora a maior parte do feeling. Valores baixos dao um puxao lento e pesado, quase de elastico esticando. Valores altos dao aquele esticao rapido de jogo de acao. Comece testando perto de 1000 e suba ou desca de cem em cem ate achar o ponto certo. Mude um numero de cada vez e jogue: misturar varios ajustes ao mesmo tempo confunde, porque voce perde a nocao de qual deles causou a mudanca.
A DISTANCIA_SOLTAR parece um detalhe, mas e ela que evita o tremor colado na ancora e, de quebra, controla quanto impulso o player carrega ao soltar. Limiar maior solta mais cedo e mantem mais velocidade, o que da saltos mais longos depois do gancho. Se voce quiser um movimento mais de pendulo, com curva, da para nao sobrescrever a velocidade inteira no estado preso e somar uma fracao da gravidade, deixando o player descrever um arco em vez de uma reta. Esse e um bom proximo experimento depois que o basico estiver redondo.
Fechando o ciclo
Com esse script voce tem um gancho 2D completo no Godot 4: mira na direcao do mouse com global_position.direction_to(get_global_mouse_position()), detecta a ancora com RayCast2D e force_raycast_update(), puxa o player com velocity rumo ao get_collision_point(), solta perto da ancora via distance_to() e desenha a corda com o Line2D usando to_local(). A separacao entre estado solto e preso e o que mantem tudo previsivel e facil de estender.
O gancho e o tipo de mecanica que abre muitas portas: da para combinar com dash, com pulo de parede, com plataformas que se movem, e cada combinacao vira um desafio de level design diferente. Se voce quer aprender a montar esse tipo de sistema com base solida, entendendo o porque de cada decisao e nao so colando codigo, vale conhecer o melhor curso de Godot para construir seu projeto do zero com acompanhamento. Abra o editor, monte a cena, cole o script e ajuste os numeros ate o gancho ficar do jeito que voce imaginou.
Perguntas frequentes
Como funciona um gancho (grappling hook) 2D no Godot 4?
A ideia tem tres fases. Primeiro o player mira numa direcao (no mouse ou num alvo) e dispara o gancho. Em seguida um RayCast2D checa se essa linha acerta um ponto de ancoragem valido. Se acertou, voce guarda esse ponto e entra no estado preso, aplicando velocidade em direcao a ancora ate o player chegar perto ou soltar.
Qual node usar para detectar o ponto de ancoragem do gancho?
Use um RayCast2D filho do CharacterBody2D. Voce aponta o target_position dele na direcao da mira, forca a atualizacao com force_raycast_update() e checa is_colliding(). Se colidiu, get_collision_point() devolve a posicao exata da ancora em coordenadas globais, que vira o ponto para onde o player sera puxado.
Como desenhar a corda do gancho na tela?
Um Line2D resolve. Voce limpa os pontos com clear_points() e adiciona dois: a posicao do player e a posicao da ancora. Como o Line2D normalmente fica em coordenadas locais, converta os pontos globais com to_local() antes de adicionar. Atualize esses dois pontos a cada quadro enquanto o gancho estiver preso.
Como ajustar a forca de puxao para o gancho ficar gostoso de usar?
A forca de puxao e a velocidade com que o player viaja ate a ancora. Valores baixos dao um movimento lento e controlado, valores altos dao aquela sensacao de lancamento rapido. Comece em torno de 900 a 1200 e teste jogando. Tambem vale soltar o gancho um pouco antes de chegar na ancora para o player manter impulso e sair voando, em vez de parar seco.
Preciso desligar a gravidade enquanto o player esta preso no gancho?
Depende da sensacao que voce quer. Se aplicar a velocidade direta para a ancora sem gravidade, o movimento fica reto e previsivel. Se mantiver parte da gravidade, o player faz uma curva mais natural, como um pendulo solto. Neste tutorial o estado preso sobrescreve a velocidade rumo a ancora, entao a gravidade nao atua durante o puxao, mas e facil misturar as duas se quiser um arco.


