Teleporte e portais em 2D no Godot 4

Aprenda a criar portal godot 2d com Area2D, teleporte sem loop infinito, par de portais ligados, preservacao de velocidade e efeito visual na transicao.
Portais sao uma das mecanicas mais satisfatorias de implementar em um jogo de plataforma ou puzzle. O jogador entra em um lugar e sai em outro, e quando funciona bem parece magica. Montar um portal godot 2d e simples na ideia: uma area que detecta o player e o move para um ponto de destino. O problema aparece nos detalhes. Se voce so reposicionar o player, ele cai dentro do portal de destino, que dispara o teleporte de volta, e voce entra num loop infinito que trava o jogo ou faz o personagem piscar entre os dois pontos.
Neste tutorial a gente resolve isso de verdade. Vamos montar um portal como Area2D, ligar dois portais em par, tratar o loop de re-entrada com um cooldown, preservar a velocidade do player na saida e adicionar um efeito visual rapido na transicao. No fim eu mostro a diferenca entre teleportar dentro da mesma cena e trocar de cena inteira, que sao problemas diferentes com solucoes diferentes.
Teleporte e portais em 2D no Godot 4
A estrutura basica de um portal godot 2d e um Area2D com um CollisionShape2D para a zona de deteccao e uma referencia para onde o player deve aparecer. O destino pode ser outro portal ou um simples Node2D marcador na cena. Vamos usar o @export para escolher o destino direto no editor, sem precisar fixar caminho no codigo.
A cena do portal
Crie uma cena com esta arvore:
Area2D(raiz, scriptportal.gd)CollisionShape2DSprite2DouAnimatedSprite2Dpara o visualMarker2DchamadoSpawnPoint(onde o player vai surgir)
O Marker2D separado e importante. Voce nao quer que o player apareca no centro exato do portal de destino, porque ai ele vai estar dentro da zona de deteccao. Coloque o SpawnPoint um pouco a frente da boca do portal, na direcao em que o jogador deve sair.
O script do portal
extends Area2D
class_name Portal
# Outro portal para onde este leva. Configurado no editor.
@export var destino: Portal
# Tempo que o portal de destino fica "surdo" depois de receber alguem.
@export var cooldown: float = 0.4
# Marcador interno de onde o player aparece ao chegar aqui.
@onready var spawn_point: Marker2D = $SpawnPoint
# Quando true, este portal ignora qualquer corpo que entrar.
var bloqueado: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if bloqueado:
return
if destino == null:
push_warning("Portal sem destino configurado: %s" % name)
return
if not body.is_in_group("player"):
return
_teleportar(body)
func _teleportar(body: Node2D) -> void:
# Bloqueia o portal de DESTINO para ele nao mandar o player de volta.
destino.bloquear_temporariamente()
# Move o player para o ponto de saida do portal de destino.
body.global_position = destino.spawn_point.global_position
func bloquear_temporariamente() -> void:
bloqueado = true
var timer := get_tree().create_timer(cooldown)
timer.timeout.connect(func() -> void: bloqueado = false)
O ponto central aqui e o bloquear_temporariamente(). Quando o portal A teleporta o player, ele nao bloqueia a si mesmo. Ele bloqueia o portal B, que e onde o player vai cair. Assim que o player entra na area do B, o B esta surdo e ignora a entrada. Depois do cooldown, o B volta a funcionar normalmente. Se o jogador quiser voltar pelo B, ele precisa sair e entrar de novo, o que e exatamente o comportamento esperado.
Repare que eu uso o grupo player para filtrar quem dispara o portal. Adicione o no do player ao grupo player no editor ou via codigo com add_to_group("player") no _ready() dele. Sem esse filtro, qualquer inimigo ou caixa fisica passaria pelo portal tambem, o que as vezes voce quer, mas e melhor decidir isso de proposito.
Ligando os portais em par
Com o @export var destino voce abre a cena da fase, seleciona o Portal A e arrasta o Portal B para o campo destino no inspetor. Depois faz o inverso no Portal B, apontando para o A. Pronto, A leva a B e B leva a A.
Se voce tem varios pares na mesma fase, cada par fica independente porque o bloqueio e por instancia, nao global. Um jogador atravessando o par 1 nao interfere no par 2.
Preservando velocidade e direcao
Teleportar so a posicao funciona, mas o resultado fica seco. Se o player entrou correndo para a direita e sai parado, quebra a sensacao de continuidade. O ideal e levar a velocidade junto. Como o player normalmente tem seu proprio velocity (em um CharacterBody2D), basta nao zerar nada e o movimento continua. Mas se voce quer rotacionar a direcao, por exemplo um portal na parede que cospe o player para cima, da para reorientar o vetor de velocidade usando o angulo entre os dois portais.
func _teleportar(body: Node2D) -> void:
destino.bloquear_temporariamente()
body.global_position = destino.spawn_point.global_position
# Se o corpo for um CharacterBody2D, reorienta a velocidade
# para a direcao em que o portal de destino aponta.
if body is CharacterBody2D:
var corpo := body as CharacterBody2D
var rotacao := destino.spawn_point.global_rotation - global_rotation
corpo.velocity = corpo.velocity.rotated(rotacao)
Aqui eu pego a diferenca de rotacao entre o ponto de entrada e o ponto de saida e aplico essa rotacao no vetor de velocidade. Se os dois portais apontam para o mesmo lado, a rotacao e zero e nada muda. Se o portal de destino esta girado noventa graus, a velocidade gira junto e o player sai na direcao certa, mantendo a inercia. Para manter simples, deixe os SpawnPoint com a rotacao apontando para a saida desejada.
Efeito visual na transicao
Um portal sem feedback visual confunde o jogador. Ele nao sabe se foi teleportado ou se algo bugou. Um flash rapido ou um burst de particulas resolve isso. A forma mais barata e um fade curto na tela inteira, mas para portais dentro da mesma cena costuma ser melhor um efeito local, no proprio personagem, para nao cobrir a acao.
Vou usar um GPUParticles2D de one shot no ponto de entrada e no de saida. Adicione um GPUParticles2D chamado Burst na cena do portal, marque one_shot como true e emitting como false.
@onready var burst: GPUParticles2D = $Burst
func _teleportar(body: Node2D) -> void:
destino.bloquear_temporariamente()
_emitir_efeito() # efeito na entrada
body.global_position = destino.spawn_point.global_position
destino._emitir_efeito() # efeito na saida
if body is CharacterBody2D:
var corpo := body as CharacterBody2D
var rotacao := destino.spawn_point.global_rotation - global_rotation
corpo.velocity = corpo.velocity.rotated(rotacao)
func _emitir_efeito() -> void:
burst.restart()
burst.emitting = true
O restart() garante que o sistema de particulas reinicie do zero mesmo se ainda estiver no meio de uma emissao anterior. Isso evita aquele bug em que o efeito so dispara na primeira vez. Se voce preferir um flash de cor, da para colocar um ColorRect branco com modulate.a indo de 1 a 0 via Tween rapido, algo como meio segundo, mas particulas tendem a parecer mais polidas para portais.
Se voce quiser um fade de tela cheia bem feito para momentos de impacto, vale ver como montar uma transicao de cena com fade reaproveitavel, que serve tanto para portais grandes quanto para mudanca de fase.
Teleporte na mesma cena versus troca de cena
Os portais ate aqui movem o player dentro da mesma cena. Isso e instantaneo e simples porque o no do player continua existindo, voce so muda a posicao dele. A camera segue, a fisica continua, nada e recarregado.
Trocar de cena e outra historia. Quando o portal leva para outra fase, o player precisa ser destruido com a cena atual e recriado na nova, ou voce mantem informacao de estado para reconstruir onde ele deve aparecer. O fluxo muda bastante.
@export var cena_destino: PackedScene
@export var id_spawn: String = "default"
func _on_body_entered(body: Node2D) -> void:
if bloqueado or not body.is_in_group("player"):
return
if cena_destino != null:
_trocar_de_cena()
elif destino != null:
_teleportar(body)
func _trocar_de_cena() -> void:
# Guarda em algum autoload onde o player deve aparecer na proxima cena.
GameState.proximo_spawn = id_spawn
get_tree().change_scene_to_packed(cena_destino)
A diferenca pratica e que na troca de cena voce nao tem mais o player do outro lado, entao nao da para arrastar a velocidade dele direto. Voce precisa de um autoload, aqui chamei de GameState, para guardar onde o player deve aparecer. Na nova cena, um script de inicializacao le GameState.proximo_spawn e posiciona o player no marcador correto. Por isso eu passo um id_spawn, um texto que identifica qual ponto de entrada usar na proxima fase.
Para teleporte na mesma cena, prefira sempre mover a posicao. E mais barato, mais suave e mais facil de depurar. Reserve a troca de cena para quando voce realmente muda de mapa.
Cuidados comuns
Alguns problemas aparecem com frequencia quando os portais comecam a ser testados de verdade.
O primeiro e o tamanho do CollisionShape2D de deteccao. Se for pequeno demais, o player atravessa em alta velocidade sem disparar o portal, porque a fisica deu um passo grande entre dois quadros. Deixe a zona com uma largura razoavel ou ative o Continuous CD no player se ele for muito rapido.
O segundo e esquecer de colocar o player no grupo player. Sem o grupo, o portal simplesmente nao reage e parece que o codigo esta errado quando na verdade o filtro esta correto.
O terceiro e posicionar o SpawnPoint dentro da area do portal de destino. Mesmo com o cooldown isso causa comportamento estranho se o cooldown for curto demais. Coloque o SpawnPoint claramente fora da zona de deteccao, na boca do portal apontando para fora.
Se o seu portal precisa de uma condicao para abrir, como o jogador ter pego um item, a logica e parecida com a de portas trancadas e chaves, so que em vez de tocar para abrir, o portal verifica a condicao antes de teleportar.
Fechando
Com Area2D, um Marker2D de saida e a flag de cooldown no portal de destino, voce tem portais robustos sem loop infinito. Adicione a reorientacao de velocidade e um burst de particulas e a mecanica passa de funcional para gostosa de usar. Comece pelo teleporte na mesma cena, que cobre a maioria dos casos, e so parta para troca de cena quando o destino for outra fase. A partir dessa base voce monta portais coloridos estilo puzzle, teleportes de checkpoint e ate atalhos secretos, sempre com o mesmo nucleo de codigo.


