1 puntos por GN⁺ 4 시간 전 | 1 comentarios | Compartir por WhatsApp
  • Catlantean 3D es un proyecto paralelo que busca crear un FPS completo con las limitaciones típicas de los juegos de PC de inicios de los 90, apuntando a renderizado basado en una resolución de 320x240 y una paleta de 256 colores
  • Como el renderizador solo maneja índices de paleta, precalcula un colormap de 32 niveles para oscurecimiento por distancia, y en ejecución elige colores más oscuros con búsquedas O(1)
  • La creación de assets se divide entre sprites prerenderizados con Blender, sprites y texturas dibujados a mano, y texturas procedurales generadas con scripts en Python
  • El HUD dibujado a mano y las reglas de escala por píxel son restricciones clave para mantener nitidez y legibilidad en baja resolución, ajustando 1 unidad del mundo a 64 píxeles
  • En lugar de usar Tiled, desarrolla su propio editor de mapas y planea ofrecer ese mismo editor a los jugadores después del lanzamiento; además, el código fuente del juego se publicará como open source en GitHub

Objetivos y restricciones del proyecto

  • Catlantean 3D es un proyecto paralelo desarrollado lentamente en el tiempo libre desde hace más de un año, con objetivo de lanzarse en Steam al año siguiente
  • La meta es crear un FPS completo y publicable usando técnicas comunes de principios de los años 90
  • Se permiten compiladores modernos y capas de abstracción de plataforma, pero esta capa se limita a un framebuffer donde escribir píxeles, entrada de teclado y mouse, un búfer de audio donde escribir samples, y E/S del sistema de archivos
  • El juego, incluidos todos los assets, debe hacerse completamente desde cero, y tanto el renderizado como la mezcla de sonido deben implementarse manualmente
  • La resolución objetivo es 320x240, y cada píxel de la pantalla solo puede usar uno de 256 colores
  • La lógica del juego usa punto fijo para garantizar comportamiento determinista, mientras que el renderizado usa punto flotante porque la determinación ahí importa menos
  • El resultado debe ser un juego completo y divertido de jugar, no solo un demo técnico, y no utiliza contenido generado por IA
  • Todo lo mostrado está en desarrollo y puede cambiar bastante

Renderizado con paleta

  • Gráficos VGA

    • El Mode 13h del hardware VGA era un modo gráfico de 320x200 y 256 colores, famoso por definir toda una generación de juegos de PC
    • Desde la perspectiva del programador, Mode 13h ofrecía un framebuffer lineal donde cada píxel se representaba con 1 byte, como índice dentro de una paleta de 256 colores
    • Para dibujar un píxel, bastaba escribir 1 byte en una dirección específica, sin necesidad de lidiar con conceptos como shaders o VRAM
    • Los assets modernos pueden usar millones de colores, pero con la limitación de 256 colores cada elección debe ser cuidadosa e intencional
    • Juegos como Doom y Duke Nukem se presentan como ejemplos donde las limitaciones técnicas terminaron dándoles nitidez y claridad visual
    • Catlantean 3D busca recrear esa sensación, pero elige 320x240, más cercano a VGA Mode-X, en lugar de 320x200
    • Mostrar 320x200 en una pantalla 4:3 produce píxeles no cuadrados; aunque eso sería más auténtico, se evita por preferencia
  • Paleta

    • La paleta empieza en 768 bytes y fue elegida tras mucho ensayo, error e iteración
    • La paleta reserva un rosa brillante para transparencia, un blanco puro y un negro puro
    • Se necesitaban muchos tonos rojos para representar sangre, y también gamas verdes y azules para llaves roja, verde y azul, y puertas diferenciadas por color
    • Como el escenario es Catlantis, una tierra de parodia parecida al antiguo Egipto por el culto a los gatos, hacían falta muchos tonos desérticos de amarillo y marrón
    • Dado que Catlantis está ocupada por humanos-perro cibernéticos, también se necesitaban muchos grises para representar instalaciones tecnológicas
    • Para romper la monotonía del gris y usar alternativas más cálidas al oscurecer, también se incluyeron tonos beige
    • El resto de los colores se fue completando según surgían necesidades durante la producción de texturas, basándose en el criterio subjetivo de que “se veía bien”
    • La paleta no se terminó de una sola vez, sino que siguió ajustándose durante la creación de assets, las pruebas y la iteración

