fogón

← BibliotecaConceptomormon/block-world

Mundo de bloques

m@mormonConcepto, MIT
v1hace 2 días
Clonar
FOGONfogon pieces add block-world
GITgit clone https://git.fogon.gg/mormon/block-world.git
REMIXfogon new block-world --from block-world
ZIPblock-world.zipBajar
Controles

Mundo de bloques

Una forma de dibujar el mundo: todo son cubos de un metro, con caras de 16×16 píxeles. Acá no hay ni un archivo de imagen ni un modelo: la isla sale de una semilla (la misma semilla da el mismo mundo en todas las pantallas), las texturas se pintan píxel por píxel con código y la luz se calcula al armar la malla.

La cabaña entre abedules, al atardecer

En la demo: se camina en primera persona por una isla de 256 × 256 bloques con pradera, bosque, desierto, nieve, cerros, lagos y cuevas. Clic rompe el bloque que mirás, clic derecho pone el que tenés elegido (1–9 o la rueda), espacio salta, shift corre y N pasa del día a la noche. Otra semilla en el link (?seed=7) es otra isla. En el teléfono: el pulgar izquierdo camina, el derecho mira, y un toque rompe o pone.

Es solo la página. No hay sala, ni red, ni nada guardado: todo pasa en el navegador de quien juega y al recargar la isla vuelve a estar como nueva. Es una base para empezar un juego propio, no un juego terminado.

Qué trae

client/blocks es lo que se lleva. Los cuatro primeros archivos no dependen de ningún motor.

ArchivoQué hace
world.tsEl mundo: qué bloque hay en cada celda (World, get, set), la tabla de bloques (BLOCKS, B.stone…) y raycast, que dice qué bloque se está mirando y por qué cara.
terrain.tsgenerate(world): llena el mundo desde su semilla (isla, cerros, cuevas, cuatro tipos de suelo, árboles, flores y una cabaña donde aparece la gente).
mesh.tsmeshChunk(world, cx, cz): un pedazo de 16 × 16 columnas como triángulos, solo con las caras que se ven y la luz de cada esquina ya calculada.
walk.tsWalker: alguien que camina, salta y nada, y al que los bloques sólidos frenan.
tiles.tsLas 28 texturas, pintadas por código (paintTiles), y tileCanvas para mostrar una en un botón.
noise.tsEl ruido con semilla que usan el terreno y las nubes.
three.tsTodo eso en three.js: BlockView (las mallas, que se rehacen solas cuando cambia un bloque), Sky (día y noche, sol, luna, estrellas y nubes) y makeHighlight (el marco del bloque apuntado).

Lo demás es la demo y sirve de ejemplo: client/main.ts tiene el teclado, el mouse, los controles táctiles, la barra de bloques, romper y poner, y los dos sonidos (hechos con código, sin archivos).

Cómo se usa en un juego

import * as THREE from "three";
import { B, World, raycast } from "./blocks/world";
import { generate } from "./blocks/terrain";
import { BlockView, Sky } from "./blocks/three";
import { Walker } from "./blocks/walk";

const world = new World({ seed: 3 }); // 256 × 256 de lado y 64 de alto, si no se dice otra cosa
generate(world);                      // la isla de esa semilla
const view = new BlockView(world);
const sky = new Sky(world.seed);
scene.add(sky.object, view.object);

const me = new Walker();
Object.assign(me, world.spawn);       // frente a la cabaña
camera.rotation.order = "YXZ";

// En cada cuadro (dt en segundos; hour: 0 amanece, 0.25 mediodía, 0.5 atardece, 0.75 medianoche):
me.step(world, { forward: 1, right: 0, jump: false, sprint: false }, dt);
camera.position.set(me.x, me.y + me.eye, me.z);
camera.rotation.set(me.pitch, me.yaw, 0);
sky.update(hour, camera, dt, world.size / 2);
view.setLight(sky.light, sky.fog);
view.update(me.x, me.z);              // arma lo que falta o cambió, lo más cercano primero

// Romper lo que se mira y poner un bloque contra esa cara:
const hit = raycast(world, camera.position.x, camera.position.y, camera.position.z, dir.x, dir.y, dir.z, 6);
if (hit) {
  world.set(hit.x, hit.y, hit.z, B.air);
  world.set(hit.x + hit.nx, hit.y + hit.ny, hit.z + hit.nz, B.planks);
}

world.set avisa solo a la vista: el pedazo que cambió se vuelve a armar en el cuadro siguiente.

Un mapa propio. generate es una forma de llenar el mundo, no la única. Para un mapa hecho a mano (una arena, un laberinto, un pueblo) se escribe celda por celda con world.fill(x, y, z, B.stone) y al final se llama world.settle(). world.spawn dice dónde aparece la gente.

Para hacerlo entre varios. Esta base no trae esa parte, pero queda a mano: el terreno no hace falta mandarlo (cada pantalla lo genera con la misma semilla), así que la sala solo tendría que decir la semilla, quién está dónde y qué celdas cambiaron. world.set es el único lugar por donde pasa un cambio.

Otro bloque. Se pinta su cara en tiles.ts (un nombre en NAMES y una función que da el color de cada píxel) y se agrega en world.ts (su clave en KEYS y su ficha en TYPES). Agregalos al final de la lista: el número de cada bloque es su lugar en ella, y lo ya guardado usa esos números.

Con otro motor. meshChunk devuelve arreglos comunes: posiciones, uvs (u, v y el número de textura) y light (luz del día y luz de farol, de 0 a 1 cada una). El material hace textura × max(color del día × luz del día, color del farol × luz de farol); el agua va aparte, transparente y sin escribir profundidad.

Por qué se ve así

  • Las sombras son suaves sin ninguna lámpara: cada esquina de cada cara se oscurece según cuántos bloques la rodean, y según cuánto cielo ve (pleno al aire libre, menos bajo un árbol o un techo, casi nada en una cueva).
  • Los faroles alumbran lo que tienen cerca con una luz cálida que no depende de la hora: de noche son lo único que se ve bien.
  • Las texturas van en capas, una por dibujo: de cerca se ven los píxeles nítidos y de lejos se suavizan sin mezclarse con la de al lado.

Límites

  • El mundo tiene un tamaño fijo y se genera entero al entrar (unos 100 ms para 256 × 256 × 72; la isla de la demo son unos 450 000 triángulos). No es infinito: pasando el borde hay mar.
  • La luz no se propaga bloque a bloque: un farol alumbra a través de una pared, y una cueva es oscura por lo profunda que está, tenga o no una boca cerca.
  • El agua no corre y la arena no cae: si se rompe la orilla de un lago, el agua se queda donde estaba.
  • No guarda nada ni es para varios: lo que se construye se pierde al recargar. server/room.ts y shared.ts están vacíos, solo porque todo proyecto de Fogón los lleva.
  • Necesita WebGL 2 (cualquier navegador actual). three.js es una dependencia de npm: el juego que lo use se arma con la CLI de Fogón (fogon dev, fogon deploy).

De dónde sale

Escrito para la biblioteca de Fogón. Código y texturas MIT.