Como Fazer um Jogo Point and Click no Godot 4: Movimento, Hotspots e Inventário

Como fazer um jogo point and click no Godot 4: movimento por clique, hotspots interativos, inventário, diálogo e estado do mundo em GDScript tipado.
Como Fazer um Jogo Point and Click no Godot 4: Movimento, Hotspots e Inventário
Se você quer aprender como fazer um jogo point and click, tem uma boa notícia: esse é um dos gêneros mais viáveis para um dev solo. Nada de física complicada, nada de combate balanceado, nada de rede. O gênero vive de lógica, escrita e alguns sistemas pequenos que conversam entre si. Neste guia eu mostro essas peças uma a uma, com código real de Godot 4 em GDScript tipado: movimento por clique, hotspots interativos, inventário e estado do mundo. No final, falo do design de puzzle e do escopo certo para o primeiro projeto.
O que define um jogo point and click
Monkey Island, Day of the Tentacle e, mais recentemente, Thimbleweed Park definem o formato: você aponta, clica e o personagem age. Explora cenários, examina objetos, coleta itens, combina coisas improváveis, conversa com NPCs e resolve puzzles que destravam a história. Não existe teste de reflexo. O desafio inteiro está em pensar.
Isso significa que um jogo point and click é, na prática, quatro sistemas pequenos mais uma cola:
- Movimento por clique: o personagem anda até onde você clicou.
- Hotspots: as áreas interativas do cenário (porta, gaveta, NPC).
- Inventário: itens que você coleta e combina com o cenário.
- Diálogo: conversas com escolhas.
- Estado do mundo: a cola. Um registro do que o jogador já fez, para o jogo reagir de forma consistente.
Cada ambiente do jogo vira uma cena da Godot: o escritório é uma cena, o corredor é outra. Cada cena carrega seus hotspots e seu fundo, e o personagem é instanciado nelas. Trocar de ambiente é trocar de cena, mantendo o inventário e o estado do mundo vivos em um autoload. Vamos montar peça por peça.
Movimento por clique: a base de tudo
O personagem é um CharacterBody2D com um CollisionShape2D e um Sprite2D. A lógica: guardar o ponto clicado em target_position, andar na direção dele a cada frame de física e parar quando chegar perto o suficiente.
Uso _unhandled_input em vez de _input de propósito. Eventos que a UI consome, como um clique no inventário, nunca chegam em _unhandled_input. Isso evita o bug clássico do personagem sair andando quando o jogador clica em um botão.
extends CharacterBody2D
var speed: float = 200.0
var stop_distance: float = 4.0
var target_position: Vector2
var moving: bool = false
func _ready() -> void:
target_position = global_position
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
var mouse_event: InputEventMouseButton = event
if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed:
target_position = get_global_mouse_position()
moving = true
func _physics_process(_delta: float) -> void:
if not moving:
velocity = Vector2.ZERO
return
var distance: float = global_position.distance_to(target_position)
if distance <= stop_distance:
moving = false
velocity = Vector2.ZERO
return
var direction: Vector2 = global_position.direction_to(target_position)
velocity = direction * speed
move_and_slide()
O stop_distance existe porque o personagem nunca cai exatamente no pixel clicado. Sem essa margem, ele fica tremendo em volta do ponto para sempre.
Esse movimento em linha reta funciona em cenários abertos. Quando o seu ambiente tiver obstáculos no meio do caminho, mesas, sofás, paredes internas, a evolução natural é o NavigationAgent2D: você desenha a área caminhável com um NavigationRegion2D e o agente calcula o caminho contornando tudo. Comece sem ele. Adicione quando o cenário pedir.
Hotspots interativos: o cenário que responde
Um hotspot é uma Area2D com um CollisionShape2D cobrindo o objeto interativo. A Area2D recebe cliques pelo sinal input_event, desde que input_pickable esteja ativo, e avisa o resto do jogo com um sinal próprio. O cursor muda quando o mouse passa por cima, usando mouse_entered e mouse_exited.
extends Area2D
class_name Hotspot
signal interacted(hotspot_name: String)
@export var hotspot_name: String = "porta"
func _ready() -> void:
input_pickable = true
input_event.connect(_on_input_event)
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
if event is InputEventMouseButton:
var mouse_event: InputEventMouseButton = event
if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed:
interacted.emit(hotspot_name)
func _on_mouse_entered() -> void:
Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)
func _on_mouse_exited() -> void:
Input.set_default_cursor_shape(Input.CURSOR_ARROW)
Com o @export, você configura o nome de cada hotspot direto no inspetor: a mesma cena serve para a porta, a gaveta e o quadro na parede. A cena do ambiente conecta os sinais de todos os hotspots (coloque todos em um grupo hotspots) em um único handler:
extends Node2D
func _ready() -> void:
for hotspot: Hotspot in get_tree().get_nodes_in_group("hotspots"):
hotspot.interacted.connect(_on_hotspot_interacted)
func _on_hotspot_interacted(hotspot_name: String) -> void:
print("Interagiu com: ", hotspot_name)
Um detalhe de acabamento: quando o jogador clica em um hotspot, o clique também dispara o movimento. Isso é o comportamento certo do gênero. Clicar na porta significa "ande até a porta e abra". No jogo completo, guarde a interação pendente e execute quando o personagem chegar perto do hotspot. Para o protótipo, interagir de qualquer distância já valida a mecânica.
Inventário: pegar, selecionar e combinar
O inventário do gênero tem três operações: adicionar item, selecionar item e usar o item selecionado em um hotspot. O clássico "usar chave na porta". Um Array[String] tipado guarda os itens, e uma variável guarda o que está selecionado.
extends CanvasLayer
class_name Inventario
signal item_selecionado(item: String)
var items: Array[String] = []
var item_ativo: String = ""
func adicionar(item: String) -> void:
if item in items:
return
items.append(item)
func remover(item: String) -> void:
items.erase(item)
if item_ativo == item:
item_ativo = ""
func selecionar(item: String) -> void:
if item in items:
item_ativo = item
item_selecionado.emit(item)
func tem(item: String) -> bool:
return item in items
A UI é um HBoxContainer com um botão por item, cada botão chamando selecionar no clique. O que importa é a lógica de combinação, que vive no handler de interação da cena. Ela pergunta: tem item ativo? Ele combina com esse hotspot?
@onready var inventario: Inventario = $Inventario
func _on_hotspot_interacted(hotspot_name: String) -> void:
if hotspot_name == "gaveta":
inventario.adicionar("chave_enferrujada")
return
if hotspot_name == "porta_trancada":
if inventario.item_ativo == "chave_enferrujada":
inventario.remover("chave_enferrujada")
GameState.set_flag("porta_aberta", true)
else:
print("Está trancada. Deve ter uma chave em algum lugar.")
Repare no else: quando a combinação falha, o jogo responde com um comentário. Isso não é enfeite. Feedback de falha é metade da diversão do gênero e, bem escrito, vira dica de puzzle.
Estado do mundo: o autoload GameState
O jogador abriu a porta, saiu da sala e voltou. A porta precisa continuar aberta. Esse é o papel do estado do mundo: um singleton que sobrevive à troca de cenas e registra tudo que o jogador já fez. Na Godot, isso é um autoload. Crie o script e registre em Projeto, Configurações do Projeto, aba Globais, com o nome GameState.
extends Node
var flags: Dictionary = {}
func set_flag(flag_name: String, value: bool) -> void:
flags[flag_name] = value
func get_flag(flag_name: String) -> bool:
return flags.get(flag_name, false) == true
O == true no retorno garante um bool de verdade mesmo com o Dictionary devolvendo Variant. Simples e à prova de aviso do analisador.
É esse dicionário de flags que permite puzzle multi-etapas. Exemplo: o NPC só entrega a chave depois que você mostrou o documento, e o documento só aparece depois que você abriu o cofre. Cada etapa checa e grava flags:
func _falar_com_npc() -> void:
if GameState.get_flag("documento_mostrado"):
inventario.adicionar("chave_enferrujada")
elif inventario.tem("documento"):
GameState.set_flag("documento_mostrado", true)
else:
print("Ele não quer conversa. Parece esperar algo de você.")
Quando a cena do ambiente carrega, o _ready dela lê as flags e monta o cenário de acordo: porta aberta ou fechada, gaveta vazia ou não. O estado mora em um lugar só, e as cenas apenas refletem esse estado.
Diálogo: onde a história acontece
Conversar com NPCs é o quinto sistema, e é onde o gênero brilha ou afunda. A estrutura mínima é uma caixa de fala com o retrato de quem fala e opções de resposta que podem checar e gravar flags no GameState, exatamente como os puzzles. Montar isso do zero é um projeto em si, e eu já destrinchei as decisões de estrutura em um guia sobre como construir um sistema de diálogo para jogos. Para o acabamento clássico do texto surgindo letra por letra, o caminho está no tutorial de efeito typewriter em diálogos na Godot.
Se preferir não construir essa parte na mão, a comunidade Godot mantém o Dialogic, um addon maduro de diálogos com editor visual. Para o gênero inteiro existe o Escoria, um framework de adventure game para Godot inspirado no fluxo clássico da LucasArts. Vale conhecer os dois, mas para um primeiro projeto eu recomendo montar os sistemas na mão: são pequenos, como você viu, e você sai entendendo cada engrenagem.
Design de puzzle: lógica acima de pixel hunting
Com os sistemas de pé, o jogo vive ou morre no design dos puzzles. Três princípios salvam um point and click iniciante:
Lógica acima de pixel hunting. O desafio deve ser perceber que a chave enferrujada abre o portão antigo, não achar a chave escondida em quatro pixels no canto da tela. Se o jogador travou porque não viu algo, o puzzle falhou. Se travou porque não conectou duas ideias, o puzzle está funcionando.
Sinalize o interativo. Cursor que muda ao passar sobre hotspots, como no nosso código, é o mínimo. Muitos jogos modernos adicionam uma tecla que destaca todos os hotspots da tela. Isso não é facilitar demais: é remover a fricção burra para sobrar a fricção boa, que é pensar.
Interação contextual antes de verbos. Os clássicos usavam painéis de verbos: olhar, usar, falar, empurrar, abrir. Isso dá expressividade, mas multiplica o trabalho, porque cada hotspot precisa responder a cada verbo. A alternativa moderna é a interação única contextual: clicar faz a ação óbvia daquele hotspot. Comece com a contextual. Se quiser um meio termo, dois botões do mouse resolvem: esquerdo interage, direito examina.
Puzzle bom no gênero segue as mesmas regras de qualquer puzzle: objetivo claro, informação disponível e o clique de "aha" quando as peças se encaixam. Escrevi em detalhe sobre isso no guia de design de puzzle para jogos, que vale ler antes de desenhar as suas correntes de itens e flags.
Escopo: 1 cômodo, 3 puzzles, 1 NPC
O maior risco de um point and click não é técnico, é de conteúdo. Cada ambiente novo pede arte, hotspots, texto de exame para cada objeto e puzzles conectados. É por isso que o primeiro projeto perfeito do gênero cabe em uma frase: um cômodo, três puzzles encadeados e um NPC.
Esse recorte obriga você a implementar tudo que este guia mostrou, movimento, hotspot, inventário, flags e diálogo, sem afogar o projeto em conteúdo. Exemplo concreto: você acorda trancado em um escritório. Puzzle 1: achar a chave da gaveta. Puzzle 2: usar o documento da gaveta para convencer o NPC no quadro falante. Puzzle 3: combinar o que ele te dá com a porta. Fim. Um jogo completo, com começo, meio e fim, em algumas semanas.
Se mesmo isso parecer grande, existe um gênero irmão que corta o movimento e os hotspots e mantém só a narrativa: o guia de como fazer uma visual novel no Godot mostra esse caminho, e é um degrau natural antes ou depois do point and click.
O gênero que deu origem a Monkey Island continua sendo uma das melhores escolas de game dev que existem: sistemas pequenos, design puro e escrita no centro. Abra a Godot, monte o cômodo e faça a primeira porta ranger.
Perguntas frequentes
Preciso de NavigationAgent2D para fazer o movimento por clique?
Não para começar. Em um cenário aberto, mover o personagem em linha reta até o ponto clicado com CharacterBody2D e move_and_slide resolve. O NavigationAgent2D entra quando o cenário tem obstáculos que o personagem precisa contornar, como mesas e paredes no meio do caminho.
O que é um hotspot em um jogo point and click?
É qualquer área clicável do cenário: uma porta, uma gaveta, um NPC. No Godot, o jeito mais direto é uma Area2D com input_pickable ativo, que recebe cliques pelo sinal input_event e avisa o resto do jogo com um sinal próprio.
Como funciona o inventário em um point and click?
O jogador coleta itens ao interagir com hotspots e depois combina esses itens com outros hotspots, como usar uma chave na porta. Um Array tipado de Strings mais uma variável com o item selecionado já cobre a mecânica clássica do gênero.
Preciso do sistema de verbos clássico, tipo olhar, usar e falar?
Não. Os clássicos da LucasArts usavam painéis de verbos, mas jogos modernos do gênero preferem interação contextual: um clique faz a ação óbvia daquele hotspot. Para um primeiro projeto, a interação única é mais simples de programar e de jogar.
Qual o escopo ideal para o primeiro jogo point and click?
Um cômodo, três puzzles encadeados e um NPC. Esse recorte obriga você a implementar todos os sistemas do gênero, movimento, hotspots, inventário, diálogo e estado do mundo, sem afogar o projeto em conteúdo que você ainda não sabe estimar.


