PixelDoomgeon · bitácora

Diez cosas que me salieron mal

Convertir Pixel Dungeon a primera persona fue, sobre todo, descubrir en qué me estaba equivocando.

01 · el motor

Pensé que necesitaba cambiar de motor. No hacía falta.

Todo el mundo asume que Pixel Dungeon usa libGDX, porque las versiones modernas de Shattered sí. Sprouted forkeó una versión muy anterior.

grep -r "com.badlogic.gdx" src/   # cero resultados
grep -r "com.watabou.noosa" src/  # todo el juego

Es Noosa, el motor propio de Watabou, sobre OpenGL ES crudo. Y resultó que su shader ya era una tubería de matrices completa, con posición de cuatro componentes. Faltaba una cámara en perspectiva, vértices con profundidad y un búfer de profundidad. Nada más.

La comprobación útil no era buscar archivos de configuración, era mirar los imports.

02 · la interfaz

La razón real de que esto fuera viable

Pixel Dungeon dibuja el mundo con una cámara y la interfaz con otra distinta. Inventario, barra de herramientas, ventanas: todo eso vive en una cámara aparte.

Así que cambiar lo que dibuja la cámara del mundo deja la interfaz entera intacta, sin tocar una línea. Si hubiera cambiado de motor, habría tenido que reescribirla toda.

Un detalle de arquitectura de otro decidió si el proyecto duraba semanas o años.

03 · el techo

Convertí el techo en una lámpara

El suelo de Pixel Dungeon es oscuro: visto desde arriba, funciona. De frente, se lee como un agujero negro. Así que subí el brillo del suelo multiplicando por 1.85.

El techo se dibuja con la textura de pared, que es mucho más clara, y estaba en la misma malla que el suelo. 0.65 por 1.85 da 1.20, y por encima de 1.0 no hay nada: blanco plano. Los pasillos parecían oficinas.

Ahora el techo es su propia malla. Es la superficie que nunca miras de frente: puede ser lo más oscuro de la sala.

04 · el agua

Cada charco era un agujero en el suelo

Durante semanas creí que el piso negro que veía era cuestión de contraste del pixel art. No lo era.

El tile del agua en el atlas está completamente transparente. En el juego plano eso es correcto: es un hueco a propósito, para que se vea por debajo la capa de agua animada. En primera persona escondí esa capa, así que cada charco quedó como un vacío. Unas cien celdas por piso.

Mi primer intento sólo trató el agua pura y el agujero seguía ahí: los otros quince tiles son las orillas. La respuesta correcta era preguntarle al juego en vez de escribir mi propia lista.

Terrain.flags[t] & Terrain.LIQUID

Diagnostiqué mal durante semanas algo que el código sabía contestar en una línea.

05 · los monstruos

Iba a redibujar 97 criaturas para nada

Mi plan decía, con todas sus letras, que los sprites originales eran vista superior y no servían, y que había que rehacerlos todos vistos de frente.

Miré la rata píxel por píxel. Está dibujada sentada, de tres cuartos, con los ojos al frente y la cola apoyada en la base del cuadro. Watabou dibujó a las criaturas como retratos verticales aunque el mapa fuera cenital: la convención de siempre en el pixel art de mazmorras.

Lo cenital era el suelo y las paredes, y eso ya lo había reemplazado la geometría. Las criaturas nunca fueron arte cenital.

La parte más grande del trabajo pendiente resultó no existir.

06 · el pasto

Quité una táctica del juego sin darme cuenta

Un comentarista de r/PixelDungeon lo vio desde una captura: el pasto alto debería tapar la vista.

HIGH_GRASS   PASSABLE | LOS_BLOCKING | FLAMABLE

Mi malla decide qué es pared preguntando si algo es sólido. El pasto es opaco pero atravesable, una categoría que mi render no contemplaba, así que lo pintaba plano en el piso. Esconderse en el pasto dejaba de significar nada.

Alguien que conoce el juego de memoria encontró en una foto lo que cuatro revisiones de código no vieron.

07 · las diagonales

Le quité el movimiento diagonal al juego

Mi stick tenía cuatro direcciones. Pixel Dungeon se mueve en ocho, y esquivar en diagonal es táctica real: escapar, rodear, ganar una casilla.

Lo había eliminado sin enterarme, escribiendo un control que parecía razonable. Ahora son ocho sectores relativos a donde miras: si miras en diagonal, las diagonales pasan a ser tu adelante y tus lados.

Un control «razonable» puede borrar una mecánica sin que nada falle ni avise.

08 · la prueba

Mis pruebas llevaban meses validando basura

Escribí suites que corren sin dispositivo: generan un nivel de verdad, construyen la malla y comprueban la geometría. Todas pasaban.

Faltaba inicializar una tabla de probabilidades de objetos. Sin ella, el primer pintor que pedía un objeto al azar reventaba, la construcción del nivel se cortaba a mitad, y toda celda sin pintar quedaba en cero — que es precisamente el valor del abismo, que dibuja negro. Las pruebas validaban medio nivel y medio vacío.

Lo escondía una sola línea:

try { level.create(); } catch (Throwable ignored) {}

Una prueba que pasa sobre datos degenerados es peor que no tener prueba.

09 · la precisión

La luz se va a romper, pero sólo en algunos teléfonos

La antorcha calcula la distancia real del ojo a cada punto. El shader pedía precisión media, que en muchas GPU móviles son 16 bits, con techo en 65504.

48 casillas = 144 unidades → 62208   cabe, al filo
64 casillas = 192 unidades → 110592  desborda
80 casillas = 240 unidades → 172800  desborda

Los niveles miden 48. Pasa por un cinco por ciento. Basta agrandar un nivel para que la luz empiece a dar artefactos en unos teléfonos y no en otros, y parezca un problema de la GPU en vez de una línea del shader.

El bug más caro es el que todavía no ocurre y ya está escrito.

10 · el fantasma

Tres días persiguiendo un fallo de 3D que no existía

La demo web salía con «aquí no hay WebGL». Culpé a la caché. Luego al navegador. Luego a la carga del nivel. Las tres veces me equivoqué.

#error  { display: grid; }      /* mío: gana */
[hidden] { display: none; }     /* del navegador: pierde */

Ocultar el panel de error nunca funcionó. Era un rectángulo opaco a pantalla completa, encima de un canvas que dibujaba perfectamente.

Y lo peor: mi forma de comprobarlo me dio la razón. Leí el contenido del canvas y salió correcto — porque el canvas estaba correcto, debajo del panel. Estuve mirando justo la parte que funcionaba.

Una verificación que sólo mira donde tú crees que está el problema confirma lo que ya pensabas.

← volver a PixelDoomgeon