VOXPIX

Modelas en vóxeles. Sale pixel art.

Un editor en el navegador: pintas por capas o en 3D, lo iluminas, lo animas y lo recorres por dentro. Cada giro del modelo sale como un sprite nítido, con la paleta que elegiste y nada más.

102,776 vóxelesgiro 000°arrastra para girarlo

El editor

Todo pasa en una pestaña del navegador, sin instalar nada. Lo que ves en la vista previa es exactamente lo que exportas.

El editor en 3D: la escena de muestra con su torre, la casa, el río y humo saliendo del brasero
3D. Pintas sobre el modelo: el lápiz pone un vóxel en la cara que tocas.

Por capas o en 3D

Por capas pintas cada altura vista desde arriba, con la de debajo en fantasma. En 3D pintas directamente sobre el modelo, lo giras con el botón derecho y acercas con la rueda. Tab cambia de uno a otro.

Diez herramientas

Lápiz, borrar, repintar, gotero, caja, cubo, luz, fluido, efecto y espejo. El fluido vierte agua que cae y se reparte, o llena una cuenca hasta la altura que digas. Efecto pone humo, chispas, brasas, lluvia o nieve que corren solos.

La barra de herramientas: lápiz, borrar, repintar, gotero, caja, cubo, luz, fluido, efecto y espejo
La barra de abajo, a tamaño real: las diez herramientas y los tonos de la rampa elegida.
El editor por capas: la lista de alturas a la izquierda y la capa 3 vista desde arriba
Capas. Cada altura con su miniatura y cuántos vóxeles tiene.
La vista previa iluminada, con giro, elevación, contorno, ambiente y los 8 ángulos
Vista previa con luz. Las antorchas del modelo alumbran cada cara y hacen sombra.

La paleta manda

No eliges colores sueltos: eliges rampas, de oscuro a claro, y el render mueve el tono dentro de la rampa según la luz. Por eso la imagen solo tiene colores de tu paleta. Puedes generar una rampa desde un color, con las sombras girando hacia el frío como haría un pixel artist, o cargar PICO-8, Endesga o AAP-64.

La ventana de la paleta con sus rampas: hierba, tierra, piedra, madera, agua, hojas, tejado y arena

Animar

Una animación son cuadros, y cada cuadro es el modelo entero. Se edita en 3D con las herramientas de siempre y sale como GIF o como hoja de sprites desde el ángulo que elijas. Estas cuatro salieron así del editor.

Un slime que bota
Un slime que bota8 cuadros, cada uno pintado como un modelo entero: aplastar, estirar, saltar.
Un molino
Un molino6 cuadros de 15 grados. Con cuatro aspas, 90 grados cierran el ciclo.
MAIA andando
MAIA andandoEl ciclo de 8 cuadros del personaje, visto de lado.
El mismo ciclo, girado
El mismo ciclo, giradoLos mismos vóxeles desde otro ángulo: no hay que redibujar nada.
El modo Animación con el molino en el cuadro 3 de 6 y el cuadro anterior en fantasma
Papel cebolla: el cuadro anterior en fantasma, con la misma cámara.

Rig por partes

Para un ciclo de andar no hace falta pintar cada cuadro. El rig dice qué cajas de vóxeles son cada pierna, dónde está la cadera y cuánto avanza el pie en cada cuadro, y genera los cuadros desde la pose de reposo.

Explorar

Te mete dentro del modelo en primera persona: WASD para andar, espacio para saltar, F para volar. La altura del personaje va de 1 a 32 vóxeles, así que puedes ver tu escena como un bicho o como alguien de su tamaño.

48 cuadros del mismo render que Explorar, acercándose a la casa.
El modo Explorar volando sobre la escena de muestra, con humo saliendo de la torre
Volando sobre la escena, con el humo del brasero.

Un modelo, todos los giros

Dibujas el personaje una vez y sale en los ocho ángulos, o en los que pidas. El contorno, la luz y la paleta se mantienen en todos.

MAIA en ocho ángulos, de frente a de espaldas y vueltaLa escena de muestra en ocho ángulos
bun run voxpix render modelo.json giros.png --turn 8

Y debajo, un motor de juego

El mismo render mueve mundos enteros en 2.5D: luz dinámica que sigue a los personajes, sombras, agua, lluvia y partículas, compuesto por la GPU y con la CPU de respaldo. El mismo mundo se puede ver en vóxeles con una tecla.

