En 2016, j'ai commencé à travailler sur un projet de loisir pour l'ingénierie inverse du jeu
Duke Nukem II et recréer son moteur à partir de zéro. Le projet s'appelle Rigel Engine et est disponible en open source (
sa page sur GitHub ). Aujourd'hui, plus de deux ans et demi plus tard, sur mon moteur, vous pouvez déjà parcourir tout l'épisode de shareware du jeu original avec un gameplay presque identique à l'original. Voici une vidéo avec le passage du premier niveau:
Que peut-il faire? Rigel Engine fonctionne comme un remplacement complet du binaire DOS d'origine (
NUKEM2.EXE
). Vous pouvez le copier dans le répertoire du jeu et il considère toutes les données qu'il contient, ou spécifier le chemin d'accès aux données du jeu comme argument de la ligne de commande. Le moteur est construit et exécuté sous Windows, Mac OS X et Linux. Il est basé sur
SDL et OpenGL 3 / OpenGL ES 2 et est écrit en C ++ 17.
Il implémente la logique de jeu de tous les ennemis et les mécanismes de jeu de l'épisode Shareware, ainsi que la plupart du système de menus. De plus, vous pouvez y importer des jeux enregistrés et un tableau des meilleurs scores du jeu d'origine.
De plus, le moteur présente des avantages par rapport à l'original:
- Aucun émulateur ou ancien matériel requis, aucun paramètre requis
- Aucun écran de chargement - sélectionnez "nouveau jeu" dans le menu, appuyez sur Entrée et démarrez immédiatement le jeu
- Plusieurs effets sonores peuvent être joués en même temps, ce qui était impossible dans l'original
- Il n'y a aucune restriction sur le nombre d'effets simultanés de particules, d'explosions, etc.
- Enregistrer des fichiers et des listes de meilleurs scores pour chaque joueur
- Menus beaucoup plus réactifs
Jusqu'à présent, je ne considère pas que le moteur Rigel soit complètement «prêt à l'emploi». Mais c'est une grande étape de développement et une bonne occasion d'écrire à nouveau sur le moteur (anciens articles publiés
ici et
ici ). Commençons par jeter un œil à l'état actuel du code et découvrez comment j'y suis parvenu.
Combien de code contient le moteur?
Au moment de la rédaction, RigelEngine se compose de 270 fichiers source contenant plus de 25 000 lignes de code (pas de commentaires / lignes vides). Parmi ceux-ci, 10 fichiers et 2,5 mille lignes sont des tests unitaires. Une ventilation détaillée des lignes vides et des commentaires est disponible
ici .
Que contient tout ce code? Un peu d'infrastructure commune et de fonctions de support, telles que des choses de base comme le rendu, et un tas de petits morceaux de logique. En plus de tout cela, les plus grandes pièces sont:
- analyseurs / téléchargeurs pour 14 formats de fichiers différents utilisés dans le jeu original - 2 mille lignes de code (LOC)
- logique de comportement / logique de jeu pour 24 ennemis / objets hostiles - 3.8k LOC
- logique de jeu pour 14 éléments interactifs et mécaniques de jeu - LOC 2k
- logique de contrôle du joueur - 1.2k LOC
- 154 entrées de configuration (la valeur de santé de chaque ennemi, le nombre de points reçus pour les objets collectés, etc.) - 1k LOC
- 31 spécifications pour les effets de destruction (effets déclenchés par la destruction d'un ennemi ou d'un autre objet destructible) - 254 LOC
- code de commande de la caméra - 159 LOC
- interpréteur de langage de description du menu du jeu / cinématique - 643 LOC
- Le HUD et autre code UI est 818 LOC
- 5 écrans / modes en dehors du menu, par exemple, l'animation initiale, l'écran bonus, etc. - 789 LOC
Bien sûr, tout ce code devait être écrit, ce qui nous amène à la question suivante.
Combien de travail at-il fallu?
Bien que deux ans et demi se soient écoulés depuis le début du projet, je n'y ai pas travaillé tout ce temps. Pendant quelques mois, je n'ai pas du tout fait de projet, dans certains autres, je n'y ai passé que quelques heures. Mais il y a eu des moments où j'ai travaillé très activement sur le moteur Rigel. En regardant le calendrier de validation sur Github, vous pouvez avoir une idée approximative de la répartition de mon travail au fil du temps:
Selon le calendrier, nous constatons que 1081 commits ont été effectués dans la branche principale. Cependant, avant même de créer le dépôt, je travaillais sur un dossier fermé, dans lequel il y avait 247 commits supplémentaires, ce qui nous donne au total 1328 commits. De plus, il y avait plusieurs branches de prototype que j'ai utilisées pour la recherche et l'expérimentation, mais jamais combinées avec la principale; en outre, avant de fusionner, j'ai parfois compressé de grandes histoires de commit en plus courtes.
Je dois également dire que l'écriture de code n'était pas la seule partie du projet - l'ingénierie inverse était une autre partie importante. J'ai passé pas mal d'heures à étudier le code désassemblé du fichier exécutable d'origine dans
Ida Pro (dans la version gratuite), à prendre des notes, à écrire du pseudocode et à planifier la mise en œuvre des éléments de ma version. De plus, j'ai testé activement le jeu original, en le lançant dans
DOSBox et sur l'équipement d'origine (différentes machines 386 et 486 achetées sur eBay). J'ai collecté des niveaux de test pour l'observation séparée d'ennemis spécifiques et l'étude des mécanismes de jeu, enregistré des clips vidéo à l'aide de DOSBox et parcouru les images image par image pour confirmer mes conclusions lors de l'étude du code assembleur. Une fois l'ennemi ou la mécanique du jeu réalisés, j'enregistrais généralement un clip vidéo de ma version et le comparais image par image avec l'original pour confirmer l'exactitude de mon implémentation.
Voici quelques photos de mes notes:
Reverse engineering du code de contrôle de la caméra. Un grand rectangle indique l'écran. Les lignes en pointillés indiquent les zones qu'un joueur peut déplacer sans déplacer la caméra. Si vous êtes intéressé, le code de contrôle de la caméra lui-même peut être trouvé ici .Remarques générales pour vous aider à comprendre le code assembleur. À gauche se trouve la procédure de mise à jour du jeu original à un niveau élevé. À droite se trouvent des notes sur les champs de bits indiquant l'état de certains objets de jeu.Transcription du code assembleur en pseudocode. Habituellement, je le fais assez mécaniquement, transcris sans penser à ce que fait le code, puis utilise la version en pseudo-code pour comprendre la logique sous-jacente. Et sur la base de cela, j'ai déjà trouvé ma mise en œuvre. Voir le code fini ici .Pseudo-code d'une version nettoyée de la logique ennemie. Les en-têtes indiquent l'état de la machine d'état, le code ci-dessous explique ce qui doit se passer dans les états respectifs. Il a été créé sur la base d'un pseudocode brut obtenu par transcription de code assembleur. Le code prêt peut être trouvé ici .En fin de compte, le travail sur le projet s'est avéré très excitant, et j'en ai beaucoup appris: sur l'ingénierie inverse, l'assembleur x86 16 bits, la programmation VGA de bas niveau, les restrictions sévères auxquelles les développeurs de jeux PC ont dû faire face au début des années 90; en outre, j'ai fait de nombreuses découvertes sur les fonctionnalités internes du jeu original et sur la façon dont certaines d'entre elles ont été mises en œuvre de manière étrange et bizarre - ce sujet en soi mérite une série de publications distinctes.
Et ensuite
En plus d'ajouter les dernières fonctions restantes et de finaliser le support de la version enregistrée, j'ai plusieurs idées pour améliorer et étendre les capacités du moteur Rigel, sans parler du nettoyage et de la refactorisation du code - comme d'habitude, la meilleure façon de créer une architecture logicielle n'apparaît qu'après la création de ce logiciel.
En ce qui concerne les améliorations futures, voici quelques points que j'ai pensé à mettre en œuvre:
- Mouvement fluide avec interpolation. Le jeu met à jour sa logique environ 15 fois par seconde, et dans le jeu original, c'est aussi la fréquence d'images pour le rendu. D'un autre côté, le moteur Rigel peut facilement fonctionner avec une fréquence de 60 FPS et plus. Pour le moment, ces cadres supplémentaires ne donnent aucun avantage, mais je pense qu'ils peuvent être utilisés pour des cadres intermédiaires afin de réaliser un défilement et un mouvement des objets plus fluides. La logique du jeu fonctionnera toujours à la même vitesse, mais les objets se déplaceront en douceur et ne «sauteront» pas avec un incrément de 8 pixels, comme ils le font actuellement. Plus tôt, j'ai créé un prototype d'un tel système, et il a fière allure, bien qu'il doive être amélioré.
- Prise en charge de la manette de jeu. Dans le jeu d'origine, les joysticks sont pris en charge et DosBox peut les émuler sur des manettes modernes, mais leur configuration peut être difficile - la préparation de la configuration et l'étalonnage dans le jeu sont nécessaires. Sans oublier que tous les boutons du contrôleur ne sont pas pris en charge, mais pour utiliser le menu, vous devez toujours prendre un clavier. Par conséquent, je crois que le support du contrôleur natif améliorera considérablement le gameplay.
- Amélioration du son. Actuellement, tous les effets sonores ont le même volume. Les objets produisant du son, par exemple, les champs de force, deviennent nettement audibles lorsqu'ils touchent l'écran et se détachent tout aussi fortement. J'étais curieux de savoir comment ils sonneraient si le volume des effets dans la distance diminuait. Par exemple, nous pouvions à peine entendre le champ de force lorsqu'il n'était pas encore sur l'écran, et à l'approche, il devenait plus fort.
- Caméra à distance / voir la plupart du niveau. Le jeu n'a pas été conçu pour cela, donc cette possibilité peut endommager le gameplay - le joueur commencera à voir des ennemis qui ne sont pas actifs en dehors de l'écran, etc. Mais je me demande encore à quoi il ressemblera et jouera. Au final, les joueurs se sont très souvent plaints de ce jeu pour leur incapacité à voir une partie suffisante du niveau. Il serait intéressant d'ajouter l'option d'éteindre le HUD ou de le remplacer par un plus minimal en utilisant la transparence.
- Augmentez la résolution graphique. Cette fonctionnalité se trouve souvent dans de nombreux ports / recréation de jeux, et ce serait formidable de l'ajouter ici. Le moteur vous permet déjà de remplacer les graphiques de sprites par vos propres images, mais jusqu'à présent, ils ne peuvent pas avoir une résolution plus élevée, car tout est rendu dans un petit tampon avec une augmentation d'échelle ultérieure. Tout d'abord, vous devez remplacer cette approche afin que la mise à l'échelle puisse être effectuée pour des objets individuels.
Je n'ai pas de feuille de route pour l'avenir, je peux donc mettre en œuvre ces points dans n'importe quel ordre. Mais avant tout cela, la prochaine étape sera l'intégration de Dear ImGui pour assembler davantage le menu d'options, qui n'est pas encore dans le jeu; en outre, il activera ou désactivera les améliorations ci-dessus. En fin de compte, je dirai que je serai reconnaissant pour toute
aide dans le travail sur GitHub !