colormap e iluminación

  • Estructura del raycaster

    • Catlantean 3D es un raycaster tradicional, y el mapa está compuesto por tiles todos del mismo tamaño
    • Algunos tiles son muros, mientras que otros son espacios vacíos con solo piso y techo
    • El renderizador usa el algoritmo DDA en cada columna de la pantalla para recorrer el tilemap y encontrar dónde choca con la geometría del mapa
    • Según la posición de impacto, dibuja en pantalla una columna de pared muestreada desde la coordenada de textura adecuada
    • El piso y el techo se renderizan después con scanlines horizontales para llenar el resto de la pantalla
    • Si el mundo del juego se renderiza solo con la paleta, la imagen se ve plana y poco impactante
    • Al reducir la luz con la distancia al jugador y hacer que una cara del tile del mapa sea un poco más oscura que la otra, aparece una mayor sensación de profundidad
  • Oscurecimiento basado en paleta

    • En renderizadores modernos acelerados por hardware, es fácil oscurecer en shaders multiplicando un vector de color por un coeficiente de punto flotante según la distancia del vértice
    • Un renderizador con paleta no maneja colores como tal, sino solo índices de paleta, así que para encontrar una versión más oscura de un color específico hay que recorrer toda la paleta
    • Como recorrer los 256 colores por cada píxel renderizado sería demasiado lento, se prepara una búsqueda rápida mediante precálculo antes de la ejecución
    • Si la paleta se organiza en una fila y se eligen 32 niveles de sombreado, entonces cada color necesita 31 variantes más oscuras además del original
    • Se calcula el color objetivo más oscuro a partir del valor RGB de cada color y el índice de sombreado, pero ese color puede no existir realmente en la paleta
    • Para construir el colormap, se recorre la paleta buscando el color más cercano al color objetivo
    • Al principio se usó distancia euclidiana, pero muchos colores tendían a arrastrarse hacia el gris y los tonos oscuros se veían fríos y apagados
    • Después se convirtieron los colores al espacio Oklab y se aplicó una fórmula de distancia perceptual más cercana a cómo los humanos perciben la diferencia entre colores
    • También se aplicó hue shifting, un concepto de pixel art que desplaza ligeramente los colores hacia tonos más cálidos conforme se oscurecen
    • El colormap es una matriz bidimensional de índices de paleta que representa el sombreado de cada color; aun así, como solo puede usar colores de la paleta, los gradientes no son perfectos
  • Reducción del costo en ejecución

    • Una vez definido el índice de fila del colormap según la distancia, se toma el elemento N de esa fila para obtener el índice de paleta del color N ya oscurecido
    • Este método resuelve la selección del color más oscuro en tiempo de ejecución mediante una búsqueda O(1)
    • En el renderizado de muros, como la columna es completamente vertical y todos sus píxeles están a la misma distancia de la cámara, el índice de fila del colormap se calcula solo una vez por columna de pantalla
    • En el renderizado del piso, como todos los píxeles de una fila horizontal están a la misma distancia, se calcula solo una vez por fila de pantalla
    • Los sprites son billboards planos cuyos píxeles comparten la misma distancia a la cámara, así que se calcula solo una vez por sprite visible
    • Basta con calcularlo 320 veces para muros, hasta 240 veces para el piso, y una vez por cada sprite visible; además, el raycasting da oclusión gratuita
    • Doom y varios otros juegos también usaban un enfoque parecido