El motor en modo 2D: MAIA en una plaza medieval de noche con una fuente, árboles, agua y nenúfares
El mismo mundo en el modo vóxel, con MAIA junto a la fuente
Modo vóxel: el mismo estado, otra cámara.
Un mundo cyberpunk bajo la lluvia con un lago tóxico que brilla
Lluvia sobre un lago tóxico que brilla solo.
Ver el motor en vivoUn escenario de ejemplo en tu navegador. Cambia de mundo, de modo y de tiempo.

Un mundo desde una receta

La forma de la sala, los colores, el agua, la vegetación, las luces y los efectos van en un JSON. El motor pone la geometría, la física y la luz. Estos seis los pintó el motor desde su línea de comandos, y la ficha es lo que devolvió. La misma receta da siempre el mismo mundo.

Una sala con forma de laberinto: las paredes siguen el contorno, sin bloques en medio.

  • 640 x 480 px de suelo
  • 5 columnas
  • 5 luces horneadas
Laberinto: el mundo que el motor pintó con esta receta
scripts/mundos/laberinto.json
{
  "theme": "medieval",
  "seed": 7,
  "shape": {
    "kind": "maze",
    "loops": 0.2
  },
  "foliage": [
    {
      "type": "grass",
      "density": 0.25
    }
  ]
}

Lo puede manejar un agente

Todo lo que hace una persona con el ratón tiene su llamada, y los errores dicen qué falta. Un agente de IA construye un modelo o un mundo entero, lo mira en PNG y comprueba lo que hizo. VoxPix lo escriben así: un equipo de agentes de IA dirigido por una persona.

El editor: window.voxpix
voxpix.box(-4, -4, 0, 3, 3, 3, 'piedra')
voxpix.light(0, -5, 4, 'torch')
voxpix.addFrame()      // otro cuadro
await voxpix.animGif({ scale: 4 })
El motor: window.voxpixWorld
await voxpixWorld.build({
  theme: 'medieval',
  seed: 5,
  size: [24, 18],
  rooms: [
    { gx: 0, gy: 0, shape: { kind: 'maze' } },
    { gx: 1, gy: 0, shape: { kind: 'T', bar: 'south' } },
  ],
})
voxpixWorld.groundAt(120, 96)  // el suelo
voxpixWorld.png({ scale: 2 })  // en PNG
Sin navegador: la línea de comandos
bun run voxpix world \
  receta.json mundo.png --whole

bun run voxpix render \
  modelo.json giros.png --turn 8

Un juego hecho con VoxPix

Brotato de la oficina es un roguelite de oleadas: diez rondas contra muebles con mala intención, una tienda entre ronda y ronda y un árbol de mejoras que se queda entre partidas. Lo hizo FERN, un agente del equipo, encima del motor y sin tocarlo. Tres mundos, cada uno con su aspecto.

Brotato de la oficina, oleada 6 de 10 en la oficina: MAIA rodeada de sillas, impresoras y archivadores
Oleada 6 en la oficina.
Una oleada en el mundo cyberpunk
Cyberpunk.
Una oleada en el mundo medieval
Medieval.
La cámara gira en cuartos de vuelta en mitad de la partida.
Detrás de una pared, la silueta en damero sigue a la vista.
La tienda entre oleadas: termo, puntero láser, café y un subfusil de agujas
La tienda entre oleadas.
La partida pausada y vista en vóxeles: MAIA en la plaza medieval
La misma partida, pausada y vista en vóxeles.
Jugar en el navegadorCon teclado, o táctil en el móvil. Versión d031e17 del 2026-10-26, compilada desde su repositorio.

Para hacer un juego

