Como Fazer um Jogo de Namoro (Dating Sim) na Godot

Como fazer um jogo de namoro (dating sim) na Godot 4: cenas de diálogo com escolhas, afeto por personagem num Dictionary tipado e finais que ramificam.
Se você já jogou algo como Amor Doce e pensou em criar a sua própria história de romance interativo, este guia mostra como fazer um jogo de namoro (dating sim) na Godot 4 do zero, com GDScript tipado de verdade. Um dating sim não precisa de física, de inimigos nem de combate: ele vive de conversa, de escolha e de consequência. O jogador lê falas, decide o que responder, e cada resposta aproxima ou afasta de um pretendente. No fim, é a soma dessas escolhas que define com quem a história termina.
A boa notícia é que todas as peças de um dating sim existem como nós nativos da Godot, e a lógica central cabe em poucas funções. Neste tutorial você vai montar a cena de diálogo que mostra o retrato do personagem, o texto da fala e os botões de escolha, vai guardar o afeto de cada pretendente num Dictionary tipado e vai escrever a função que decide o final comparando esses valores. No caminho, vou apontar quando Ren'Py faz mais sentido e quando a Godot vale o esforço.
O que define um dating sim (e o caminho otome)
Antes de escrever código, vale separar os ingredientes do gênero. Um dating sim, na raiz, tem quatro peças:
- Personagens pretendentes: cada um com retrato, personalidade e uma rota própria de história. Na vertente otome (como Amor Doce), a protagonista costuma ser uma mulher e os pretendentes são os rapazes ao redor dela.
- Cenas de diálogo com escolhas: o jogador lê uma fala e escolhe entre duas ou mais respostas. A escolha é o verbo principal do jogo.
- Sistema de afeto: cada pretendente tem uma pontuação de relacionamento. Escolhas certas somam, escolhas frias tiram.
- Finais que ramificam: ao fim da história, o jogo compara o afeto e entrega o final da rota de quem acumulou mais pontos (ou um final solo, se ninguém engatou).
Perceba que nada disso é gráfico pesado. O charme está no texto e no ritmo. Por isso muita gente começa um projeto desses no Ren'Py, que é um motor feito só para visual novel e já traz diálogo, escolhas e saves prontos. Ren'Py é um atalho honesto quando o jogo é texto puro. A Godot entra quando você quer mais: um minigame no meio do encontro, uma animação de cena, um inventário, ou simplesmente porque você já domina a engine e quer tudo num lugar só. É esse caminho na Godot que vamos trilhar.
A cena de diálogo: retrato, texto e escolhas
A tela clássica de dating sim tem três zonas: o retrato do pretendente (geralmente de um lado), a caixa com a fala dele embaixo e, quando é a sua vez de responder, os botões de escolha. Na Godot 4, monte isso como uma cena Control com esta estrutura:
# Dialogo (Control) <- raiz, com o script
# ├── Fundo (TextureRect) # cenário da cena
# ├── Retrato (TextureRect) # imagem do personagem falando
# ├── CaixaTexto (RichTextLabel) # a fala atual
# └── Escolhas (VBoxContainer) # onde os botões nascem
O TextureRect do retrato segura a arte do personagem. O RichTextLabel mostra a fala e aceita BBCode, útil para dar ênfase em uma palavra. E o VBoxContainer vai empilhar os botões de resposta, um embaixo do outro. Para a arte entrar bonita, vale montar os personagens como recortes reaproveitáveis, no padrão que explico em sprites para visual novel, que separa corpo, roupa e rosto.
O script da raiz expõe funções tipadas para atualizar cada zona. Comece pela que mostra uma fala:
extends Control
@onready var retrato: TextureRect = $Retrato
@onready var caixa_texto: RichTextLabel = $CaixaTexto
@onready var escolhas: VBoxContainer = $Escolhas
func mostrar_fala(imagem: Texture2D, texto: String) -> void:
retrato.texture = imagem
caixa_texto.text = texto
Repare na tipagem: imagem é Texture2D, texto é String, e o retorno é -> void porque a função só atualiza a tela, não devolve nada. Essa clareza faz o editor te ajudar com autocomplete e pega erro cedo. Trocar a textura do retrato por uma versão com outra cara (surpresa, irritada, feliz) é o que dá peso emocional à conversa; esse pulo de qualidade está detalhado em expressões faciais no retrato de diálogo.
Se você quer que o texto apareça letra por letra, como nos jogos clássicos, em vez de surgir de uma vez, dá para animar a propriedade visible_ratio do RichTextLabel num Tween. Para o dating sim funcionar, porém, o texto cheio de uma vez já basta.
Os botões de escolha que alimentam o afeto
Agora a parte que define o gênero: as escolhas. Cada escolha carrega consigo um personagem alvo e quanto afeto ela concede. Vamos representar uma opção como um Dictionary simples, e gerar os botões em tempo de execução dentro do VBoxContainer:
func mostrar_escolhas(opcoes: Array[Dictionary]) -> void:
for filho in escolhas.get_children():
filho.queue_free()
for opcao in opcoes:
var botao: Button = Button.new()
botao.text = opcao["texto"]
botao.pressed.connect(_ao_escolher.bind(opcao))
escolhas.add_child(botao)
O primeiro for limpa os botões da pergunta anterior com queue_free(), para não empilhar lixo. O segundo cria um Button novo para cada opção, define o texto e conecta o sinal pressed à função _ao_escolher, usando .bind(opcao) para que o clique já leve consigo o Dictionary daquela resposta. Esse uso de sinais é o mesmo alicerce de qualquer conversa com ramificação; se você ainda não montou a base de falas e ramos, o guia de sistema de diálogo para jogos cobre o esqueleto que o dating sim reaproveita.
Quando o jogador clica, cai aqui:
func _ao_escolher(opcao: Dictionary) -> void:
var personagem: String = opcao["personagem"]
var ganho: int = opcao["afeto"]
Relacionamento.adicionar_afeto(personagem, ganho)
avancar_dialogo()
A função lê de qual personagem é aquela resposta e quanto afeto ela vale, repassa esses dados para o gerenciador de relacionamento e chama o próximo trecho da história. Uma opção, na prática, fica assim quando você monta uma pergunta:
var pergunta: Array[Dictionary] = [
{"texto": "Te acompanho até em casa.", "personagem": "theo", "afeto": 2},
{"texto": "Prefiro ir sozinha, obrigada.", "personagem": "theo", "afeto": -1},
]
Duas respostas, mesmo pretendente, impactos opostos. É essa oposição que faz o jogador pensar.
O sistema de afeto num Dictionary tipado
O afeto é o estado que o jogo inteiro consulta. Guardar isso num Dictionary tipado, com o nome de cada pretendente como chave, deixa o acesso direto e legível. Crie um script autoload chamado Relacionamento (em Project Settings, Autoload) para que qualquer cena enxergue o afeto:
extends Node
var afeto: Dictionary = {"marina": 0, "theo": 0}
func adicionar_afeto(personagem: String, valor: int) -> void:
if afeto.has(personagem):
afeto[personagem] += valor
func get_afeto(personagem: String) -> int:
return afeto.get(personagem, 0)
A função adicionar_afeto recebe o nome e o valor, confere com has() se aquele personagem existe no Dictionary (evita criar chave errada por um typo) e soma. A get_afeto devolve a pontuação atual, usando get(personagem, 0) para retornar zero caso a chave não exista, em vez de quebrar. Como é um autoload, a cena de diálogo chama Relacionamento.adicionar_afeto("theo", 2) de qualquer lugar.
Montar sozinho todo esse encaixe (cena de diálogo, sinais de botão, autoload de estado, finais) é exatamente o ponto em que muita gente empaca: cada peça funciona isolada, mas juntar tudo num jogo que começa, flui e termina é outra história. Se você quer trilhar esse caminho com uma sequência pensada, do primeiro print até um projeto fechado, o curso de Godot da CursoGame.Dev organiza a jornada para você não ficar colando trechos soltos sem entender como o jogo respira.
Decidindo o final pela afinidade
Chegou o momento que paga todo o resto: o final. A regra padrão de dating sim é simples: ganha a rota do personagem com mais afeto, desde que esse afeto passe de um piso mínimo. Se ninguém passou do piso, o final é solo. Veja a função tipada que resolve isso:
func decidir_final(minimo: int = 10) -> String:
var melhor: String = ""
var maior: int = -1
for personagem in afeto:
var pontos: int = afeto[personagem]
if pontos > maior:
maior = pontos
melhor = personagem
if maior < minimo:
return "solo"
return melhor
A função percorre o Dictionary guardando quem tem mais pontos em melhor e quanto em maior. No fim, se nem o melhor pretendente chegou ao minimo (aqui, 10 pontos), ela devolve "solo", o final de quem não se envolveu com ninguém. Caso contrário, devolve o nome da rota vencedora. O parâmetro minimo tem valor padrão, então você chama decidir_final() para o piso normal ou decidir_final(20) para uma rota mais difícil de conquistar.
Com o nome do final em mãos, a cena final decide o que mostrar:
func mostrar_desfecho() -> void:
var rota: String = Relacionamento.decidir_final()
match rota:
"marina":
mostrar_fala(retrato_marina, "A Marina segura sua mao no portao.")
"theo":
mostrar_fala(retrato_theo, "O Theo sorri e diz que esperou por isso.")
"solo":
mostrar_fala(null, "Voce saiu dessa historia do seu jeito, sozinha.")
O match sobre a String do final é limpo e se estende fácil: cada novo pretendente vira mais um ramo. Para finais em camadas (ruim, bom e verdadeiro do mesmo personagem), basta checar faixas de pontos dentro do ramo, por exemplo um if Relacionamento.get_afeto("theo") >= 25 para liberar o final verdadeiro.
Onde ir a partir daqui
Você tem agora a espinha de um dating sim na Godot 4: uma cena de diálogo que mostra retrato, texto e escolhas, um Dictionary tipado que acumula o afeto de cada pretendente e uma função que lê esses pontos para decidir o final. É pouco código, e todo ele é GDScript tipado de verdade, com nós que existem na engine. O próximo passo natural é dar volume ao conteúdo: mais pretendentes no Dictionary, perguntas com três ou quatro opções, expressões diferentes no retrato a cada emoção e um save que guarde o afeto entre sessões com FileAccess.
Se o seu jogo cresce para fora do texto, com um minigame de encontro ou uma fase para explorar, a Godot segura esse crescimento sem te forçar a trocar de ferramenta, que é justamente a vantagem de não começar no Ren'Py. Comece pequeno: dois pretendentes, uma tarde de história, três finais. Quando esse laço estiver redondo e divertido, escalar é só repetir o padrão que você já domina.
Perguntas frequentes
O que é um dating sim?
Dating sim (simulador de namoro) é um gênero em que o jogador conversa com personagens, faz escolhas em diálogos e tenta aumentar a afinidade com um ou mais pretendentes. A variação otome, como o brasileiro Amor Doce, foca em uma protagonista mulher cercada de pretendentes. O coração do gênero é o sistema de afeto: cada escolha soma ou tira pontos de relacionamento, e esses pontos decidem qual final você vê.
Preciso usar Ren Py ou dá para fazer na Godot?
Ren Py é o atalho mais conhecido para visual novel e dating sim, porque já traz diálogo, escolhas e saves prontos. Mas dá para fazer tudo na Godot 4, e vale quando você quer minigames, animação ou mecânicas que fogem do texto puro. Na Godot você monta a cena de diálogo com nós nativos e controla o afeto em GDScript, com total liberdade sobre o que acontece a cada escolha.
Como guardar os pontos de afeto de cada personagem?
Use um Dictionary tipado com o nome de cada personagem como chave e um inteiro como valor, por exemplo var afeto: Dictionary = {"marina": 0, "theo": 0}. A cada escolha relevante você chama uma função que soma pontos na chave certa. No fim, basta comparar os valores do Dictionary para saber de quem o jogador ficou mais próximo.
Como decidir o final pela afinidade?
Percorra o Dictionary de afeto procurando o personagem com mais pontos. Se o maior valor não passar de um limite mínimo, você entrega o final solo, em que ninguém engatou. Se passar, o final é da rota daquele personagem. Esse mesmo padrão permite finais ruins, bons e verdadeiros só mudando as faixas de pontos.
Como mostrar o retrato e o texto do personagem?
Monte uma cena Control com um TextureRect para o retrato, um RichTextLabel ou Label para a fala e um VBoxContainer para os botões de escolha. Uma função tipada recebe a textura e o texto e atualiza esses nós. Trocar a textura do retrato por uma expressão diferente é o que dá vida emocional ao diálogo.
Como fazer as escolhas aparecerem como botões?
Crie os botões em tempo de execução dentro de um VBoxContainer: para cada opção, instancie um Button, defina o texto e conecte o sinal pressed a uma função, passando a opção com bind. Quando o jogador clica, a função lê qual personagem e quanto afeto aquela fala concede e atualiza o Dictionary.