Forma de crear los assets

  • Tres categorías de assets

    • Las texturas y sprites de Catlantean 3D se dividen en tres categorías
    • La primera consiste en sprites prerenderizados a partir de modelos 3D hechos en Blender
    • La segunda consiste en sprites y texturas dibujados a mano
    • La tercera consiste en texturas procedurales generadas con scripts especiales en Python que combinan arte dibujado a mano
  • Sprites prerenderizados

    • Los sprites animados complejos requieren modificar muchos frames, así que iterar manualmente es difícil y consume mucho tiempo
    • Una forma más eficiente es crear un modelo 3D en Blender, hacer el rigging y la animación, y luego usar un script con la API de Python de Blender para renderizar múltiples texturas
    • Las modificaciones se hacen sobre el modelo y el script de renderizado se encarga del resto, reduciendo mucho el tiempo de iteración
    • La principal dificultad fue que los sprites renderizados salían demasiado borrosos y con colores deslavados
    • Renderizar a alta resolución y luego reducir con filtrado dio resultados mixtos, porque los detalles se perdían en el filtrado y se borraba la nitidez de los bordes
    • El método más efectivo y reutilizable fue usar la composición de Blender para obtener el contraste y la nitidez adecuados
    • Una vez lista la imagen, un script especial en Python hace la cuantización a la paleta y genera imágenes de 1 byte por píxel para el motor
    • Para cada píxel de la imagen fuente, el script encuentra el color de la paleta perceptualmente más cercano según Oklab y usa su índice como valor del píxel
    • El arreglo de índices y la información de tamaño se empaquetan en un formato simple TEX que usa el juego
    • Los sprites de enemigos pueden tener varias animaciones, y cada animación necesita frames para las 8 direcciones hacia las que puede mirar el sprite
    • El script en Python rota el sprite para cada animación, renderiza todos los frames y luego vuelve a rotarlo, repitiendo el proceso
    • Los nombres de archivo de los sprites siguen una convención que indica nombre del sprite, nombre de la acción, dirección e índice de frame
    • Los sprites renderizados no se guardan en el repositorio y están en .gitignore; en otras máquinas, el script de compilación renderiza todos los modelos para generarlos
    • En una RTX 3070, procesar unos 15 modelos toma alrededor de 10 segundos
  • Sprites y texturas dibujados a mano

    • Al inicio del desarrollo se creó en Blender una cabeza con forma de gato, texturizada con el gato Vilko, para usarla como cara de la barra de estado
    • El resultado parecía flojo y de poco esfuerzo, no transmitía bien las emociones y fue una de las primeras cosas que la gente señaló al comentar el tono del juego
    • Se concluyó que algunos elementos tenían que dibujarse a mano, y que una versión animada dibujada manualmente funcionaba mucho mejor
    • Por el tamaño de los sprites, cada píxel debe ser intencional, y no hay margen para dejar eso al renderizador de Blender
    • La misma lógica aplicó a la mayoría de los ítems recolectables, porque los resultados prerenderizados anteriores no daban consistentemente buenos resultados a escala pequeña con el compositor de Blender
    • Tras intervención manual, la nitidez y legibilidad de los ítems recolectables mejoró mucho
    • Subir simplemente la resolución de los sprites permitiría que el rasterizador del juego los escalara, pero como la escala de píxel dejaría de ser consistente, el resultado no sería bueno
    • Inconscientemente se espera que, al moverse hacia delante o hacia atrás en una misma fila o columna visual, el tamaño de los píxeles se mantenga; si cada sprite tiene una escala de píxel distinta, se ve raro
    • En Catlantean 3D, 1 unidad del mundo equivale a 64 píxeles, y todos los sprites se crean conforme a esa escala
    • Un sprite con altura de un cuarto de unidad del mundo debe medir 64/4=16 píxeles de alto

HUD y pipeline de generación procedural

  • HUD

    • El HUD y casi todos sus componentes se colocan y dibujan a mano
    • La barra de estado inferior, varios paneles y pantallas de transición, y las fuentes entran en la categoría de HUD dibujado a mano
    • En lugar de pintar directamente todos los elementos, se usa mucho composición y efectos de capa de Affinity Photo
    • Entre los efectos usados están emboss para dar sensación 3D a superficies planas, generación y superposición de ruido para una textura rugosa, overlays de color, modos de fusión y efectos glow
    • Como los elementos del HUD se revisan con frecuencia, también importa mucho la facilidad para reacomodarlos gracias al trabajo por capas
    • En general se trabaja primero en truecolor en Affinity Photo, y muchos elementos son simplemente rectángulos de color sólido con efectos especiales y blending aplicados encima
    • Las imágenes exportadas desde Affinity Photo incluían artefactos raros que parecían relacionados con el antialiasing, y no fue posible desactivarlos de forma confiable
    • Como no era adecuado para trabajo pixel-perfect, en Aseprite se hacían tareas adicionales como texto pixel-perfect, segmentación del arte y repintado de bordes más nítidos
  • Texturas de generación procedural

    • Algunas texturas son lo bastante simples o específicas como para dibujarlas directamente, pero muchas comparten desgaste, polvo y variaciones de detalle superficial sobre un material base
    • Dibujar cada variante a mano sería tedioso e inconsistente, así que se generan con scripts en Python
    • El pipeline de generación recibe como entrada un heightmap que define el relieve de la superficie, un noise map para variaciones, un grime map para suciedad y desgaste, dos colores base y un brightmap
    • El heightmap en realidad se usa para generar un normal map, y el normal map se usa para hornear iluminación y sombras simples
    • El brightmap especifica qué partes deben conservar su color sin importar el resto de los parámetros
    • El script genera la textura final y también hace la cuantización a la paleta para dejarla lista para usarse directamente en el motor
    • Modificar una textura pasa a ser cuestión de ajustar parámetros en vez de repintar píxeles, lo que ahorra mucho tiempo al trabajar en solitario

