CPC Retrodev 2020: Fire Tyre
10623 hits
Un año más, una edición de CPC Retrodev más. Así que toca explicar el origen de "Fire Tyre", el juego presentado al concurso; un origen que abarca siete días contados, en un caso un poco menos extremo que "Epimetheus" pero aún así bastante complicado. -- .One more year, one more CPC Retrodev edition. Thus it's the time to explain the origin of "Fire Tyre", the game I delivered to the compo; an origin that spans seven exact days, in a somewhat less extreme case than "Epimetheus" but still quite complicated.
Mi idea original era reciclar los detritos de "Wire Ware", el juego que quise hacer en 2019 y que finalmente se quedó en la cuneta porque me faltaba de todo: no tenía gráficos, no tenía escenarios, no tenía ni siquiera una idea clara de lo que el juego tendría que ser, más allá del concepto de rescatar rehenes que seguirían al jugador y a los que éste debería proteger. Si en 2019 no estaba inspirado, en 2020 era todavía peor. -- My original idea was to recycle the detritus of "Wire Ware", the game I wanted to make in 2019 and that finally ended up dead on the water because I lacked everything: I had no graphics, no backgrounds, not even a clear idea of what the game had to be, beyond the concept of rescuing hostages that would follow the player and whom he'd have to protect. If I was lacking inspiration in 2019, it was even worse in 2020.
En consecuencia no me sirvió de nada empezar con dos meses de antelación, pues más allá de escribir código genérico (pintar decorados, dibujar sprites, hacer scroll por doble búfer, etc.) no tenía nada sólido, ni tampoco tenía gráficos más allá de un montón de azulejos que no significaban nada, ni siquiera tenía un héroe más allá de hacer alguna clase de vaga alusión a "Prince of Persia", el juego homenajeado por la organización en su trigésimo aniversario. -- Consequently, beginning the work two months in advance was useless, because beyond writing generic code (drawing backgrounds, rendering sprites, performing a double-buffer scroll, etc.) I had nothing solid, I lacked graphics beyond a bunch of tiles that meant nothing, I didn't even have a hero beyond the making some kind of vague allusion to "Prince of Persia", the game that the organisation paid a homage to in its thirtieth anniversary.
A diferencia del año pasado no llegué a rendirme y a abandonarlo todo, pero estaba claro que si el tiempo pasaba no podía seguir luchando contra lo imposible. Paulatinamente empecé a ponderar ideas diferentes, y fue por casualidad que me di cuenta de que había un tema que había querido tocar en una edición pasada y que podía dar pie a un juego autónomo: conducir coches en 3D. -- Unlike the past year I never gave up and quit altogether, but it was obvious that if the time kept running out I couldn't continue fighting the impossible. Step by step I began considering different ideas, and it was by coincidence that I realised that there was a topic I had wanted to touch in a past edition and that it could lead to a standalone game: 3D car driving.
Así fue como el martes 27 de octubre (una semana antes de la fecha de entrega) tomé la decisión irrevocable de improvisar un juego de carreras. El título es un juego de palabras simplón: Hot Wheels, Burnin' Rubber... hay un tema común en estos títulos (¡y otros!), la idea de que las ruedas de los coches se calientan muchísimo. Pues hala, ¡ya tenemos título, "Fire Tyre", el neumático de fuego! -- And that's how on Tuesday October 27th (one week before the deadline) I took the unchangeable decision of improvising a racing game. The title is an utterly simple word play: Hot Wheels, Burnin' Rubber... there's a common theme in these titles (and others!), the idea of automobile wheels becoming extremely warm. So that's it, we have a title, "Fire Tyre", the tyre of fire!
La parte técnica estuvo muy clara desde el principio: debía encontrar una forma de hacer cálculos en 3D muy rápidamente, y además debía ser capaz de dibujar los mismos "sprites" en distintos tamaños al vuelo, es decir "scaling" en tiempo real. Estamos hablando de un Z80 a 4 MHz, ¡nada de esto es fácil! Para colmo, como de costumbre, para garantizar la fluidez de la animación el juego debía emplear un búfer doble por hardware, es decir que debía de reservar 32K a la memoria de vídeo y arreglármelas con los 32K restantes para todo, absolutamente todo: vectores, pila, código, datos estáticos y variables dinámicas. Pero si Ocean pudo producir y vender "WEC Le Mans" con semejantes restricciones (en realidad menos estrictas: encogía la pantalla a 32x24 caracteres y ganaba 8K para código y datos), ¿por qué no iba a lograr algo parecido un servidor? -- The technical part was very clearly defined since the beginning: I had to find a way to perform 3D calculations very rapidly, and it also have to be able to render the same "sprites" at different sizes on the run, in few words realtime scaling. We're talking a 4 MHz Z80 here, none of these things is easy! Eve worse, as usual, to ensure that animation would be smooth I had to utilise a hardware double buffer, and this means I had to reserve 32K for the video memory and get everything else, absolutely everything else, done with the remaining 32K: vectors, stack, code, static data and dynamic variables. But if Ocean was able to produce and sell "WEC Le Mans" with such restrictions (in fact they were less strict: it shrank the size to 32x24 characters and gained 8K for code and data), why couldn't yours truly achieve anything similar?
Los cálculos en 3D se redujeron grandemente gracias al empleo de tablas precalculadas (por ejemplo, la tabla de distancias relativas se compone de 96 pasos obtenidos mediante FOR n=0 TO 95:PRINT ROUND(2^(n/19));:NEXT en BASIC), y el resultado era que lo más complicado que tenía que hacer (matemáticamente hablando) era multiplicar valores por coeficientes ubicados en el intervalo de 0 a 15, y dividir los resultados entre 8, 16 ó 32. Mucho más pesado fue concebir una rutina de "scaling" para al menos ocho escalas distintas (1:8, 1:4, 3:8, 1:2, 5:8, 3:4, 7:8, 1:1) y la solución fue bastante costosa: almacenar los sprites "escalables" como píxeles sueltos (lo que les hacía gastar el doble de memoria, de una forma semejante a la usada por Jon O'Brien "Jobbeee" y Robert Hemphill en "Burnin' Rubber", pero ellos tenían la ventaja de trabajar en un cartucho de 128K de tamaño y yo no) y escribir dieciséis rutinas diferentes de dibujado de sprites, una para cada escala posible y para las dos anchuras posibles de sprites: estrechos (ocho bytes de anchura, por ejemplo los árboles del decorado) y anchos (dieciséis bytes de anchura, por ejemplo los coches de los rivales). Menos mal que las macros lo hacían todo más sencillo: -- 3D calculations were greatly reduced thanks to the usage of precalculated tables (for example, the table of relative distances is made of 96 steps that can be generated with FOR n=0 TO 95:PRINT ROUND(2^(n/19));:NEXT in BASIC), and the outcome was that the most complicated operation I had to do (mathematically speaking) was to multiply values by coefficients within the range from 0 to 15, and dividing their results between 8, 16 or 32. It was much more heavy when I had to conceive a "scaling" routine for at least eight different scales (1:8, 1:4, 3:8, 1:2, 5:8, 3:4, 7:8, 1:1) and the solution was quite costly: storing the "scalable" sprites as standalone pixels (and so they spent twice as much memory, in a similar fashion used by Jon O'Brien "Jobbeee" y Robert Hemphill en "Burnin' Rubber", but they had the advantage of working in a 128K cartridge and that wasn't my case) and writing sixteen different sprite rendering routines, one for each possible scale and for the two possible sprite widths: narrow (eight bytes wide, for example the trees in the scenery) and wide (sixteen bytes wide, for example the rivals' cars). Fortunately the macros made everything simpler:
single_init 5 scaled_x inc c inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c ;*MID scaled_y inc c inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c inc c scaled_z single_exit 5 |
single_init 6 scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c ;*MID scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_z single_exit 6 |
single_init 7 scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c scaled_x inc c ;*MID scaled_y inc c scaled_x inc c scaled_y inc c inc c scaled_x inc c scaled_y inc c scaled_x inc c scaled_z single_exit 7 |
single_init 8 scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c ;*MID scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c scaled_x inc c scaled_y inc c scaled_x inc c scaled_z single_exit 8 |
Estos procedimientos sirven para dibujar un sprite de ocho bytes de anchura a las escalas 5:8, 3:4, 7:8 y 1:1 respectivamente; para ello recogen regularmente píxeles (estamos en MODE 0, cada byte contiene dos píxeles) y los dibujan por parejas en la memoria de vídeo mediante "scaled_x", "scaled_y" y "scaled_z". Además se saltan líneas a un ritmo igualmente regular, que es lo que se hace dentro de "single_init": por ejemplo el primer caso (5:8) omite cuatro líneas y dibuja una. -- These procedures' purpose is to render a sprite eight bytes wide at the scale of 5:8, 3:4, 7:8 and 1:1respectively; thus they regularly gather pixels (we're in MODE 0, each byte contains two pxiels) and draw them in couples into the video memory thru "scaled_x", "scaled_y" y "scaled_z". They also skip lines at an equally regular rhythm, as it's done inside "single_init": for example the first case (5:8) omits four lines and renders one.
Dibujar los decorados también exigió desenrollar el código en la medida en que la memoria limitada me lo permitía. Las tablas precalculadas me servían para conocer la anchura de la carretera y de sus bordes según su distancia del jugador; gracias a ello el único inconveniente serio era "cortar" el procedimiento de dibujado de tal forma que el dibujador de decorados no trabajase a ciegas, sino que supiese en qué puntos debía empezar y terminar. No es ninguna tontería, pues dibujar píxeles de más me costaría un tiempo precioso aunque fuese capaz de limpiarlos a posteriori. -- Rendering the backgrounds also required unrolling the code as much as the limited memory allowed. The precalculated tables let me know the width of the road and its borders in respect to their distance from the player; thanks to it the only serious inconvenience was to "cut" the rendering procedure in such a way that the background renderer wouldn't work blindly, but it would be aware of the points where it would have to begin and end. It isn't trivial, because drawing more pixels than intended would cost valuable time even if I were able to clean them up later.
El motor gráfico ya estaba terminado y aguantaba el tipo entre 8 y 12 frames por segundo. Ahora tocaba dibujar todos los sprites que faltaban e implementar las reglas de juego. Por mor de la sencillez de juego (y porque el tiempo ya no me sobraba, ¡dos días!) elegí como modelo "Pole Position" (1982 Namco), con ribetes de "Pitstop II" (1984 Epyx), "Formula 1 Simulator" (1986 Mastertronic) y por supuesto "Burnin' Rubber" (1990 Ocean): el primero es el progenitor de todos los juegos de carreras en 3D, el segundo es un buen ejemplo de cámara fija (que me parece más agradable a la vista que la cámara móvil de "Pole Position"), el tercero es simple pero divertido (¡y tiene música de Rob Hubbard!) y el cuarto es el coche de carreras para Amstrad CPC (bueno... Amstrad Plus) por excelencia. -- The graphic engine was finished at last and it could stand between 8 and 12 frames per second. It was the time to draw all the missing sprites and writing the game rules. For sake of gameplay simplicity (and because I didn't have much time left, two days!) the chosen model was "Pole Position" (1982 Namco), with bits from "Pitstop II" (1984 Epyx), "Formula 1 Simulator" (1986 Mastertronic) and of course "Burnin' Rubber" (1990 Ocean): the first one is the progenitor of all 3D racing games, the second one is a good example of static camera (that I deemed nicer on the eyes than the moving camera from "Pole Position"), the third one is simple but funny (and it's got music from Rob Hubbard!) and the fourth one is the driving game for Amstrad CPC (well... Amstrad Plus) "par excellence".
Entonces llegó al día de entrega: grabé este vídeo tal como solicitaban las reglas del concurso. -- Then the delivery day came: I recorded this video as the contest's rules requested.
Una vez más hube de improvisar la música a base de apoyarme en el legendario Rob Hubbard y más exactamente en su canción para el antes mencionado "Formula 1 Simulator" para Commodore 64. Una vez más lo hice todo con CHIPNSFX, que también me sirvió para adaptar "Comic Bakery" de Martin Galway para el menú del juego y "JT in Space" de Jeroen Tel para la secuencia final. No soy original y me duele, pero cuando el tiempo corre no hay más alternativa que hurgar en el catálogo. -- Once again I had to improvise the music by relying on the legendary Rob Hubbard and more exactly on his song for the already mentioned "Formula 1 Simulator" for Commodore 64. Once again I made everything with CHIPNSFX, that also helped me adapt Martin Galway's "Comic Bakery" for the game menu and Jeroen Tel's "JT in Space" for the ending sequence. I'm not original and it hurts, but when the time runs out there's no choice but peeking into the catalogue.
Y esto es todo por ahora; en el momento de escribir estas líneas el juego ya ha sido admitido a concurso, pero aún falta una semana antes de que el jurado se reúna para anunciar sus decisiones y proclamar los ganadores. ¿Os atrevéis a desearme suerte? -- And this is all for now; the game was already admitted into the compo when I wrote these lines, but there's one week left before the jury gathers to state their choices and announce the winners. Do you dare wishing me luck?
César N.G., Nov 05 2020, 18:55
EDIT 20201103: quinto puesto y ninguna mención especial. El jurado explícitamente rechazó puntuar la música del juego por tratarse de versiones. -- fifth place and no special mentions at all. The jury explicitly rejected scoring the game's music because it's made of covers.