artículo

Los scrollers de antaño

Making of «Logon’s run - 3D meets the aging bits» (presentada en la Revision 2017) por Overflow / Logon System

Artículo escrito y diseñado por Overflow / Logon System en febrero de 2017 - ​Publicado por NoRecess en abril de 2017. Haz clic AQUÍ para ver la demo en Pouet.

Introducción

Hacer trampas siempre ha formado parte del demomaking. ¿Animación fluida? piensa en el hardware-scroller: todo puede estar pre-renderizado en la VRAM, y luego la visualización en pantalla se ajusta hábilmente en cada frame cambiando el puntero a la VRAM. ¡Creo que llevo obsesionado con esto desde el mismísimo principio! A lo largo de los años he usado HW-scrollers de distintas maneras para conseguir animaciones que no dejaban de mejorar. Hasta la publicación de hoy de «Logon’s run - 3D meets the aging bits». Como bonus o como making-of: aquí está la historia completa. Mucho que ver, unos cuantos DSK que descargar, pero no tanto que leer.

64KB de VRAM pre-renderizada = 4096 caracteres = 60 (ancho) * 68 (alto) + 16 | visualización en pantalla = ventanas de scroll de 48*34

gagacubiz_1

«Gagacubiz» [2014 | publicada en 2017 en «Logon’s run - 3D meets the aging bits»] Pouet!

1991-1992 LOS PRIMEROS AÑOS

En 1991: esconder 12 caracteres en el borde, fuera de la pantalla visible.

s koh

«S&KOH» [1991] Pouet!

Animación de 4 pasos | scroll de 4 caracteres hacia la derecha

s koh fire

Scroller fluido | 4 flippings + scroll de 1 carácter hacia la derecha

s koh logon

Llegó una mejora: línea de caracteres sin restricción de longitud, para esconder mucho más en una sola línea de caracteres. Normalmente no debería haber más de 64 caracteres en una sola scanline. Partiendo la pantalla en cada línea de caracteres: la restricción desaparece.

sotb

«Shadow of the Beast - demo menu» [1992 | preview 1994] Pouet!

Scroller fluido | scroll de N caracteres hacia la derecha + desplazamiento de 1 píxel

sotb

Otra vez haciendo trampas: renderizar lentamente unos datos de pantalla en basic y guardar luego 16KB de VRAM; más tarde: cargarlos y hacer HW-scroll.

16KB de VRAM pre-renderizada = 900 caracteres | scroll de N caracteres hacia la derecha - ancho del tile = ~6c.

waves

«Waves» [1992 | preview 1994]

2002-2004 DEULIGNES

«Deulignes» significa «2 líneas de basic». PhenixInformatique organizó varias competiciones amistosas sobre «deulignes» en 2002, 2004 y 2008. Mi método fue: la mayor parte del código fuente para renderizar la pantalla (¡probablemente 15-30 min. de renderizado! ya que el basic es lentísimo), y luego HW-scroller para hacer que la animación entre en bucle. Más tarde CPCrulez los reempaquetó: 16KB de vram cargados de una sola vez. Descarga en CPC-power.

4KB de VRAM pre-renderizada | scroll de 2 caracteres hacia la derecha

worms

«Worms» [2002 | repack 2010 por cpcrulez]

4KB de VRAM pre-renderizada | scroll de 4 caracteres hacia la derecha

plasmegg

«Plasmegg» [2002 | repack 2010 por cpcrulez]

16KB de VRAM pre-renderizada = 1024 caracteres = 40 (ancho) * 25 (alto) +24 | scroll de 2 caracteres hacia arriba

barfive

«Barfive» [2004 | repack 2010 por cpcrulez]

​2004-2011 EL TRABAJO INTERNO

¡Por fin una animación genérica con tiles cuadrados!

16KB de VRAM pre-renderizada = 1024 caracteres = 64 * tiles | tile cuadrado = 4 (ancho) * 4 (alto)

tiles

«0%-cpu animation» [2004 inédita - preview+gfx 2010 por cpcrulez] Descarga en CPC-power.

Pasar de 16KB a 64KB (para multiplicar por 4 el nº de frames de animación) habría sido tarea fácil. Pero… mira esa pantalla: ¿tiles cuadrados estáticos? ¿usando solo números enteros? Esto me hizo preguntarme cómo se podría mejorar. Le di vueltas unos días hasta que encontré unos principios que me traían de cabeza: ¿un número de capas virtualmente ilimitado? ¿¿¿tiles de tamaño no entero con scroll sub-píxel??? ¡Tenía que probar la idea! Esto fue en 2006.

64KB de VRAM pre-renderizada | 6 capas | solo ploteo y trazado de líneas

[2006 inédita - preview furtiva en la Croco Chanel party 2007]

2006.dskDSK · 170 KB

Cálculos y cifras

tess xlsm

Suministrado sin ningún comentario (¡intenta averiguar por tu cuenta lo que está pasando!):

tess.xlsmXLSM · 105 KB

