Recriar um jogo DOS antigo em C ++ 17

Em 2016, comecei a trabalhar em um projeto de hobby para engenharia reversa do jogo Duke Nukem II e recriando seu motor a partir do zero. O projeto é chamado Rigel Engine e está disponível em código aberto ( sua página no GitHub ). Hoje, mais de dois anos e meio depois, no meu mecanismo, você já pode passar por todo o episódio shareware do jogo original com uma jogabilidade quase idêntica à original. Aqui está um vídeo com a passagem do primeiro nível:


O que ele pode fazer? O Rigel Engine funciona como um substituto completo para o binário DOS original ( NUKEM2.EXE ). Você pode copiá-lo para o diretório do jogo e ele considera todos os dados dele, ou especificar o caminho para os dados do jogo como um argumento para a linha de comando. O mecanismo é construído e executado no Windows, Mac OS X e Linux. Ele é baseado em SDL e OpenGL 3 / OpenGL ES 2 e é escrito em C ++ 17.

Ele implementa a lógica do jogo de todos os inimigos e a mecânica do jogo no episódio Shareware, além da maior parte do sistema de menus. Além disso, você pode importar jogos salvos e uma tabela de pontuação alta do jogo original para ele.

Além disso, o mecanismo tem vantagens sobre o original:

  • Não é necessário emulador ou hardware antigo, nenhuma configuração é necessária
  • Sem telas de carregamento - selecione "novo jogo" no menu, pressione Enter e inicie o jogo imediatamente
  • Vários efeitos sonoros podem ser reproduzidos ao mesmo tempo, o que era impossível no original.
  • Não há restrições quanto ao número de efeitos simultâneos de partículas, explosões e assim por diante.
  • Salve arquivos e listas de recordes para cada jogador
  • Menus muito mais responsivos

Até agora, não considero o Rigel Engine completamente "pronto". Mas este é um ótimo estágio de desenvolvimento e uma boa oportunidade para escrever sobre o mecanismo novamente (postagens antigas publicadas aqui e aqui ). Vamos começar analisando o estado atual do código e descobrir como cheguei a ele.

Quanto código há no mecanismo?


No momento da redação deste artigo, o RigelEngine consistia em 270 arquivos de origem contendo mais de 25 mil linhas de código (sem comentários / linhas vazias). Desses, 10 arquivos e 2,5 mil linhas são testes de unidade. Um detalhamento detalhado de linhas e comentários vazios está disponível aqui .

O que há em todo esse código? Um pouco de infraestrutura comum e funções de suporte, coisas básicas como renderização e um monte de pequenos pedaços de lógica. Além de tudo isso, as maiores partes são:

  • analisadores / downloaders para 14 formatos de arquivo diferentes usados ​​no jogo original - 2 mil linhas de código (LOC)
  • lógica de comportamento / lógica de jogo para 24 inimigos / objetos hostis - 3.8k LOC
  • lógica de jogo para 14 elementos interativos e mecânica de jogo - 2k LOC
  • lógica de controle do player - 1.2k LOC
  • 154 entradas de configuração (o valor de saúde de cada inimigo, o número de pontos recebidos por itens coletados etc.) - 1k LOC
  • 31 especificações para efeitos de destruição (efeitos desencadeados pela destruição de um inimigo ou outro objeto destrutível) - 254 LOC
  • código de controle da câmera - 159 LOC
  • descrição do menu do jogo intérprete de idioma / cena - 643 LOC
  • O HUD e outro código da interface do usuário é 818 LOC
  • 5 telas / modos fora do menu, por exemplo, a animação inicial, tela de bônus, etc. - 789 LOC

Obviamente, todo esse código precisava ser escrito, e isso nos leva à próxima pergunta.

Quanto trabalho foi necessário?


Embora dois anos e meio se passaram desde o início do projeto, não trabalhei nele todo esse tempo. Por alguns meses eu não fiz nenhum projeto, em alguns outros passei apenas algumas horas nele. Mas houve momentos em que trabalhei no Rigel Engine bastante ativamente. Observando a programação de consolidação no Github, você pode ter uma idéia aproximada de como meu trabalho foi distribuído ao longo do tempo:


De acordo com o cronograma, vemos que 1081 confirmações foram feitas na ramificação mestre. No entanto, mesmo antes de criar o repositório, eu estava trabalhando em um fechado, no qual havia mais 247 confirmações, o que totaliza 1328 confirmações. Além disso, havia vários ramos de protótipo que usei para pesquisa e experimentação, mas nunca combinados com o principal; Além disso, antes de mesclar, às vezes eu compactava grandes histórias de confirmação em histórias mais curtas.

Devo também dizer que a escrita de código não foi a única parte do projeto - a engenharia reversa foi outra parte importante. Passei algumas horas estudando o código desmontado do arquivo executável original no Ida Pro (na versão gratuita), fazendo anotações, escrevendo pseudocódigo e planejando a implementação dos elementos da minha versão. Além disso, testei ativamente o jogo original, iniciando-o no DOSBox e no equipamento original (diferentes máquinas 386 e 486 compradas no eBay). Eu coletei níveis de teste para observação separada de inimigos específicos e para o estudo da mecânica do jogo, gravei videoclipes usando o DOSBox e examinei os quadros quadro a quadro para confirmar minhas conclusões ao estudar o código do montador. Depois que as mecânicas do inimigo ou do jogo eram realizadas, normalmente eu gravava um videoclipe da minha versão e o comparava quadro a quadro com o original para confirmar a precisão da minha implementação.