Lo que más cuenta quien ya construyó uno encima: FERN, el agente que hizo brotato-oficina, un roguelite sobre VoxPix.

  1. Un estado, una verdad

    El mundo vive en un WorldState que avanza a pasos fijos de 1/60 s, sin reloj ni azar suelto. Con la misma semilla y la misma entrada da el mismo mundo byte a byte, y state.hash() lo comprueba. Así se prueban partidas y curvas de dificultad a 64 semillas sin abrir un navegador.

  2. El aspecto es del juego

    El motor no trae temas. El juego le pasa su WorldLook: paleta, suelo, columnas, fachada, muebles. Con eso brotato-oficina hizo una oficina con café y mamparas y un cyberpunk de agua tóxica sin tocar el motor.

  3. La vista solo lee

    Lo estático se hornea una vez y las entidades se componen en cada cuadro. Ver el mismo estado en vóxeles, la silueta del jugador detrás de una pared o un objeto que late son capas encima, y pausarlas no cambia la partida.

  4. El presupuesto, medido

    En un Galaxy Z Flip 6, con la GPU, brotato-oficina da un p95 de 16,7 ms por cuadro en la oleada 10, con entre 76 y 291 enemigos, en sus tres mundos; y girar la cámara no pasa de 16,8 ms en ningún cuadro (medido por FERN, octubre de 2026). Si no hay WebGL2, la CPU pinta los mismos píxeles, más despacio. Las medidas del motor y cómo repetirlas están en scripts/perf.

Instalarlo en tu juego
// package.json: el motor, fijado a un commit
"voxpix": "github:andyeswong/voxpix#<commit>"

// vite.config: el alias, con ruta absoluta
resolve: { alias: { '@voxpix': fileURLToPath(
  new URL('./node_modules/voxpix/src', import.meta.url)) } }

// tsconfig: lo mismo en paths (con baseUrl)
"paths": { "@voxpix/*": ["node_modules/voxpix/src/*"] }
Un mundo en pantalla (de docs/motor.md)
import { generateLayout } from '@voxpix/worldgen'
import { buildWorldState } from '@voxpix/world/build'
import { THEMES } from '@voxpix/samples/themes'
import { bakeWorld } from '@voxpix/world/bake'
import { TICK } from '@voxpix/world/state'
import { createCompositor } from '@voxpix/view/compositor'
import { walkFrames } from '@voxpix/anim/walk'

// Aquí, un tema de muestra; tu juego trae el suyo
// (un WorldLook: paleta, suelo, columnas...)
const { spec, look, ambient } = THEMES.medieval
const layout = generateLayout(spec, 7)  // semilla
const { world, state } =
  buildWorldState(layout, look)
const baked = bakeWorld(world, { topZ: 40 })
// GPU con WebGL2; si no, la CPU
const compositor = createCompositor(canvas)

// walk: los cuadros de andar de un modelo
// (Model.fromJSON de un .voxpix.json)
const walk = walkFrames(modelo)
// layout.spawn: los pies del jugador, en píxeles
// (el mundo cuenta y hacia el norte: se invierte)
const x = layout.spawn.x
const y = world.depth - 1 - layout.spawn.y
state.entities.add({
  model: walk[0], x, y, z: 1, facing: 0 })

// Pasos fijos de 1/60 s, a cualquier ritmo de
// pantalla (en 120 Hz, dos cuadros por paso)
let acc = 0, last = performance.now()
function loop(now) {
  acc += (now - last) / 1000; last = now
  while (acc >= TICK) { state.step(); acc -= TICK }
  // La vista: los pies proyectados, centrados
  const [fx, fy] = baked.cam.project(x + 0.5, y, 1)
  const vx = Math.floor(fx) - 192
  const vy = Math.floor(fy) - 108
  compositor.frame(state, baked, { vx, vy,
    vw: 384, vh: 216, time: now / 1000,
    lights: true, ambient })
  requestAnimationFrame(loop)
}
requestAnimationFrame(loop)
// Si el mundo cambia vóxeles (agua, destrucción),
// rehornea lo cambiado: rebakeChangedJobs + FrameBudget

Empezar

Para usarlo no hace falta instalar nada: el editor y la demo del motor corren aquí mismo. Para el código y la línea de comandos:

git clone https://github.com/andyeswong/voxpix
cd voxpix
bun install
bun run dev        # el editor en http://localhost:5190
bun run test       # las pruebas

Lo que necesita y lo que aún no hace

  • Un navegador con WebGL2. Sin él funciona igual por la CPU, más despacio.
  • Teclado y ratón: el editor y la demo están pensados para escritorio.
  • bun para instalar, y Node 20 o posterior para el código y la línea de comandos.
  • No hay paquete publicado: un juego lo instala desde GitHub, fijado a un commit (bun add github:andyeswong/voxpix#commit).
  • El reflejo del metal está reservado pero no se pinta todavía.
  • Explorar dentro de un mundo grande va a unos 30 ms por cuadro; dentro de un modelo, a 60 fps.