Gibs y efectos prerenderizados

  • Gibs

    • El gibbing suele ocurrir cuando a un enemigo se le aplica daño excesivo, como un disparo de escopeta a quemarropa o una explosión
    • Para transmitir el impacto del daño fuerte, se usa una animación en la que el enemigo explota en pedazos ensangrentados
    • Este pipeline está dirigido por un script en Python que toma sprites, una paleta y un conjunto de parámetros para generar los frames de animación que irán a los datos del juego
    • En la primera etapa, Voronoi decomposition, se eligen aleatoriamente K píxeles semilla dentro del cuerpo opaco del sprite y se asigna cada píxel al seed más cercano
    • Cada celda resultante se convierte en un fragmento volador
    • En la segunda etapa, wound bleeding, los píxeles de borde adyacentes a fragmentos distintos se marcan como heridas de profundidad 0, y un BFS se propaga hacia adentro asignando valores de profundidad
    • Al renderizar, los píxeles cercanos al borde se mezclan hacia colores de sangre tomados de una rampa derivada de la paleta del juego, y cuanto más adentro del fragmento, más se conserva el color original del sprite
    • La selección de la rampa de paleta está parametrizada, por lo que ciertos enemigos también pueden usar “sangre” verde o azul
    • En la tercera etapa, physics, a cada fragmento se le asignan un centro, una velocidad de dispersión aleatoria hacia afuera desde el centro del sprite, velocidad angular, gravedad y drag
    • No hay detección de colisiones, pero los fragmentos se detienen al chocar con el suelo; es tosco, pero suficiente
    • La cantidad de fragmentos, fuerza de explosión, gravedad, drag, dispersión y profundidad de la herida pueden ajustarse mediante parámetros
    • Hace falta algo de prueba y error para encontrar seeds que se vean bien, pero sigue siendo más rápido que dibujar la animación a mano
    • La misma técnica se usa también en objetos destructibles del entorno, como macetas, barriles y cajas
    • Igual que las animaciones prerenderizadas, los resultados de gibs no se guardan en el repositorio: se regeneran después del checkout y el tiempo de ejecución es despreciable
  • Sistema de partículas prerenderizado

    • La mayoría de los efectos de partículas se dibujan a mano en Aseprite, pero algunos se generan y se hornean de la misma forma que los gibs
    • Un script en Python ejecuta la simulación para producir una secuencia de frames en PNG, que luego se cuantiza a TEX
    • No existe un sistema de partículas en runtime; todos los efectos se hornean de antemano para que el rasterizador por software pueda renderizarlos lo más rápido posible
    • Aquí la palabra “particle” es algo engañosa, porque en realidad no se están simulando partículas individuales
    • Cada frame calcula un radial energy field por píxel y se compone sumando varias capas independientes
    • El core es un disco suave que se expande hacia afuera durante la animación
    • Los rays son destellos puntiagudos alrededor del core; se puede ajustar su sharpness y length, y cada ray lleva una variación aleatoria de longitud basada en RNG para que se vea irregular
    • El ring es una shockwave expansiva opcional, y el noise multiplica la energía total por value noise para volver la forma menos limpia y más irregular
    • La energía acumulada por píxel se cuantiza según la rampa de paleta indicada en los parámetros del script
    • Por el diseño de la paleta, cada fila se trata como un gradiente de claro a oscuro, así que se puede oscurecer un píxel usando solo aritmética de índices de paleta, sin blending ni cálculo alfa
    • Por encima de cierto umbral, el píxel se empuja hacia el blanco para dar la impresión de un núcleo white-hot
    • Opcionalmente se pueden dispersar pequeños sparkles encima; estas formas en cruz se mueven hacia afuera y se desvanecen durante su propia vida útil
    • La animación soporta un modo one-shot, donde crece y desaparece como una explosion o un teleport flash, y un modo loop donde el primer y último frame coinciden para que el ciclo no tenga corte
    • El modo loop es útil para efectos persistentes y repetitivos como plasma bolts y energy projectiles