Esta prueba de concepto estaba bien, pero desde luego no estaba lista para publicarse: la idea principal era plotear (y luego: trazar una línea) en cada frame desde una capa de animación. Era lentísimo… La mejora que se me ocurrió fue: dibujar de una sola vez una forma rellena. Y eso programé.

64KB de VRAM pre-renderizada | 4 capas | dibujando una forma rellena

[2008 inédita]

2008.dskDSK · 170 KB

¡Otra decepción más! Parecía que la 3D era la única manera de conseguir una buena sensación de capas. Parecía también que el dithering era otro requisito para hacer trampas con la restricción de 4 colores. ¡Diablos! ¿esto significaba que tenía que conseguir mi propio motor 3D? ¡Menudo asunto! ya que me veo más como hardware-coder que como software coder. ¡En fin! me llevó unos cuantos años alcanzar el objetivo: ¡motor 3D listo! listo para mezclarse con el HW-scroll… unos años más tarde.

Hommage Face Hugger [2011 inédita]

2011.dskDSK · 170 KB

​2014-2016 EL TRABAJO OCULTO EN «Logon’s run»

En realidad conseguí los 3 FX principales bastante rápido: unos meses en 2014. Esto significaba: poder dibujar objetos 3D y colocarlos como sprite en cualquier parte de los 64KB de VRAM. Los objetos pequeños incluso podían generarse en pantalla en un solo frame, lo que permite hacerlos aparecer mientras se hace scroll de la pantalla.

doyousea

«Doyousea» [2014 | publicada en 2017 en «Logon’s run - 3D meets the aging bits»]

dragonfly

«Dragonfly» [2014 | publicada en 2017 en «Logon’s run - 3D meets the aging bits»]

FX impresionantes en pantalla. Pero… ¡se tarda una eternidad en dibujar tantos objetos 3D en varias capas dentro de 64KB de VRAM! La idea básica en aquel momento era: bueno, tengamos una especie de gfx procedural, mostremos parcialmente lo que se va poniendo en pantalla.

Preview temprana: renderizando directamente en pantalla. ¡Qué vergüenza!

Título provisional «Crafted» [2014 | preview en la Reset party 2015]

2014.dskDSK · 170 KB

Así que decidí esconder los gfx mientras se generan. Lo hice por 3 medios. En primer lugar: cuando es posible, ocultar bitplanes para mostrar solo un fondo o unos FX de 1 color. ¿Y luego? ¡vuelta a animaciones mucho más simples en 16KB! olvidarse de la 3D, usar solo sprites: esto se genera rápido y deja más tiempo para los FX pesados. Por último, pero no menos importante: usar una tarea de fondo para generar la animación, mientras se muestra otra cosa en primer plano (por ejemplo: el gfx procedural de un logo).

«BITPLANE» OCULTO

Mode 1 = 4 colores = hacen falta 2 bits. El CPC no tiene bit-planes. Dicho esto: un uso hábil de las «inks» permite ocultar 1 bit de cada 2. Esto permite renderizar un gfx de 1 color mientras otro gfx se muestra en pantalla a través del otro bit.

Nota: las siguientes capturas (las de la izquierda) están tomadas de este DSK:

2017.dskDSK · 190 KB

Renderizando cubos de tamaño medio… pero no se ven

tess bitplane1

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess bitplane1 ok

[«Logon’s run - 3D meets the aging bits» - build final]

Renderizando cubos de tamaño XL… pero no se ven

tess bitplane2

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess bitplane2 ok

[«Logon’s run - 3D meets the aging bits» - build final]

Renderizando el fondo de «Doyousea» (gris - abajo)… pero no se ve

tess bitplane3

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess bitplane3 ok

[«Logon’s run - 3D meets the aging bits» - build final]

ANIMACIÓN EN 16KB

16KB de VRAM | fx solo con sprites | durante el renderizado de «Dragonfly» 1/2

lite1

16KB de VRAM | fx solo con sprites | durante el renderizado de «Dragonfly» 2/2

lite2

El mismo fondo para 3 animaciones | 1) altura del tile = 2 caracteres

deulignes4

El mismo fondo para 3 animaciones | 2) altura del tile = 4 caracteres - scroll de 2*4 caracteres

deulignes3

El mismo fondo para 3 animaciones | 3) altura del tile = 6 caracteres

deulignes2

TAREA DE FONDO

Mostrar «algo» mientras se generan los datos (principalmente 64KB/16KB de VRAM).

Renderizando «Doyousea» como tarea de fondo, mientras en primer plano se muestra: el gfx procedural del logo de «Logon System»

tess background1

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess background1 ok

[«Logon’s run - 3D meets the aging bits» - build final]

Renderizando «Dragonfly» como tarea de fondo, mientras en primer plano se muestra: sprites fantasma

tess background2

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess background2 ok

[«Logon’s run - 3D meets the aging bits» - build final]

Renderizando «Gears» como tarea de fondo, mientras en primer plano se muestra: «Dragonfly» y luego las plataformas de «Donkey Kong»

tess background3a

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess background3a ok

[«Logon’s run - 3D meets the aging bits» - build final]

