pratica

Gerenciamento de Cenas e Transições

Aprenda sobre gerenciamento de cenas e transições

30 min
Aula 4 de 5

Gerenciamento de Cenas e Transições em Godot

Olá, futuros desenvolvedores de jogos! 👋 Sejam bem-vindos à terceira aula do módulo "Construindo Seu Primeiro Jogo 2D". Hoje, vamos mergulhar em um dos pilares da arquitetura de Godot: o gerenciamento de cenas e como criar transições suaves entre elas.

Em Godot, tudo é uma cena. Seu menu principal é uma cena, seu nível de jogo é uma cena, até mesmo seu personagem pode ser uma cena! Entender como carregar, descarregar e navegar entre essas cenas é fundamental para construir jogos complexos e envolventes. Além disso, vamos aprender a adicionar aquele toque profissional com transições visuais entre as cenas.

Esta é uma aula prática, então prepare seu Godot Engine! Vamos codificar e ver tudo funcionando em tempo real. 🚀


1. O que são Cenas em Godot? 🌳

Em Godot, uma cena é uma coleção de nós (Nodes) organizados em uma árvore. Uma cena pode ser tão simples quanto um único nó ou tão complexa quanto um nível inteiro de jogo com personagens, inimigos, UI e muito mais.

A beleza do sistema de cenas de Godot é que você pode aninhar cenas dentro de outras cenas. Isso promove a modularidade e a reutilização de componentes.

Por que gerenciar cenas?

  • Organização: Mantém seu projeto limpo e fácil de entender.
  • Performance: Permite carregar e descarregar apenas o que é necessário, economizando memória e recursos.
  • Fluxo do Jogo: Define a progressão entre diferentes estados do jogo (menu, gameplay, game over, etc.).

2. Carregando e Descarregando Cenas 🔄

Existem algumas maneiras principais de lidar com cenas em Godot. Vamos explorar as mais comuns.

2.1. Trocando para uma Nova Cena (Direto)

A forma mais simples de mudar de cena é usando os métodos change_scene_to_file() ou change_scene_to_packed() da SceneTree.

get_tree().change_scene_to_file(path)

Este método carrega uma nova cena a partir de um caminho de arquivo e a define como a cena principal da árvore. A cena atual é descarregada automaticamente.

# Exemplo: Mudar para a cena "GameScene.tscn"
func _on_start_button_pressed():
    get_tree().change_scene_to_file("res://scenes/GameScene.tscn")

👉 Vantagem: Simples e direto. 👉 Desvantagem: Não permite pré-carregamento. A cena é carregada no momento da chamada, o que pode causar uma pequena pausa ("stutter") se a cena for muito grande.

get_tree().change_scene_to_packed(packed_scene)

Este método é similar ao anterior, mas aceita um recurso PackedScene já carregado. Isso é útil para pré-carregar cenas.

@onready var game_scene_packed: PackedScene = preload("res://scenes/GameScene.tscn")
 
func _on_start_button_pressed():
    get_tree().change_scene_to_packed(game_scene_packed)

👉 Vantagem: Permite pré-carregamento (preload), evitando pausas no momento da troca. 👉 Desvantagem: Ainda é uma troca instantânea sem transição visual.

2.2. Instanciando Cenas Dinamicamente

Às vezes, você não quer substituir a cena inteira, mas sim adicionar uma nova cena como um filho da cena atual. Isso é comum para instanciar inimigos, projéteis, ou sub-menus.

# Exemplo: Instanciar um inimigo (que é uma cena separada)
func spawn_enemy():
    var enemy_scene: PackedScene = preload("res://entities/Enemy.tscn")
    var enemy_instance = enemy_scene.instantiate()
    add_child(enemy_instance) # Adiciona o inimigo como filho do nó atual
    enemy_instance.global_position = Vector2(randf_range(0, 1000), randf_range(0, 600))

Para "descarregar" uma cena instanciada dinamicamente, você geralmente a remove da árvore e a libera da memória usando queue_free().

# Exemplo: Remover um inimigo
func _on_enemy_health_zero(enemy_node):
    enemy_node.queue_free() # Marca o nó para ser liberado com segurança

3. Criando Transições de Cena Suaves ✨

Trocar de cena instantaneamente pode ser abrupto. Adicionar uma transição, como um fade-out e fade-in, melhora a experiência do jogador. Vamos criar um sistema simples de transição usando um CanvasLayer e um ColorRect.