Editor de mapas y ecosistema de herramientas

  • La edición de mapas empezó inicialmente en Tiled; en general era una herramienta razonable, pero le faltaban funciones específicas que el juego necesitaba
  • Tiled no tenía light level painting por celda, cell flags ni una noción de propiedades propias del juego, así que al principio se improvisó abusando de object properties
  • También se necesitaba un script en Python para convertir la salida JSON de Tiled al formato binario usado por el motor, lo que añadía otro componente para compensar el desajuste entre la herramienta y los requisitos del juego
  • Si para crear mapas los jugadores tuvieran que instalar Tiled, aprender su interfaz y configurar el script de conversión, la carga sería tan alta que prácticamente anularía la posibilidad de que el editor se use de verdad
  • El editor propio soporta de forma nativa light level painting, cell flags y todos los tipos de entidades y propiedades que el juego conoce
  • Cuando el juego se lance, los jugadores también recibirán el mismo editor usado durante el desarrollo
  • El editor es plug and play y permite ejecutar niveles directamente desde ahí
  • Se reconoce que los íconos de la toolbar son horribles, y precisamente por eso se quedan así
  • El editor está hecho con wxPython, que encajó mejor que tkinter en widgets, event handling y layout
  • El resultado con wxPython se veía más nativo y permitió iterar más rápido
  • La estructura centrada en el patrón MVP separa limpiamente la lógica de UI y los datos del mapa, algo importante porque el formato del mapa todavía no está estabilizado y ambos lados cambian con frecuencia
  • No todas las partes del editor están escritas en Python; buena parte del model depende de la librería pybast
  • pybast son bindings internos del motor para Python mediante pybind, y proporcionan lectura del game data archive, lectura de texturas del juego, una clase de fixed point para coordenadas de entidades y serialization
  • Esta elección evita reimplementar en Python funciones ya hechas en C++, y hace que el motor y las herramientas formen un ecosistema pequeño y estrechamente integrado

Plan de lanzamiento y forma de publicación

  • Catlantean 3D se espera para el primer trimestre de 2027
  • El enfoque actual está en diseño de niveles, añadir enemigos y armas, y trabajo continuo de polish
  • El precio objetivo está en el rango de 5 a 8 dólares
  • El código fuente del juego se publicará como open source en GitHub
  • El data archive real, con gráficos, niveles, sonido y música, requerirá comprar el juego
  • La transparencia del proceso se considera uno de los pocos factores que construyen confianza de manera sostenida
  • A diferencia de los AAA, los juegos indie dependen de una audiencia más pequeña, pero esa audiencia suele estar más dispuesta a seguir el proyecto, apoyarlo y difundirlo
  • Mostrar el proceso de trabajo se presenta como la forma más honesta de demostrar que realmente importa lo que se está creando

