CPC Retrodev 2019: Epimetheus
54432 hits
La historia del desarrollo de "Epimetheus", el videojuego escrito por un servidor para el concurso CPC Retrodev 2019, es breve pero accidentada: abarca las últimas cuarenta y ocho horas antes del cierre del plazo. No bromeo. -- The story of the development of "Epimetheus", the videogame written by yours truly for the CPC RetroDev 2019 compo, is short but accident-ridden: it spans the last forty eight hours before the deadline. I'm not joking.
¿Cómo se pudo llegar a este extremo? La idea inicial era completamente distinta: mi primer proyecto se llamó "Wire Ware" (en alusión facilona a mi propio "Hire Hare", a su vez con el nombre heredado del legendariamente enigmático "Mire Mare" de Ultimate) y debía ser un juego de scroll horizontal de plataformas y disparos, menos parecido al popular y famoso "Astro Marine Corps / AMC" (1989 Dinamic) que al difícil pero exquisito "Mutan Zone" (1988 Opera Soft). -- How could it get to such an extreme? The original idea was completely different: my first project was called "Wire Ware (in a cheap reference to my own "Hire Hare", itself inheriting the name from the legendarily mysteryous "Mire Mare" from Ultimate)) and it was supposed to be a horizontal scrolling platform-and-shooting game, less similar to the popular and famous "Astro Marine Corps / AMC" (1989 Dinamic) than to the difficult but exquisite "Mutan Zone" (1988 Opera Soft).
El desarrollo de "Wire Ware" había empezado con dos meses de antelación. Debería haber sido suficiente tiempo para hacer un videojuego presentable; y la verdad es que la parte de la programación no me dio demasiados problemas. También estuve inspirado para escribir música original, ¡por una vez! Pero todo fue en vano porque para lo que me faltaron inspiración y tiempo fue todo lo visual. Pasaban las semanas y dibujaba pocos azulejos y menos sprites todavía. No estaba inspirado, así de sencillo y brutal. -- The development of "Wire Ware" begun two months in advance. It should have been enough time to make an acceptable videogame; and truth is that the programming part didn't bring me too many troubles. I was also inspired enough to write original music, for first time! But it ultimately meant nothing because what I lacked inspiration and time for were all the visuals. Weeks went by and I drew few tiles and even fewer sprites. I was simply and brutally uninspired.
Cuando quedaban diez días antes de acabarse el plazo anuncié formalmente que lo daba por imposible y me rendía. Me dolió, pero en su momento parecía que no había más remedio, y así lo hice saber en mi blog así como en ámbitos más personales. Recibí comentarios de comprensión y consuelo, lo que aliviaba la tristeza del fracaso. Además me animaba a reanudar las mejoras del emulador CPCEC, y precisamente durante esos días conseguí resolver los problemas que hacían injugable el recientemente publicado y muy espectacular "Pinball Dreams" de Batman Group. -- When only ten days were left before the deadline I formally announced that I deemed it unfeasible and I gave up. I was hurt, but back in the day it seemed like there was nothing else to do, and that's how I stated it in my blog as well as in other more personal environments. I received words of sympathy and consolation, softening the sadness of failure. I was also encouraged to continue improving the emulator CPCEC, and it was precisely during those days that I was able to overcome the problems that rendered unplayable the recently published and very spectacular "Pinball Dreams" from Batman Group.
Sin embargo, el lunes 28 por la tarde (dos días antes del final del plazo) tuve una idea súbita y repentina. En su momento, tiempo antes, yo había mencionado "Mad Planets" (1983 Gottlieb) en el foro de CPC Wiki a raíz de un comentario sobre la falta de sprites alineados al píxel (además de los típicos alineados al byte) en los juegos presentados a concurso. Respondí que los sprites optimizados a base de "desenrollar" código marcaban un límite severo al tamaño de los programas, y puse como ejemplo este título como un caso de juego que, con sprites MUY limitados, podría hacerse en un CPC. Así que de repente me dije: "¿y si lo hago?" -- However, on Monday 28th in the evening (two days before the deadline) I got a sudden and spontaneous idea. Back in the day, years before, I had mentioned "Mad Planets" (1983 Gottlieb) in the CPC Wiki forum because of a comment about the lack of pixel-aligned sprites (besides the typical byte-aligned ones) in the games sent to the contest. I answered that sprites that get optimised by "unrolling" code set a severe limit to the size of the programmes, and I used as an example this title as a case of game that, with VERY limited sprites, would be feasible on a CPC. So I suddenly told to myself: "what if I make it"?
Así fue como una serie de ideas que había arrastrado durante un año o dos y con las que había experimentado un poco por cuenta propia pero que no habían pasado de ser simples experimentos tomaron forma y me impulsaron a escribir mi propio "Mad Planets"; y donde digo esto también digo su inspiración "Asteroids" (1979 Atari), sus imitadores "Crazy Comets" (1985 Martech) y "Mega Apocalypse" (1987 Martech) y en general todos los matamarcianos donde el jugador puede moverse con cierta libertad por el área de juego y se enfrenta a marcianos igualmente capaces de desplazarse a capricho. Otra influencia, más moderna, sería el matamarcianos de estilo "bullet hell" o "danmaku" japonés, que incentiva la velocidad y los reflejos por encima de la complejidad. -- That's how a series of ideas that I had dragged for a year or two and that I had performed several experiments on my own but that hadn't gone beyond the basic experimenting stage took shape and pushed me into writing my own "Mad Planets"; and where I say this I also mean its inspiration "Asteroids" (1979 Atari), its imitators "Crazy Comets" (1985 Martech) and "Mega Apocalypse" (1987 Martech) and more generally all the shoot-em-ups where the player can move with some freedom thru the gameplay area and faces invaders similarly able to move on their own will. Another influence, more modern, would be the "bullet hell" style of shoot'em up, also known as Japanese "danmaku", that prioritises speed and reflexes above complexity.
Desde el principio estuvo claro que para crear un juego que corriese a 50 frames por segundo tendría que optimizar mucho las rutinas gráficas y además definir un algoritmo general que redujese al mínimo toda la actividad gráfica. En esta tesitura, utilizar el doble búfer por hardware del CPC era inevitable: el juego tendría que caber en 32K porque los otros 32K disponibles estarían divididos en dos mitades de 16K donde cada una de ellas sería uno de los búferes de pantalla. El uso del doble búfer (que abarca mucha memoria, cuya manipulación consume tiempo) implicaba un requisito adicional: el programa debía reducir los borrados al mínimo, así que por cada operación de dibujado de sprites debía haber otra operación que guardase en una lista las áreas de memoria que deberían ser borradas en la próxima iteración que emplease el mismo búfer de memoria de video. ¡Menos mal que solamente eran dos búferes! -- It was obvious since the beginning that creating a game that ran at 50 frames per second would require heavy optimisations on the graphical routines as well as defining a general algorithm that reduced to the maximum all the graphical activity. In these conditions, using the hardware double buffer in the CPC was unavoidable: the game had to fit in 32K because the other available 32K would be divided in two halves of 16K where each would be a screen buffer. The double buffer (that spans a lot of memory, whose handling consume time) implied an additional requirement:the programme had to reduce erasing to the minimum, so for each sprite drawing operation there had to be another operation that kept a list of memory areas to be erased in the next iteration that handled the same video memory buffer. Fortunately they were just two buffers!
También se hacía inevitable ir un paso más allá de las rutinas de sprites desenrolladas habituales: necesitaba rutinas de sprites precompilados, es decir una rutina distinta pero específica y óptima para cada sprite, en lugar de una sola rutina general para todos los sprites. Y es que al dibujar sprites se va mucho tiempo en discernir si cada nuevo byte es totalmente opaco o si tiene alguna transparencia; incluso utilizando tablas precalculadas se pierde tiempo en consultarlas. Resumiendo: -- It was also unavoidable to go one step beyond the usual unrolled sprite routines: I needed precompiled sprite routines, i.e. a different yet unique and optimal routine for each sprite, instead of a single general routine for all the sprites. It's because when we draw sprites we lose so much time in guessing whether each new byte is fully opaque or includes some transparency; even with the help of precalculated tables we lose time in checking them. In few words:
µs | operación |
2 | LD A,(BC) |
1 | INC C |
1 | LD L,A |
2 | LD A,(DE) |
2 | AND (HL) |
1 | OR L |
2 | LD (DE),A |
1 | INC E |
Esto gasta 12 µs en dibujar en la dirección de vídeo DE un byte de un sprite genérico almacenado en la dirección BC mediante una tabla precalculada ubicada en una página de 256 bytes cuya dirección es igual a H*256; se asume que vídeo y sprite están alineados a la página y que podemos usar INC C e INC E sin miedo a salirnos de la página, porque entonces necesitaríamos las operaciones INC BC e INC DE que consumen 2 µs cada una en lugar de uno. Pero si supiésemos de antemano las transparencias y opacidades de los sprites podríamos reducirlo todo a cuatro casos posibles basados en ubicar la dirección de vídeo en HL y los valores $AA y $55 en los registros B y C: -- This spends 12 µs in drawing in the video address DE a byte of a generic sprite stored in the address BC through a precalculated table set in a 256-byte page whose address equals H*256; we assume that video and sprite are aligned to the page and that we can use INC C and INC E without fearing to get ouf of the page, because in that case we'd need to use the operaions INC BC and INC DE that consume 2 µs each instead of one. But if we knew in advance the transparencies and opacities of the sprites we'd be able to reduce everything to four possible cases based on setting the video address on HL and the values $AA and $55 in the registers B and C:
Totalmente transparente -- Fully transparent
|
Píxel izquierdo opaco -- Opaque left pixel
|
||||||||||||||
Píxel derecho opaco -- Opaque right pixel
|
Totalmente opaco -- Fully opaque
|
La mejora es manifiesta incluso en el peor caso posible: 8 µs contra 12. El único problema es que hay que generar este código, con el resultado de que lo primero que hace el juego tras cargar en memoria es examinar sus propios datos gráficos para generar el código correspondiente a cada sprite. Además de consumir tiempo (afortunadamente no mucho) esto consume memoria (desgraciadamente mucha): donde antes bastaba con una copia del código de dibujado de sprites y los datos puros de cada sprite, ahora había que generar una rutina para cada sprite a partir de sus datos particulares; y esto significaba no solamente tener que convertir un byte de datos en hasta 6 bytes de código, sino que además había que añadir el código necesario para calcular la posición del sprite en la memoria de vídeo, para pasar de una línea de vídeo a la siguiente, etc. El resultado: con solamente 30 sprites distintos (que ocupan menos de 5K en su estado natural) la memoria disponible de 32K para código y datos se agotó. -- The improvement stands out even in the worst possible case: 8 µs against 12. The only problem is that we have to generate this code, with the outcome that the first thing the game does after loading itself in memory is to examine its own graphical data to generate the matching code for each sprite. Besides consuming some time (fortunately not a lot) this consumes memory (unfortunately a lot): where it was enough to keep a single copy of the sprite-rendering code and the pure data of each sprite, now we had to generate a routine for each sprite from its particular data; and this meant not only having to turn one byte of data in up to 6 bytes of code, but it also had to include the required code to calculate the location of the sprite in the video memory, to jump to one video line to the next one, etc. The outcome: with only 30 different sprites (that required less than 5K in their natural state) the available memory of 32K for code and data ran out.
La escasez de memoria obligó a reducir en general los contenidos del juego; además el tiempo corría en mi contra a toda velocidad. El juego genera todas las oleadas de invasores de una forma puramente procedural: el número de atacantes, su apariencia y su comportamiento dependen de la fase en curso, del tiempo de ejecución del juego y de las acciones del propio jugador. De igual forma la inteligencia artificial de los enemigos es puramente algorítmica. Incluso el comportamiento de los disparos del jugador sigue una lógica puramente matemática: el daño que un disparo inflige a un enemigo es inversamente proporcional a la distancia desde la que el jugador lo disparó. Esto fomenta el juego arriesgado donde el jugador debe acercarse a los enemigos y abrir fuego a bocajarro. -- The scarcity of memory made me reduce the general contents of the game; besides, the time ran out to my harm at full speed. The game generates all the invader waves in a purely procedural way: the amount of aggressors, their appearance and behaviour depend of the current round, the time of gameplay and the actions of the player himself. Similarly, the artificial intelligence of the enemies is purely algorithmical. Even the behavior of the player's shots follows a purely mathematical logic: the damage a shot inflicts on an enemy is inversely proportional to the distance where the player shot it. This encourages risky playing where the player must move near the enemies and shoot at point blank.
En hacer la primera versión del juego se fue el primer día; el resultado fue un vídeo de muestra preliminar. -- The first day went away in making the first version of the game; the outcome was a preliminar test video.
En pulir las asperezas de la primera versión se fue el segundo día: fondo de estrellas multicolor, más variedad en los sprites (dos meteoros y dos platillos volantes), IA más voluble (si te limitas a esquivarlos acaban por perder la "paciencia", acelerarse e ir a por ti), ¡incluso los enemigos podían disparar al jugador a partir de la fase 8! Con ello grabé un nuevo vídeo, más cuidado que el anterior, aunque inevitablemente corto dados los requisitos del concurso. -- The second day went away in softening the rough edges of the first version: multicolour starfield background, more variety in sprites (two meteors and two flying saucers), more randomized AI (if you stick to dodge them they end up losing "patience", speeding up and chasing you), even the enemies could shoot at the player from round 8 onwards! That's how I recorded a new video, more detailed than the previous one, although unavoidably short because of the contest requirements.
La música de "Wire Ware" ya no servía para este caso, pero afortunadamente ya tenía preparada la mejor elección posible para esta clase de videojuego: adaptaciones caseras para mi motor musical CHIPNSFX de las dos canciones de Rob Hubbard para la versión de Commodore 64 (¿hubo más?) de "Crazy Comets". Desde aquí debo agradecer un poco incómodamente a Rob Hubbard su inestimable labor en el desarrollo de mis juegos desde "Frogalot" para CPC RetroDev 2015. ¡Cómo se va el tiempo! -- The music of "Wire Ware" was no longer appropriate, but fortunately I already had ready the best possible choice for this type of videogame: home covers for my musical engine CHIPNSFX of the two songs by Rob Hubbard for the Commodore 64 version (were there others?) of "Crazy Comets". From here I must thank somewhat uncomfortable to Rob Hubbard his priceless role in the development of my games since "Frogalot" for CPC RetroDev 2015. Look at how time flies away!
Finalmente, el título del juego encierra un juego de palabras bastante tonto que cualquiera familiarizado con la mitología griega conoce bien. Epimeteo es el hermano de Prometeo, el titán bienhechor de la humanidad que robó el fuego de la fragua de Vulcano y se lo dio a los hombres para que creasen su primera civilización. Pero mientras que Prometeo era inteligente y audaz, Epimeteo era menos agudo y cometió errores graves, de los que el más famoso es el mito de Pandora (su esposa) y la caja que los dioses olímpicos les regalaron en sus bodas. "Prometheus" en griego alude a 'pensar antes' (de obrar); "Epimetheus", a pensar después. ¡Y qué duda cabe de que el desarrollo de "Epimetheus" había sido una aventura epimeteica por todo lo que tenía de descerebrada, improvisada, accidentada y apresurada! -- Finally, the title of the game hides a quite silly wordplay that anyone who's already acquainted with Greek mythology knows well. Epimetheus is the brother of Prometheus, mankind's benefactor titan who stole the fire from Vulcan's forge and gave it to the men so they could create their first civilisation. But while Prometheus was intelligent and daring, Epimetheus was less sharp and made serious mistakes, the most famous being the myth of Pandora (his wife) and the box that the Olympic gods gave them on their wedding. "Prometheus" in Greek stands for 'thinking before' (acting); "Epimetheus", for 'thinking after. And how can we question that the development of "Epimetheus" had been an Epimethean adventure because of everything it had of brainless, improvised, accidental and rushed!
César N.G., Nov 08 2019, 20:46
EDIT 20191109: tercer puesto y menciones especiales de Pablo Ariza (al desarrollo técnico y la IA) y Javier Ortiz (al mejor juego de acción) -- third place and special mentions from Pablo Ariza (technical development and IA) and Arcade Vintage (best arcade game)