11. Itens que não voltam, e o inventário
O chunk é destruído e recriado; o item coletado tem que continuar coletado.
Os itens nascem pelo ruído como densidade (aula 1, seção 7). O problema novo: o jogador coleta um carregador, anda 3 chunks, volta, e o chunk é gerado de novo do zero, com o carregador no lugar. A solução é dar a cada item uma chave que não depende de quando o chunk foi gerado, e guardar o conjunto de chaves coletadas.
Setas ou WASD movem. Clique num item para coletar (precisa estar perto). Ande até o chunk sumir e volte: o item coletado não reaparece.
Inventário
Save (JSON)
A chave
// ao gerar o chunk (cx, cz)
i = 0
para cada ponto (x, z) do chunk, de PASSO em PASSO:
gx = cx * TAMANHO + x; gz = cz * TAMANHO + z
d = fbm(gx * 0.05 + 1000, gz * 0.05 + 1000) // mapa de densidade, offset diferente do terreno
se d > LIMIAR e bioma(gx, gz) é caminhável:
chave = semente + ":" + cx + ":" + cz + ":" + i // ex.: "7:2:-1:14"
se chave não está em coletados:
instanciar_item(tipo(d), gx, altura(gx, gz), gz, chave)
i += 1 // conta mesmo se não instanciou!
| Nome | O que é |
|---|---|
PASSO | espaçamento dos candidatos a item, em unidades. 4 a 8. |
+ 1000 | offset para o mapa de densidade não ser o mesmo do terreno |
LIMIAR | quanto maior, menos itens |
tipo(d) | o próprio valor de densidade escolhe o tipo: faixa alta é item raro, faixa média é comum |
chave | texto único e determinístico: com a mesma semente, o mesmo chunk gera as mesmas chaves na mesma ordem. Não use o nome do objeto nem o índice de instanciação. |
i | contador de candidatos dentro do chunk. Tem que avançar mesmo quando o item já foi coletado, senão as chaves dos itens seguintes mudam. |
coletados | um HashSet<string> global. É o que vai para o save. |
O inventário
Inventário é dado, não interface. Um objeto que guarda slots e responde a duas perguntas: "cabe?" e "quantos tenho?". A tela só desenha o que ele diz, e só quando ele avisa que mudou.
classe Inventario:
slots: lista de (id_do_item, quantidade) // tamanho fixo = capacidade
evento Mudou
adicionar(id, qtd):
para cada slot com o mesmo id e quantidade < MAX_PILHA: completa a pilha
se sobrou: procura slot vazio e cria pilha nova
se ainda sobrou: devolve o que não coube (o item fica no chão)
dispara Mudou
remover(id, qtd): tira das pilhas, apaga slot que zerou, dispara Mudou
contar(id): soma as pilhas com esse id
| Nome | O que é |
|---|---|
id_do_item | identificador do tipo ("carregador_rifle"), não do objeto na cena. Vinte carregadores iguais têm o mesmo id. |
MAX_PILHA | quantos cabem num slot. Munição empilha até 99; um rifle não empilha. |
capacidade | número de slots. É o que faz "inventário cheio" existir. |
Mudou | evento (C# event/UnityEvent, Godot signal). A UI se inscreve uma vez e redesenha quando dispara. Nada de Find no Update. |
Se o seu projeto é a continuação do airsoft, o carregador equipado da Atividade 2 é um item que sai do inventário ao equipar e volta ao trocar. O HUD que você já tem passa a escutar o evento do inventário além do da arma.
Salvar e carregar
O save é pequeno porque o mundo não está nele: só a semente, a lista de chaves coletadas, o inventário e a posição verdadeira do jogador (posição na cena + origem do mundo, se houver floating origin). Ao carregar, o jogo lê a semente, coloca o jogador na posição, e os chunks se geram sozinhos, já pulando as chaves coletadas.
{ "semente": 7,
"coletados": ["7:0:0:3", "7:1:0:11"],
"inventario": [ {"id": "carregador", "qtd": 2}, {"id": "municao", "qtd": 45} ],
"jogador": {"x": 812.5, "z": -40.2} }
No Unity, JsonUtility não serializa HashSet nem Dictionary: converta para lista antes. No Godot, JSON.stringify aceita Dictionary e Array direto. Grave em Application.persistentDataPath ou user://.