Voltar para o Blog
Quest Log

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

Tela de dating sim na Godot 4 com retrato de personagem, caixa de texto e botões de escolha

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.

Ferramenta:Godot

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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

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.