1 comentarios

 
GN⁺ 4 시간 전
Comentarios de Hacker News
  • Si quieres jugar con el renderizado por software, hay un ejemplo casi con el código más corto posible para subir eficientemente a la pantalla, en todas las plataformas, un arreglo 2D ARGB8888 en memoria principal usando SDL2 y C: https://gist.github.com/CoryBloyd/6725bb78323bb1157ff8d4175d...
    La conversión de un framebuffer con paleta de 320x200x8 bits a ARGB la tienes que hacer tú mismo ;)
    Si quieres inspirarte sobre lo que se puede hacer con un framebuffer con paleta, prueba hacer clic en Show Options en http://www.effectgames.com/demos/canvascycle/ o mira la charla GDC de ese artista https://youtu.be/aMcJ1Jvtef0
    Después, si quieres una vibra clásica de Deluxe Paint IIe, puedes abrir https://github.com/mriale/PyDPainter, o si quieres una herramienta más moderna, https://www.aseprite.org/

    • Al menos en SDL3, ya no hacen falta ni renderer ni textura. Puedes obtener una superficie con SDL_GetWindowSurface y mostrarla con SDL_UpdateWindowSurface
      Por lo que entiendo de la biblioteca, esta es la forma más cercana al gráfico por software, y SDL igual sigue encargándose del doble búfer

    • Definitivamente es la forma más básica. Si quieres una optimización pequeña en el bucle interno, puedes precalcular el offset de la scanline antes de entrar al bucle de píxeles:
      int s = y*screenRect.w;

      for (int x = 0; x < screenRect.w; x++) {
      pixels[s + x] = argb(255, frame>>3, y+frame, x+frame);
      }

    • Gracias por compartirlo. Ya hay varios forks populares de Quake, pero Planimeter distribuye el fork Quake-VS2026, que no mete cambios
      El equipo está trabajando en una compilación x64 y para eso tiene que reemplazar la antigua SciTech Multi-platform Graphics Library (solo x86) por SDL3. La otra opción sería portar scitech-mgl a x64, pero eso parece poco probable y, hasta donde supe por última vez, el renderizador por software incluso podía quedar fuera
      Pero quizá pueda conservarse usando el renderizador por software y SDL_Texture

  • Este artículo toma mucha inspiración de Doom, pero el verdadero motor de raycasting se parece más a los predecesores de Doom, especialmente al más famoso de ellos, Wolfenstein 3D
    Usa paredes verticales y alturas constantes para piso y techo. Wolf3D no tenía pisos ni techos con textura por razones de rendimiento, pero otros juegos parecidos sí los tenían
    Si lo recuerdo bien, tanto Doom como Duke Nukem usaban motores BSP mucho más flexibles, donde las paredes podían cruzarse en ángulos arbitrarios y las alturas de piso y techo podían variar. Aun así, los niveles seguían siendo “planos”, así que no podías crear varios pisos dentro de un mismo nivel; por ejemplo, no podías diseñar un puente por el que se pudiera pasar tanto arriba como abajo

    • El motor Build no usaba BSP. Trataba las conexiones entre sectores como portales y hacía clipping contra esos portales mientras rasterizaba las paredes como trapecios rotados 90 grados
      Gracias a eso podía tener geometría dinámica de paredes, como trenes en movimiento o luces giratorias, y también configuraciones de “habitación sobre habitación”, siempre que no pudieras ver dos habitaciones al mismo tiempo
      En Blood y Shadow Warrior creaban sectores con la misma forma y usaban el piso de un sector como si fuera un portal hacia el techo del otro, como solución alternativa para crear espacios más cercanos al “3D”. No era una función soportada originalmente por el motor, pero era lo bastante flexible como para que estudios sin acceso al código fuente lo lograran por su cuenta
      El primer nivel de Duke Nukem 3D también usa algunos trucos de Build. Por ejemplo, los sprites podían no rotar con la cámara, quedarse alineados a los ejes y además tener colisión, así que si tratabas cada sprite como un rectángulo alineado a ejes, podías crear geometría 3D básica. En el primer nivel se usa para hacer el puente entre dos edificios justo antes del botón de salida

    • Blake Stone y Rise of the Triad usaban una versión posterior del motor de Wolf3D y tenían pisos y techos con textura
      El motor Build de Duke Nukem no usaba BSP

      https://www.jonof.id.au/forum/topic-137.html#msg1548

    • Creo que más adelante en Shadow Warrior también se podía hacer algo así. Recuerdo que estaba implementado con portales y que era bastante doloroso de configurar en el editor

    • En cuanto a los pisos, hasta donde sé, ni siquiera DOOM los manejaba de forma exacta. En paredes verticales, para una porción concreta de pared solo necesitas hacer la división de perspectiva una vez por cada columna de píxeles
      Con los pisos, por desgracia, no tienes ese lujo y, si no recuerdo mal, DOOM dividía el piso en parches, calculaba la perspectiva correcta solo en las esquinas y luego interpolaba en medio

    • Al principio pensé que era solo un Wolfenstein 3D con cambio de skin. Habría sido un juicio muy injusto; aquí realmente hubo muchísimo trabajo

  • Es un gran texto. En particular, fue interesante el enfoque para crear la animación de gib
    Era una demo técnica, pero hacia mediados de los 90 yo también hice algo parecido. Un detalle que no apareció en este texto es que usábamos lightmaps de 8x8 o 16x16 sobre las texturas para crear fácilmente efectos como antorchas parpadeantes o cohetes que volaban por un pasillo iluminándolo. Si querías, también podías usar lightmaps para “hornear” la iluminación
    Como el lightmap era de “apenas” 8x8, se podía soportar la matemática de calcular, para cada luxel, es decir, cada unidad del lightmap, la distancia hasta la fuente de luz y la línea de visión para obtener un valor de brillo. Al renderizar la textura, el luxel se usaba junto con una tabla de consulta para determinar el color real del píxel a dibujar
    Por rendimiento, si mal no recuerdo, los lightmaps se actualizaban 15 veces por segundo. Gracias a DJGPP, usábamos ensamblador en línea para el renderizado y, como en esa época las operaciones de punto flotante eran lentas, usábamos aritmética de punto fijo, que se optimizaba bien. Para los estándares de las computadoras de entonces, el rendimiento de renderizado era sorprendente

    • La idea de punto fijo se usa demasiado poco y está infravalorada. De verdad hay muchos ámbitos donde es una mejor opción, y a veces incluso da más rendimiento
  • La programación gráfica de principios y mediados de los 90 era bastante divertida. Escribías datos de píxeles en la RAM de video mapeada en memoria y aparecían de inmediato en pantalla
    Bastaba con un puntero a 0xA0000, y no hacía falta nada como una API. La razón del modo VGA de 320×200 con píxeles no cuadrados que se menciona era que el búfer de video tenía 64000 bytes, así que cabía dentro de un segmento de 16 bits y era fácil de direccionar desde código de 16 bits y desde la CPU

    • Siempre me pareció curioso que, aunque la PC tenía una CPU monstruosa comparada con las consolas de la época, le costara implementar scroll suave como el de Mario en la NES de 1985 por su arquitectura gráfica
      Pero gracias a esa debilidad, se podía hacer muchísimo más trabajo por cada píxel de la pantalla, y por eso eran posibles sistemas como este de ray casting o de árboles BSP
      No había procesadores dedicados para sprites ni capas de fondo, pero justamente por eso lo que podía hacer la PC no quedaba encerrado en una arquitectura rígida de funciones fijas
      Cuando aparecieron los procesadores 3D dedicados a mediados y fines de los 90, dejó de ser un problema, pero por un breve momento a principios de los 90 hubo un patio de juegos para el renderizado visual realmente único
    • Bastaba con un puntero a 0xA0000, pero el extender que usaras podía volver esa parte un poco más molesta :-P
      DJGPP y Free Pascal usan el mismo extender go32 de DJ Delorie, que no hace un mapeo lineal completo, así que había que retocar un poco más las cosas para mostrar algo en pantalla
    • Antes de que apareciera VGA, la historia era bastante más complicada
  • Lo más interesante son las herramientas internas. Cosas como el script en Python para crear animaciones de gib, o el otro script en Python para generar spritesheets 2D desde Blender
    El autor original claramente parece ser un ingeniero 10x que además puede hacer buen arte, y creo que eso es realmente raro. Que además tenga una dirección de arte coherente también me pareció bastante sorprendente

    • Visto desde alguien que era fan de este género en los 90, siento que detrás de los grandes éxitos siempre había este tipo de ingenieros renacentistas. Todavía recuerdo algunos nombres, y eran verdaderos artistas
      En los últimos 15 años de la industria del videojuego, fuera del CEO o del director principal, casi no sé el nombre de nadie
  • Recién me di cuenta de que este juego podría ser uno de los pocos shooters con protagonista femenina. La gata tiene pelaje calicó, y esos gatos casi siempre son hembras (https://en.wikipedia.org/wiki/Calico_cat)

    • ¿Son tan raros los shooters con protagonista femenina? Se me ocurren de inmediato varios títulos bastante conocidos: Perfect Dark, Mirror's Edge, Dishonored 1 o 2, Metroid, y todos son una especie de shooter con protagonista femenina
      Claro, si queremos ser 100% precisos, Mirror's Edge es más “en primera persona” que un “shooter”
      Además, entre los “RPG + FPS” también hay muchas obras donde puedes jugar como hombre o mujer
      Parece que el autor también conoce la relación entre el patrón del pelaje y la posible hembra del gato:

      After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)

    • Esto no es un juego de Perfect Dark

    • Hoy en día hay bastantes boomer shooters con protagonista femenina. Por ejemplo, Selaco[0], Supplice[1], The Citadel[2] y su secuela[3], Zortch[4] y su secuela anunciada[5], Nightmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9], entre otros
      De hecho, hoy parece que hay más boomer shooters con protagonista femenina que sin ella :-P Si combinas las etiquetas “boomer shooter” y “female protagonist” en Steam, salen 143 resultados, aunque eso incluye juegos donde puedes elegir el género del personaje o donde normalmente juegas como hombre pero en algunas secciones juegas como mujer

      [0] https://store.steampowered.com/app/1592280/Selaco/

      [1] https://store.steampowered.com/app/1693280/Supplice/

      [2] https://store.steampowered.com/app/1378290/The_Citadel/

      [3] https://store.steampowered.com/app/3371240/Beyond_Citadel/

      [4] https://store.steampowered.com/app/2443360/Zortch/

      [5] https://store.steampowered.com/app/3807500/Zortch_2/

