En 2016, comencé a trabajar en un proyecto de pasatiempos para realizar ingeniería inversa del juego
Duke Nukem II y recrear su motor desde cero. El proyecto se llama Rigel Engine y está disponible en código abierto (
su página en GitHub ). Hoy, más de dos años y medio después, en mi motor, ya puedes ver todo el episodio shareware del juego original con un juego casi idéntico al original. Aquí hay un video con el pasaje del primer nivel:
Que puede hacer el? Rigel Engine funciona como un reemplazo completo para el binario original de DOS (
NUKEM2.EXE
). Puede copiarlo en el directorio del juego y considera todos los datos que contiene, o especificar la ruta a los datos del juego como argumento de la línea de comando. El motor está construido y ejecutado bajo Windows, Mac OS X y Linux. Está basado en
SDL y OpenGL 3 / OpenGL ES 2, y está escrito en C ++ 17.
Implementa la lógica del juego de todos los enemigos y la mecánica del juego del episodio Shareware, además de la mayoría del sistema de menús. Además, puede importar juegos guardados y una tabla de puntaje alto del juego original en él.
Además, el motor tiene ventajas sobre el original:
- No se requiere emulador ni hardware antiguo, no se necesita configuración
- No hay pantallas de carga: seleccione "nuevo juego" en el menú, presione Entrar e inmediatamente comience el juego
- Se pueden reproducir varios efectos de sonido al mismo tiempo, lo cual era imposible en el original
- No existen restricciones sobre el número de efectos simultáneos de partículas, explosiones, etc.
- Guarde archivos y listas de puntajes para cada jugador
- Menús mucho más receptivos
Hasta ahora, no considero que el motor Rigel esté completamente "listo". Pero esta es una gran etapa de desarrollo y una buena oportunidad para volver a escribir sobre el motor (publicaciones antiguas publicadas
aquí y
aquí ). Comencemos por echar un vistazo al estado actual del código y descubrir cómo llegué a él.
¿Cuánto código hay en el motor?
Al momento de escribir, RigelEngine consta de 270 archivos fuente que contienen más de 25 mil líneas de código (sin comentarios / líneas vacías). De estos, 10 archivos y 2.5 mil líneas son pruebas unitarias. Un desglose detallado de líneas vacías y comentarios está disponible
aquí .
¿Qué hay en todo este código? Un poco de infraestructura común y funciones de soporte, cosas básicas como el renderizado y un montón de pequeñas piezas de lógica. Además de todo esto, las partes más grandes son:
- analizadores / descargadores para 14 formatos de archivo diferentes utilizados en el juego original: 2 mil líneas de código (LOC)
- lógica de comportamiento / lógica de juego para 24 enemigos / objetos hostiles - 3.8k LOC
- lógica de juego para 14 elementos interactivos y mecánica del juego - 2k LOC
- lógica de control del jugador - 1.2k LOC
- 154 entradas de configuración (el valor de salud de cada enemigo, el número de puntos recibidos por los elementos recogidos, etc.) - 1k LOC
- 31 especificaciones para efectos de destrucción (efectos desencadenados por la destrucción de un enemigo u otro objeto destructible) - 254 LOC
- código de control de la cámara - 159 LOC
- intérprete de lenguaje de descripción del menú del juego / escena - 643 LOC
- El HUD y otro código de UI es 818 LOC
- 5 pantallas / modos fuera del menú, por ejemplo, la animación inicial, la pantalla de bonificación, etc. - 789 LOC
Por supuesto, todo este código necesitaba ser escrito, y esto nos lleva a la siguiente pregunta.
¿Cuánto trabajo tomó?
Aunque han pasado dos años y medio desde el inicio del proyecto, no he trabajado en ello todo este tiempo. Durante un par de meses no hice un proyecto en absoluto, en algunos otros solo pasé varias horas en él. Pero hubo momentos en que trabajé en el motor Rigel de manera bastante activa. Mirando el cronograma de confirmación en Github, puede tener una idea aproximada de cómo se distribuyó mi trabajo a lo largo del tiempo:
De acuerdo con el cronograma, vemos que se realizaron 1081 confirmaciones en la rama maestra. Sin embargo, incluso antes de crear el repositorio, estaba trabajando en uno cerrado, en el que había 247 confirmaciones más, lo que en total nos da 1328 confirmaciones. Además, había varias ramas prototipo que utilicé para investigación y experimentación, pero nunca las combiné con la principal; Además, antes de fusionarme, a veces comprimía grandes historias de commit en otras más cortas.
También debo decir que la escritura de códigos no fue la única parte del proyecto; la ingeniería inversa fue otra parte importante. Pasé unas horas estudiando el código desmontado del archivo ejecutable original en
Ida Pro (en la versión gratuita), tomando notas, escribiendo pseudocódigo y planificando la implementación de los elementos de mi versión. Además, probé activamente el juego original, lanzándolo en
DOSBox y en el equipo original (diferentes máquinas 386 y 486 compradas en eBay). Recolecté niveles de prueba para la observación separada de enemigos específicos y el estudio de la mecánica del juego, grabé videoclips usando DOSBox y miré los cuadros cuadro por cuadro para confirmar mis conclusiones hechas mientras estudiaba el código del ensamblador. Después de que el enemigo o la mecánica del juego se dieron cuenta, generalmente grabé un video clip de mi versión y lo comparé cuadro por cuadro con el original para confirmar la precisión de mi implementación.
Aquí hay algunas fotos de mis notas:
Código de control de cámara de ingeniería inversa. Un rectángulo grande indica la pantalla. Las líneas discontinuas muestran las zonas que un jugador puede mover sin mover la cámara. Si está interesado, el código de control de la cámara se puede encontrar aquí .Notas generales para ayudarlo a comprender el código de ensamblaje. A la izquierda está el procedimiento para actualizar el juego original a un alto nivel. A la derecha hay notas en los campos de bits que indican el estado de algunos objetos del juego.Transcripción de código ensamblador en pseudocódigo. Por lo general, lo hago de forma mecánica, transcribo sin pensar en lo que está haciendo el código y luego uso la versión en pseudocódigo para comprender la lógica subyacente. Y en base a esto, ya se me ocurrió mi implementación. Vea el código terminado aquí .Pseudocódigo de una versión limpia de la lógica enemiga Los encabezados indican el estado de la máquina de estados, el siguiente código explica lo que debe suceder en los estados respectivos. Fue creado sobre la base del pseudocódigo bruto obtenido al transcribir el código del ensamblador. El código listo se puede encontrar aquí .Al final, el trabajo en el proyecto resultó ser muy emocionante, y aprendí mucho de él: sobre ingeniería inversa, ensamblador x86 de 16 bits, programación VGA de bajo nivel, restricciones severas que los desarrolladores de juegos de PC tuvieron que enfrentar a principios de los 90; Además, hice muchos descubrimientos sobre las características internas del juego original y cuán extraños y extraños se implementaron algunos de ellos: este tema en sí mismo merece una serie separada de publicaciones.
Que sigue
Además de agregar las últimas funciones restantes y finalizar el soporte para la versión registrada, tengo varias ideas para mejorar y expandir las capacidades de Rigel Engine, sin mencionar la limpieza y refactorización del código, como de costumbre, la mejor manera de crear una arquitectura de software se hace evidente solo después de que se completa la creación de este software.
En cuanto a futuras mejoras, estos son algunos de los puntos que pensé en implementar:
- Movimiento suave con interpolación. El juego actualiza su lógica aproximadamente 15 veces por segundo, y en el juego original también es la velocidad de fotogramas para el renderizado. Por otro lado, el Rigel Engine puede funcionar fácilmente con una frecuencia de 60 FPS y superior. Por el momento, estos cuadros adicionales no ofrecen ninguna ventaja, pero creo que pueden usarse para cuadros intermedios con el fin de lograr un desplazamiento y movimiento de objetos más suaves. La lógica del juego seguirá funcionando a la misma velocidad, pero los objetos se moverán suavemente y no "saltarán" con un incremento de 8 píxeles, como lo están haciendo ahora. Anteriormente, creé un prototipo de dicho sistema, y se ve muy bien, aunque debe mejorarse.
- Soporte para gamepad. En el juego original hay soporte para joysticks, y DosBox puede emularlos en gamepads modernos, pero su configuración puede ser difícil: se requiere preparación de la configuración y calibración en el juego. Sin mencionar que no todos los botones del controlador son compatibles, pero para usar el menú aún debe tomar un teclado. Por lo tanto, creo que la compatibilidad con el controlador nativo mejorará significativamente la jugabilidad.
- Mejora de sonido. Actualmente, todos los efectos de sonido tienen el mismo volumen. Los objetos que producen sonido, por ejemplo, campos de fuerza, se vuelven audiblemente agudos cuando golpean la pantalla, y se rompen tan bruscamente. Tenía curiosidad por cómo sonarían si el volumen de efectos en la distancia se desvanecía. Por ejemplo, apenas podíamos escuchar el campo de fuerza cuando aún no estaba en la pantalla, y al acercarnos se volvería más fuerte.
- Cámara remota / ver la mayor parte del nivel. El juego no fue diseñado para esto, por lo que esta posibilidad puede dañar el juego: el jugador comenzará a ver enemigos que no están activos fuera de la pantalla, y cosas por el estilo. Pero todavía me pregunto cómo se verá y jugará. Al final, los jugadores a menudo se quejaron de este juego por su incapacidad para ver una parte suficiente del nivel. Sería interesante agregar la opción de apagar el HUD o reemplazarlo por uno más mínimo con transparencia.
- Aumenta la resolución de los gráficos. Esta característica a menudo se encuentra en muchos puertos / recreación de juegos, y sería genial agregarla aquí. El motor ya le permite reemplazar los gráficos de sprites con sus propias imágenes, pero hasta ahora no pueden ser de mayor resolución, porque todo se procesa en un pequeño búfer con un aumento posterior en la escala. Primero, debe reemplazar este enfoque para que la escala se pueda realizar para objetos individuales.
No tengo ninguna hoja de ruta para el futuro, por lo que puedo implementar estos puntos en cualquier orden. Pero antes de todo esto, el siguiente paso será la integración de Dear ImGui para ensamblar aún más el menú de opciones, que aún no está en el juego; Además, habilitará o deshabilitará las mejoras anteriores. ¡Al final, diré que agradeceré cualquier
ayuda para trabajar en GitHub !