3.1. A Cena de Transição (TransitionLayer.tscn)

Vamos criar uma cena reutilizável para o nosso efeito de transição.

  1. Crie uma nova cena: Node como raiz. Renomeie-o para TransitionLayer.
  2. Adicione um CanvasLayer: Este nó garante que a transição seja desenhada acima de tudo, independentemente da câmera ou do zoom.
  3. Adicione um ColorRect como filho do CanvasLayer:
    • Defina Layout para Full Rect (no Inspector, clique em Layout e escolha Full Rect). Isso fará com com que ele cubra a tela inteira.
    • Defina a cor para preto (#000000).
    • Defina o Modulate inicial para uma cor totalmente transparente (clique no Modulate e arraste o A (Alpha) para 0).
  4. Salve a cena como res://scenes/TransitionLayer.tscn.
  5. Anexe um script ao nó TransitionLayer: Salve como res://scripts/TransitionLayer.gd.
# scripts/TransitionLayer.gd
extends CanvasLayer
 
@onready var color_rect: ColorRect = $ColorRect
var tween: Tween
 
# Sinal que será emitido quando a transição de fade-out terminar
signal fade_out_finished
 
func _ready():
    # Garante que o ColorRect esteja transparente no início
    color_rect.modulate = Color(0, 0, 0, 0)
 
func fade_out(duration: float = 0.5):
    if tween:
        tween.kill() # Interrompe qualquer tween anterior
    
    tween = create_tween()
    # Fazer o ColorRect ficar opaco (Alpha = 1)
    tween.tween_property(color_rect, "modulate", Color(0, 0, 0, 1), duration)
    await tween.finished # Espera a animação terminar
    emit_signal("fade_out_finished") # Avisa que o fade-out terminou
 
func fade_in(duration: float = 0.5):
    if tween:
        tween.kill() # Interrompe qualquer tween anterior
        
    tween = create_tween()
    # Fazer o ColorRect ficar transparente (Alpha = 0)
    tween.tween_property(color_rect, "modulate", Color(0, 0, 0, 0), duration)
    await tween.finished # Espera a animação terminar
    # Opcional: pode emitir um sinal aqui também se precisar saber quando o fade-in termina

3.2. Gerenciador de Cenas Global (Autoload Singleton) 🌐

Para centralizar a lógica de troca de cenas e transições, vamos usar um Autoload Singleton. Isso é um script que é carregado automaticamente na inicialização do jogo e fica acessível de qualquer lugar.

  1. Crie um novo script: File -> New Script.... Salve como res://scripts/SceneManager.gd.
  2. Conteúdo do script:
# scripts/SceneManager.gd
extends Node
 
# Pré-carrega a cena de transição para instanciá-la quando necessário
var transition_scene_packed: PackedScene = preload("res://scenes/TransitionLayer.tscn")
var current_transition_layer: TransitionLayer = null
 
# Opcional: pré-carregar as cenas principais para evitar pausas
var main_menu_scene_path = "res://scenes/MainMenu.tscn"
var game_scene_path = "res://scenes/GameScene.tscn"
 
# Função para mudar de cena com transição
func goto_scene(path: String):
    # 1. Instancia ou obtém a camada de transição
    if !is_instance_valid(current_transition_layer):
        current_transition_layer = transition_scene_packed.instantiate()
        get_tree().root.add_child(current_transition_layer)
        # Garante que a transição esteja sempre no topo da árvore de nós para renderização
        current_transition_layer.owner = get_tree().root 
        current_transition_layer.layer = 128 # Valor alto para garantir que esteja acima de tudo
 
    # 2. Inicia o fade-out
    await current_transition_layer.fade_out()
 
    # 3. Muda a cena
    var error = get_tree().change_scene_to_file(path)
    if error != OK:
        push_error("Erro ao mudar para a cena: ", path, " - Código de erro: ", error)
        # Se houver erro, faça um fade-in de volta ou trate de outra forma
        await current_transition_layer.fade_in()
        return
 
    # 4. Inicia o fade-in na nova cena
    await current_transition_layer.fade_in()
    
    # Opcional: Se a transição não for mais necessária, pode ser liberada.
    # No entanto, mantê-la pode evitar instanciar e liberar repetidamente.
    # Se você quiser liberar:
    # current_transition_layer.queue_free()
    # current_transition_layer = null
  1. Configure o script como Autoload:
    • Vá em Project -> Project Settings....
    • Na aba Autoload, clique no botão Add Folder (ícone de pasta) e selecione res://scripts/SceneManager.gd.
    • Deixe o nome do nó como SceneManager.
    • Clique em Add.

Agora, o SceneManager está disponível globalmente e pode ser acessado de qualquer script usando SceneManager.goto_scene("res://path/to/scene.tscn").


4. Exercícios Práticos: Construindo Nosso Fluxo de Jogo 🎮

Vamos aplicar o que aprendemos para criar um fluxo de jogo básico com transições.

✅ Tarefas:

  1. Crie as Cenas Principais:

    • Crie uma nova cena Node2D. Adicione um Label com o texto "Menu Principal" e um Button com o texto "Iniciar Jogo". Salve como res://scenes/MainMenu.tscn.
    • Crie outra nova cena Node2D. Adicione um Label com o texto "Cena do Jogo" e um Button com o texto "Voltar ao Menu". Salve como res://scenes/GameScene.tscn.
  2. Implemente a Lógica de Navegação no MainMenu.tscn:

    • Anexe um script ao nó MainMenu (ou ao Node2D raiz).
    • Conecte o sinal pressed() do botão "Iniciar Jogo" a uma função no script.
    • Dentro dessa função, chame SceneManager.goto_scene("res://scenes/GameScene.tscn").
    # scenes/MainMenu.gd
    extends Node2D
     
    func _on_start_game_button_pressed():
        SceneManager.goto_scene(SceneManager.game_scene_path) # Usando o path predefinido no SceneManager
  3. Implemente a Lógica de Navegação no GameScene.tscn:

    • Anexe um script ao nó GameScene (ou ao Node2D raiz).
    • Conecte o sinal pressed() do botão "Voltar ao Menu" a uma função no script.
    • Dentro dessa função, chame SceneManager.goto_scene("res://scenes/MainMenu.tscn").
    # scenes/GameScene.gd
    extends Node2D
     
    func _on_back_to_menu_button_pressed():
        SceneManager.goto_scene(SceneManager.main_menu_scene_path) # Usando o path predefinido no SceneManager
  4. Defina a Cena Principal do Projeto:

    • Vá em Project -> Project Settings....
    • Na aba Application -> Run, clique no ícone de pasta ao lado de Main Scene e selecione res://scenes/MainMenu.tscn.
  5. Teste o Jogo!

    • Execute o projeto (F5). Você deve ver o "Menu Principal".
    • Clique em "Iniciar Jogo" e observe a transição de fade.
    • Na "Cena do Jogo", clique em "Voltar ao Menu" e veja a transição novamente.

5. Resumo e Próximos Passos 🎓

Uau! Você acabou de construir um sistema robusto de gerenciamento de cenas com transições em Godot. 🎉

O que aprendemos hoje:

  • Cenas são os blocos de construção de Godot.
  • change_scene_to_file() e change_scene_to_packed() para trocar a cena principal.
  • preload() e instance() para carregar e adicionar cenas dinamicamente.
  • Como criar uma camada de transição (TransitionLayer) usando CanvasLayer e ColorRect.
  • Como usar Autoload Singletons (SceneManager) para centralizar a lógica de gerenciamento de cena e transição, tornando seu código mais limpo e reutilizável.
  • Como usar Tween para animar propriedades de nós de forma suave.
  • Como usar await para esperar a conclusão de operações assíncronas (como animações).

Próximos Passos:

  • Transições Mais Complexas: Explore outras formas de transição (wipe, slide, blinds) usando ShaderMaterial no ColorRect ou AnimationPlayer.
  • Telas de Carregamento: Para cenas muito grandes, o carregamento pode levar tempo. Pesquise sobre ResourceLoader.load_threaded_request() para carregamento assíncrono e crie uma tela de progresso.
  • Gerenciamento de Estado do Jogo: Use o SceneManager ou outro Autoload para persistir dados entre cenas (pontuação, vida do jogador, etc.).

Continue praticando e experimentando! O gerenciamento de cenas é uma habilidade essencial para qualquer desenvolvedor de jogos em Godot. Até a próxima aula! 👋🎮

© 2025 Escola All Dev. Todos os direitos reservados.

Gerenciamento de Cenas e Transições - Fundamentos do Godot: Desenvolvimento de games para iniciantes | escola.all.dev.br