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!
NomeO que é
PASSOespaçamento dos candidatos a item, em unidades. 4 a 8.
+ 1000offset para o mapa de densidade não ser o mesmo do terreno
LIMIARquanto maior, menos itens
tipo(d)o próprio valor de densidade escolhe o tipo: faixa alta é item raro, faixa média é comum
chavetexto ú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.
icontador de candidatos dentro do chunk. Tem que avançar mesmo quando o item já foi coletado, senão as chaves dos itens seguintes mudam.
coletadosum HashSet<string> global. É o que vai para o save.
Teste do painel. Ligue "ignorar a chave (ERRADO)": o jogo passa a instanciar sem consultar o conjunto. Colete um item, afaste-se até o chunk sumir, volte: ele está lá de novo.

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
NomeO que é
id_do_itemidentificador do tipo ("carregador_rifle"), não do objeto na cena. Vinte carregadores iguais têm o mesmo id.
MAX_PILHAquantos cabem num slot. Munição empilha até 99; um rifle não empilha.
capacidadenúmero de slots. É o que faz "inventário cheio" existir.
Mudouevento (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://.