[6] https://store.steampowered.com/app/1051690/Nightmare_Reaper/

[7] [https://store.steampowered.com/app/1785940/COVEN/](<https://store.steampowered.com/app/1785940/COVEN/>;)

[8] [https://store.steampowered.com/app/1406780/Viscerafest/](<https://store.steampowered.com/app/1406780/Viscerafest/>;)

[9] [https://store.steampowered.com/app/1072150/Hedon_Bloodrite/](<https://store.steampowered.com/app/1072150/Hedon_Bloodrite/>;)
  • No creo que haya sido intencional, pero en general eso no me impresiona mucho ni le veo valor. Es lo mismo que en Hollywood cuando muestran a una mujer noqueando a un hombre del doble de su tamaño
    Me parece poco realista, ridículo y dañino

  • Está realmente genial. Otro truco divertido que se usaba en los 90 era la animación de paleta. Solo cambiando la paleta se podían crear efectos increíblemente geniales con un costo de ejecución muy bajo

    • Sí. Para ver un gran ejemplo de esta técnica, recomiendo muchísimo este sitio web

      http://www.effectgames.com/demos/canvascycle/

    • También es divertido cambiar la paleta a mitad del frame. En PC no había algo como el copper de Amiga, así que había que cuidar mucho más el timing, pero aun así se podía

    • Si recuerdo bien, muchos enemigos de Diablo 1 y 2 eran básicamente el mismo sprite con una paleta distinta aplicada. ¿Es el mismo truco?

    • El azul es agua, el morado es plasma, y el rojo/naranja es sangre o lava

  • Me sorprendió mucho lo bien que se ven los sprites cuantizados después de renderizarlos. Gracias a esa conversión rápida se veían muy nítidos

  • Como desarrollador colega que hace motores 3D con restricciones absurdamente severas, me encanta ver los detalles de la explicación y el proceso por el que pasaron

  • Estoy jugueteando con una demo técnica de renderizado de espacio vóxel para homebrew de PlayStation. Incluso después de uno o dos días de trabajo de fin de semana, ya estoy obteniendo resultados decentes, como 10~15 FPS, y todavía no he usado DMA ni GTE, ni siquiera primitivas básicas de polilínea
    Se siente refrescante volver a sacar la trigonometría y los viejos trucos de optimización de bajo nivel. Cuando tu scratch buffer es de 1KiB y solo puedes usar una parte de eso para la pila, te das cuenta de lo lujosos que son los microcontroladores que uso en el trabajo. Ahí cada hilo tiene asignada una pila de 8KiB, y hasta aparecen backtraces con más de 50 funciones plantilla de C++ apiladas