Aqui estão algumas fotos das minhas anotações:


Código de controle de câmera de engenharia reversa. Um retângulo grande indica a tela. As linhas tracejadas mostram as zonas que um jogador pode mover sem mover a câmera. Se você estiver interessado, o próprio código de controle da câmera pode ser encontrado aqui .


Notas gerais para ajudá-lo a entender o código de montagem. À esquerda, está o procedimento para atualizar o jogo original em um nível alto. À direita, há notas nos campos de bits, indicando o status de alguns objetos do jogo.


Transcrição do código do assembler em pseudocódigo. Normalmente, faço isso mecanicamente, transcrevo sem pensar no que o código está fazendo e depois uso a versão no pseudo-código para entender a lógica subjacente. E com base nisso, eu já proponho minha implementação. Veja o código finalizado aqui .


Pseudo-código de uma versão limpa da lógica inimiga. Os cabeçalhos indicam o estado da máquina de estado, o código abaixo explica o que deve acontecer nos respectivos estados. Foi criado com base no pseudocódigo bruto obtido pela transcrição do código do assembler. Código pronto pode ser encontrado aqui .

No final, o trabalho no projeto acabou sendo muito empolgante e aprendi muito com ele: sobre engenharia reversa, montador x86 de 16 bits, programação VGA de baixo nível, as limitações estritas que os desenvolvedores de jogos enfrentaram para jogos de PC no início dos anos 90; além disso, fiz muitas descobertas sobre os recursos internos do jogo original e sobre o quão estranho e bizarro alguns deles foram implementados - esse tópico em si merece uma série separada de posts.

O que vem a seguir


Além de adicionar as últimas funções restantes e finalizar o suporte para a versão registrada, tenho várias idéias para melhorar e expandir os recursos do Rigel Engine, sem mencionar a limpeza e refatoração do código - como sempre, a melhor maneira de criar uma arquitetura de software é aparente apenas após a criação desse software.

Quanto a melhorias futuras, aqui estão alguns dos pontos que pensei em implementar:

  • Movimento suave com interpolação. O jogo atualiza sua lógica cerca de 15 vezes por segundo, e no jogo original também é a taxa de quadros para renderização. Por outro lado, o Rigel Engine pode trabalhar facilmente com uma frequência de 60 FPS e superior. No momento, esses quadros adicionais não oferecem vantagens, mas acho que eles podem ser usados ​​para quadros intermediários para realizar rolagem e movimento mais suave dos objetos. A lógica do jogo ainda funcionará na mesma velocidade, mas os objetos se moverão sem problemas, e não “pularão” com um incremento de 8 pixels, como estão fazendo agora. Anteriormente, criei um protótipo de um sistema desse tipo e ele parece ótimo, embora precise ser aprimorado.
  • Suporte para Gamepad. No jogo original, há suporte para joysticks, e o DosBox pode emulá-los em gamepads modernos, mas sua configuração pode ser difícil - é necessária a preparação da configuração e a calibração do jogo. Sem mencionar que nem todos os botões do controlador são suportados, mas para usar o menu, você ainda precisa usar o teclado. Portanto, acredito que o suporte ao controlador nativo melhorará significativamente a jogabilidade.
  • Melhoria de som. Atualmente, todos os efeitos sonoros têm o mesmo volume. Objetos que produzem som, por exemplo, campos de força, tornam-se nitidamente audíveis quando atingem a tela e se quebram da mesma maneira. Fiquei curioso para saber como eles soariam se o volume de efeitos na distância diminuísse. Por exemplo, mal podíamos ouvir o campo de força quando ele ainda não está na tela e, ao se aproximar, fica mais alto.
  • Câmera remota / veja a maior parte do nível. O jogo não foi projetado para isso, então essa possibilidade pode prejudicar a jogabilidade - o jogador começará a ver inimigos que não estão ativos fora da tela e assim por diante. Mas ainda me pergunto como será a aparência e o jogo. No final, os jogadores muitas vezes reclamavam desse jogo por sua incapacidade de ver uma parte suficiente do nível. Seria interessante adicionar a opção de desativar o HUD ou substituí-lo por um mínimo usando a transparência.
  • Aumente a resolução gráfica. Esse recurso geralmente é encontrado em muitas portas / recreação de jogos e seria ótimo adicioná-lo aqui. O mecanismo já permite substituir os gráficos de sprite por suas próprias imagens, mas até agora elas não podem ter uma resolução mais alta, porque tudo é renderizado em um pequeno buffer com um aumento subsequente na escala. Primeiro, você precisa substituir essa abordagem para que a escala possa ser executada para objetos individuais.

Não tenho um roteiro para o futuro, para poder implementar esses pontos em qualquer ordem. Mas, antes de tudo isso, o próximo passo será a integração do Dear ImGui para montar ainda mais o menu de opções, que ainda não está no jogo; além disso, habilitará ou desabilitará as melhorias acima. No final, direi que serei grato por qualquer ajuda no trabalho no GitHub !

Source: https://habr.com/ru/post/pt454570/


All Articles