Continuamos a conversa sobre o jogo de tiro em 3D no fim de semana. Se alguma coisa, então eu lembro que esta é a segunda metade:
Como eu disse, faço o possível para apoiar o desejo dos alunos de fazer algo com suas próprias mãos. Em particular, quando dou um curso de palestras sobre a introdução à programação, então, como exercícios práticos, deixo a eles quase total liberdade. Existem apenas duas limitações: a linguagem de programação (C ++) e o tema do projeto, este deve ser um videogame. Aqui está um exemplo de um dos centenas de jogos que meus alunos do primeiro ano fizeram:
Infelizmente, a maioria dos estudantes escolhe jogos simples, como plataformas 2D. Estou escrevendo este artigo para mostrar que criar a ilusão de um mundo tridimensional não é mais difícil do que clonar mario broz.
Lembro que paramos em um estágio que permite texturizar as paredes:

Etapa 13: desenhe monstros no mapa
O que é um monstro no nosso jogo? Estas são suas coordenadas e número de textura:
struct Sprite { float x, y; size_t tex_id; }; [..] std::vector<Sprite> sprites{ {1.834, 8.765, 0}, {5.323, 5.365, 1}, {4.123, 10.265, 1} };
Tendo definido vários monstros, para começar, simplesmente os desenhamos no mapa:

As mudanças que
você pode ver aqui .

Etapa 14: quadrados pretos em vez de monstros em 3D
Agora vamos desenhar sprites na janela 3D. Para fazer isso, precisamos determinar duas coisas: a posição do sprite na tela e seu tamanho. Aqui está a função que desenha um quadrado preto no lugar de cada sprite:
void draw_sprite(Sprite &sprite, FrameBuffer &fb, Player &player, Texture &tex_sprites) {
Vamos descobrir como isso funciona. Aqui está o diagrama:

Na primeira linha, consideramos o ângulo absoluto sprite_dir (o ângulo entre a direção do jogador para o sprite e a abcissa). O ângulo relativo entre o sprite e a direção do olhar é obviamente obtido subtraindo simplesmente dois ângulos absolutos: sprite_dir - player.a. A distância do jogador ao sprite é trivial para calcular, e o tamanho do sprite é uma simples divisão do tamanho da tela pela distância. Bem, por precaução, cortei dois mil do topo para não obter quadrados gigantes (a propósito, esse código pode ser facilmente dividido por zero). h_offset e v_offset fornecem as coordenadas do canto superior esquerdo do sprite na tela; então um simples loop duplo preenche nosso quadrado com preto. Verifique com uma caneta e um pedaço de papel se h_offset e v_offset estão corretamente calculados, no meu commit há um erro (não crítico), acredite no código do artigo :) Bem, o código mais recente no repositório também foi corrigido.

As mudanças que
você pode ver aqui .

Etapa 15: Mapa de Profundidade
Nossos quadrados são milagrosamente bons, mas há apenas um problema: o monstro distante espreita na esquina, e o quadrado é desenhado inteiramente. Como ser Muito simples Nós desenhamos sprites
depois que as paredes foram desenhadas. Portanto, para cada coluna da tela, sabemos a distância da parede mais próxima. Salvamos essas distâncias em uma matriz de 512 valores e passamos a matriz para a função de renderização de sprite. Os sprites também são desenhados coluna por coluna; portanto, para cada coluna do sprite, compararemos a distância a ele com o valor da nossa matriz de profundidade.

As mudanças que
você pode ver aqui .

Etapa 16: problema com sprites
Eles se tornaram grandes monstros, não é? Mas, neste estágio, não adicionarei nenhuma funcionalidade, pelo contrário, quebrarei tudo adicionando outro monstro:

As mudanças que
você pode ver aqui .

Etapa 17: classificação de sprites
Qual foi o problema? O problema é que eu posso ter uma ordem arbitrária de desenhar sprites e, para cada um deles, comparo sua distância com as paredes, mas não com outros sprites, de modo que a criatura distante se arrasta sobre o mais próximo. É possível adaptar uma solução com um mapa de profundidade para desenhar sprites?
Texto ocultoA resposta correta é "você pode". Mas como Escreva nos comentários.
Eu vou para o outro lado, resolvendo o problema estupidamente na testa. Vou desenhar todos os sprites do mais distante ao mais distante. Ou seja, vou ordenar os sprites em ordem decrescente de distância e desenhá-los nessa ordem.

As mudanças que
você pode ver aqui .

Etapa 18: Horário do SDL
Chegou a hora do SDL. Existem várias bibliotecas de janelas multiplataforma e eu não as entendo. Pessoalmente, eu gosto do
imgui , mas por algum motivo meus alunos preferem o SDL, então eu faço o link com ele. A tarefa para esta etapa é muito simples: crie uma janela e exiba a imagem da etapa anterior:

As mudanças que
você pode ver aqui . Não dou mais um link para o gitpod, porque SDL no navegador ainda não aprendeu a iniciar :(
Atualização: APRENDIDO! Você pode executar o código em um clique no navegador!
Etapa 19: Processamento e limpeza de eventos
Adicionar uma reação ao pressionamento de teclas nem sequer é engraçado, não vou descrever. Ao adicionar o SDL, removi a dependência em stb_image.h. É bonito, mas leva muito tempo para compilar.
Para quem não entende, as fontes do décimo nono estágio
estão aqui . Bem, aqui está uma performance típica:
Conclusão
No momento, meu código contém apenas 486 linhas e, ao mesmo tempo, não os salvei:
haqreu@daffodil:~/tinyraycaster$ cat *.cpp *.h | wc -l 486
Não lambi meu código, intencionalmente jogando roupa suja. Sim, eu escrevo assim (e não apenas eu). Um sábado de manhã, sentei-me e escrevi isso :)
Eu não fiz o jogo terminado, minha tarefa é apenas dar um impulso inicial para a fuga da sua imaginação. Escreva seu próprio código, provavelmente será melhor que o meu. Compartilhe seu código, compartilhe suas idéias, envie solicitações pull.