O Segredo dos Drops Aleatórios no Godot (Loot Tables com Pesos)
Sabe aquela sensação de suar a camisa para derrotar um chefe difícil e ele dropar... uma poção de vida básica? Pois é. Balancear os drops do seu jogo é uma arte, mas antes de chegar na arte, a gente precisa resolver a matemática.
Quando comecei a programar sistemas de loot (recompensas), eu cometia o erro clássico: tentava usar porcentagens fixas. Fazia um monte de if chance <= 10: dropar_espada(). O problema é que, no dia que você decide adicionar um item novo, a soma passa de 100% e você tem que refazer a matemática de todo o código. Um pesadelo.
A solução definitiva para isso é usar um sistema de Pesos (Weights). E é aqui que muita gente se enrola com as funções matemáticas do Godot. Vamos desmistificar isso de um jeito prático.
A Lógica: Esqueça a porcentagem, pense em uma fita métrica
Em vez de dizer que uma Espada tem 10% de chance, vamos dizer que ela tem "Peso 10". Se a Poção tem "Peso 90", o peso total da nossa lista é 100.
Para sortear o item, a gente não vai calcular frações. Nós pegamos a função randi() (que gera um número inteiro aleatório gigante) e usamos o operador % (módulo) junto com o nosso Peso Total.
O que o % faz na prática? Ele garante que o número gerado pelo Godot fique "preso" entre 0 e o nosso Peso Total. Se o peso total é 100, randi() % 100 vai sempre cuspir um número de 0 a 99. Se amanhã você adicionar um item de peso 50, o código automaticamente passa a rolar entre 0 e 149. Zero dor de cabeça.
O Código na Prática (GDScript)
extends Node
# Tabela de recompensas. Formato: { "Nome do Item" : Peso }
var loot_table = {
"Poção de Vida": 60, # Super comum
"Espada de Ferro": 30,
"Escudo Mágico": 9,
"Amuleto Lendário": 1 # Só para quem tem sorte
}
func _ready():
# Importante: Leia a nota abaixo sobre Godot 3 vs Godot 4!
randomize()
# Vamos simular a abertura de 5 baús
for i in range(5):
var meu_loot = calcular_drop(loot_table)
print("Baú ", i + 1, ": ", meu_loot)
func calcular_drop(tabela: Dictionary) -> String:
var peso_total = 0
# 1. Somamos o peso de tudo que existe na tabela
for item in tabela:
peso_total += tabela[item]
# 2. Rolamos o nosso dado virtual limitado ao peso total
var valor_rolado = randi() % peso_total
var peso_acumulado = 0
# 3. Onde o número caiu?
for item in tabela:
peso_acumulado += tabela[item]
if valor_rolado < peso_acumulado:
return item
return "Nada" # Prevenção de erros caso a tabela venha vazia
Entendendo o Loop "Onde o número caiu?"
Essa é a parte que costuma dar um nó na cabeça. Imagine que você esticou uma fita métrica no chão. Do cm 0 ao 60 é o espaço da Poção. Do 60 ao 90 (60+30) é a Espada. Do 90 ao 99 é o Escudo.
O valor_rolado é como jogar uma pedrinha nessa fita métrica. O laço for simplesmente vai andando pela fita (somando os pesos no peso_acumulado) e verifica: "A pedrinha caiu aqui antes desse limite?". Se sim, ele te entrega o item e encerra a função.
O Pulo do Gato que a Documentação não foca
Se você testar um sistema de aleatoriedade e reparar que está sempre caindo os mesmos itens na mesma ordem toda vez que você dá "Play" no seu jogo, o culpado é a semente (seed) do RNG.
Se você usa o Godot 3: Você é obrigado a chamar a função randomize() no início do jogo (como no _ready acima). Ela pega o relógio do seu computador e gera uma semente única para aquela sessão.
Se você usa o Godot 4: O Godot 4 já chama o randomize() automaticamente nos bastidores quando o jogo inicia. Você pode apagar aquela linha do código.
Dica de Ouro de Debug: Quando você estiver testando as chances de drop, comente o randomize() e defina uma semente fixa, tipo seed(12345). Assim, os resultados serão sempre os mesmos e você tem certeza de que o seu código está funcionando, em vez de achar que só está com "azar" nos testes!
Como você costuma organizar os dados dos seus itens? Usa Dicionários soltos ou já partiu para os Custom Resources? Comenta aí embaixo pra gente trocar uma ideia!

Comentários
Postar um comentário
Tem alguma dúvida sobre este código ou sugestão de melhoria? Comente abaixo! Respondo a todas as perguntas técnicas.