Renderizando «Rubberbar» como tarea de fondo, mientras en primer plano se muestra: «Mario»

tess background3b

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess background3b ok

[«Logon’s run - 3D meets the aging bits» - build final]

Generando los 64KB de datos de «Gameboy» como tarea de fondo, mientras en primer plano se muestra: «Rubberbar»

tess background4

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess background4 ok

[«Logon’s run - 3D meets the aging bits» - build final]

BONUS: ¡SPLITRASTERS!

Cambiar el puntero a la VRAM: ¡desde luego no cuesta mucha cpu! ¿Y entonces? tiempo de sobra para añadir algunos «split-rasters» (es decir, cambiar una sola «ink» hasta 12 veces en una única scanline).

La forma fácil: un gran sprite chunky - o más adelante: algo de texto

tess rasters0a

tess rasters0b

La forma insólita: esconder cubos abajo y hacerlos caer desde arriba

tess rasters1_1

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess rasters1 ok_1

[«Logon’s run - 3D meets the aging bits» - build final]

La forma difícil: aplicar muchos colores sobre una forma 3D de un solo color

tess rasters2

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess rasters2 ok

[«Logon’s run - 3D meets the aging bits» - build final]

…PERO ¿QUÉ PASA CON LA GAMEBOY?

La escena de la Gameboy es otra historia. Tampoco está hecha con hardware-scroll y no encaja aquí como un Elder’s scroller. Dicho esto, este artículo parece un making-of, ¿verdad? Puede que sea el sitio adecuado para más texto y menos GIF animados.

Batman me mató. La escena de la Gameboy se inspira en aquel Batlogo demoledor. Usa el mismo algoritmo de base. Evidente: la animación está empaquetada. Para cada frame empaquetado, los datos almacenados son: de la scanline #n a la scanline #(n+1), qué bytes hay que cambiar. Dicho de otro modo: delta-packing considerando que la pantalla usa una sola scanline repetida que se actualiza siguiendo al haz. Partiendo de esto, la escena de la Gameboy introduce algunas otras características.

Se repiten dos scanlines. Puede aparecer un glitch si hay demasiados bytes que actualizar en una sola scanline. Si un byte se actualiza demasiado tarde, es decir, una vez que el haz ha pasado por ese byte: el byte se muestra en su estado anterior. Para evitarlo: no usar una sola scanline sino dos. La scanline par se actualiza mientras se muestra la scanline impar; y viceversa.

gb

A la izquierda: las dos scanlines repetidas se actualizan durante el frame, cada vez con muy pocos bytes, a la derecha

Dithering. Como se usan dos scanlines, añadir algo de dithering fue tarea fácil.

de bytes actualizados en una scanline. En esa animación de Gameboy: se actualizan hasta 14 bytes en una sola scanline. El tiempo de cpu necesario es 14*6 = 84μs. Que es más que 64μs = el ancho de la scanline tal y como la ve el haz. Esto provocaría algunos glitches (ver punto a) con un esquema de una sola scanline. Esto no importa con el esquema de doble scanline. ¿Hacen falta +20μs más en una scanline dada? sin problema: las siguientes scanlines, con menos bytes, permitirán compensar ese retraso.

Fondo. Se muestran algunos cuadrados en el fondo. Esto se consigue con rasters (es decir, cambiando el color) aplicados en (3) columnas.

de colores. Se muestran 7 en lugar de 4. Ver el punto anterior para los cambios de color en el fondo. Hay también otro cambio de color (no siempre a la misma y) en la propia Gameboy: la pantalla y los botones comparten la misma ink.

Cuadrados de fondo y más colores gracias al cambio de ink siguiendo al haz.

tess rasters3

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess rasters3 ok

[«Logon’s run - 3D meets the aging bits» - build final]

Scroll hacia abajo en el eje y. Esto requiere que la rutina de depacking se detenga antes de que terminen los datos del frame actual.

Scroll hacia arriba en el eje y. Esto requiere que una rutina de pre-depacking se ejecute antes del bucle-principal-que-sigue-al-haz.

Sin pantalla en negro antes/después. El fx cambia desde/hacia un esquema de pantalla estándar en el que la Gameboy se ha pre-renderizado usando los mismos datos que el fx.

Sin carga de datos. Pero: 25 segundos de tarea de fondo para renderizar y empaquetar 64KB de datos.

Fondo/primer plano. Las columnas de cuadrados parecen pasar por delante del objeto que gira. Esto se consigue modificando los datos generados previamente.

ESCENA FINAL

Escena fácil que no costó mucho tiempo programar.

En cada frame, cambiar solo los poquísimos bytes que han cambiado (borrar en la parte de arriba de los corazones, poner píxeles en la de abajo).

Después, usando una tabla de máscara inversa: al poner los píxeles de los corazones, los píxeles del fondo tienen prioridad.

Por cada corazón que hace scroll: borrar 7 bytes, poner 7 bytes rojos nuevos.

tess 8bit

[«Logon’s run - 3D meets the aging bits» - build especial «hidden work»]

tess 8bit ok

[«Logon’s run - 3D meets the aging bits» - build final]