Saltar al contenido
Docs

Servicio mundo

Referencia del servicio mundo. Lo que hay en la escena: piezas, zonas, apariciones, luces y mapas de baldosas, y los eventos de tocar, entrar y chocar.

Corresponde aFormato 1, API 1 Para quien escribe código Esta página se genera desde las fuentes del SDK.

En esta página

Lo que hay en la escena: piezas, zonas, apariciones, luces y mapas de baldosas, y los eventos de tocar, entrar y chocar.

Los tipos que aparecen en las firmas (Vec3, Texto, el selector) están en la referencia de la API.

Funciones y propiedades de mundo#

PropiedadTipoDescripción
mundo.gravedadnumberAceleración vertical (negativa hacia abajo).
mundo.terrenoTerrenoEl terreno de cubos de la escena (mundo abierto). Existe siempre: en una escena sin terreno, activo es false y sus métodos fallan con un mensaje claro. Agregado en la versión 1.1.

mundo.pieza#

ts
mundo.pieza(id: string): Pieza

La pieza con ese id. Falla con un mensaje claro si no existe (usa mundo.existe para preguntar).

De docs/EJEMPLOS.md, «interfaz: rótulos, barras y números en el mundo»:

ts
const torre = mundo.pieza('torre');

mundo.existe#

ts
mundo.existe(id: string): boolean

Dice si hay una pieza con ese id.

De games/isla-taller/scripts/principal.ts:

ts
if (!mundo.existe(`pad-${o.id}`)) continue;

mundo.piezas#

ts
mundo.piezas(etiqueta?: string): Pieza[]

Todas las piezas con esa etiqueta (sin numeral). Sin argumento, todas las piezas.

De docs/EJEMPLOS.md, «azar»:

ts
const losas = azar.mezclar(mundo.piezas('losa'));

mundo.crear#

ts
mundo.crear(def: PiezaDef): Pieza

Crea una pieza nueva. Mismo formato que en la escena.

De docs/EJEMPLOS.md, «mundo: crear piezas desde el script»:

ts
const moneda = mundo.crear({
  forma: 'cilindro',
  posicion: [azar.entre(-10, 10), 1, azar.entre(-10, 10)],
  rotacion: [90, 0, 0],
  tamano: [0.9, 0.16, 0.9],
  material: { color: 'ambar', acabado: 'brillante' },
  solida: false,
  etiquetas: ['moneda']
});

mundo.eliminar#

ts
mundo.eliminar(pieza: Pieza | string): void

Elimina una pieza. Recibe la pieza o su id.

De games/isla-taller/scripts/principal.ts:

ts
function quitarFugaz(cristal: Pieza): void {
  const haz = cristal.datos.haz as string;
  if (mundo.existe(haz)) mundo.eliminar(haz);
  cristal.eliminar();
}

mundo.zona#

ts
mundo.zona(id: string): Zona

La zona con ese id.

De games/isla-taller/scripts/principal.ts:

ts
mundo.zona(`zona-${id}`).activa = false;

mundo.zonas#

ts
mundo.zonas(etiqueta?: string): Zona[]

Todas las zonas con esa etiqueta. Sin argumento, todas las zonas.

mundo.aparicion#

ts
mundo.aparicion(id: string): { id; posicion; rotacionY }

El punto de aparición con ese id: su posición y hacia dónde mira.

mundo.apariciones#

ts
mundo.apariciones(): Aparicion[]

Todos los puntos de aparición de la escena.

mundo.alTocar#

ts
mundo.alTocar(selector: Selector, fn: (pieza: Pieza, jugador: Jugador) => void): void

Un jugador empieza a tocar una pieza (sólida o no).

De docs/EJEMPLOS.md, «jugadores: mover, teletransportar y cambiar habilidades»:

ts
mundo.alTocar('#trampolin', (_p, j) => j.impulsar([0, 20, 0]));

mundo.alDejarDeTocar#

ts
mundo.alDejarDeTocar(selector: Selector, fn: (pieza: Pieza, jugador: Jugador) => void): void

Un jugador deja de tocar una pieza.

De docs/EJEMPLOS.md, «mundo: tocar piezas por etiqueta y por id»:

ts
mundo.alDejarDeTocar('#placa', (placa) => (placa.material = 'gris'));

mundo.alEntrar#

ts
mundo.alEntrar(selector: Selector, fn: (zona: Zona, jugador: Jugador) => void): void

Un jugador entra a una zona.

De docs/EJEMPLOS.md, «juego: cambiar de escena»:

ts
mundo.alEntrar('tesoro', (_zona, j) => juego.terminar({ resultado: 'victoria', puntaje: 1, de: j }));

mundo.alSalir#

ts
mundo.alSalir(selector: Selector, fn: (zona: Zona, jugador: Jugador) => void): void

Un jugador sale de una zona.

De docs/EJEMPLOS.md, «mundo: zonas»:

ts
mundo.alSalir('zona-segura', (_zona, j) => interfaz.mensaje(interfaz.t('cuidado'), { para: j, tipo: 'peligro' }));

mundo.rayo#

ts
mundo.rayo(origen: Vec3, direccion: Vec3, distanciaMaxima: number): { pieza: Pieza | null; punto: Vec3; distancia: number } | null

Lanza un rayo contra las piezas sólidas. direccion no necesita estar normalizada.

De docs/EJEMPLOS.md, «mundo: rayo»:

ts
for (const j of jugadores.todos()) {
  const p = j.posicion;
  const hacia: [number, number, number] = [p[0] - ojo[0], p[1] + 1 - ojo[1], p[2] - ojo[2]];
  const distancia = Math.hypot(hacia[0], hacia[1], hacia[2]);
  const impacto = mundo.rayo(ojo, hacia, distancia);
  j.datos.visto = impacto === null || impacto.distancia > distancia - 0.6;
}

mundo.mientrasToca#

ts
mundo.mientrasToca(selector: Selector, fn: (pieza: Pieza, jugador: Jugador, dt: number) => void): void

Se llama en cada cuadro mientras un jugador sigue tocando la pieza (daño por segundo, cintas, hielo).

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Lava: quita vida mientras se pisa. dt siempre vale 1/60.
mundo.mientrasToca('#lava', (_lava, j, dt) => { j.datos.vida = (j.datos.vida as number) - 10 * dt; });

mundo.alChocar#

ts
mundo.alChocar(selector: Selector, fn: (pieza: Pieza, otra: Pieza) => void): void

Una pieza dinámica que calza con el selector empieza a tocar otra pieza (la pelota toca el arco).

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Una pelota (pieza dinamica con "rebote": 0.8) toca el arco.
mundo.alChocar('pelota', (_pelota, otra) => { if (otra.tiene('arco')) juego.registro('gol'); });

mundo.piezasEn#

ts
mundo.piezasEn(centro: Vec3, radioOTamano: number | Vec3, etiqueta?: string): Pieza[]

Piezas (sólidas o no) que tocan una esfera de ese radio, o una caja de ese tamaño, centrada en centro.

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Explosion: todo lo que haya a 4 unidades con la etiqueta "caja".
mundo.alTocar('bomba', (bomba) => {
  for (const caja of mundo.piezasEn(bomba.posicion, 4, 'caja')) caja.impulsar([0, 12, 0]);
  bomba.eliminar();
});

mundo.jugadoresEn#

ts
mundo.jugadoresEn(centro: Vec3, radio: number): Jugador[]

Jugadores cuyo cuerpo está a menos de radio del punto.

mundo.grupo#

ts
mundo.grupo(nombre: string): Grupo

El grupo de piezas con ese nombre (propiedad grupo de las piezas).

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Las piezas con "grupo": "puerta" en la escena se mueven juntas.
const puerta = mundo.grupo('puerta');

mundo.crearZona#

ts
mundo.crearZona(def: ZonaDef): Zona

Crea una zona nueva. Mismo formato que en la escena.

De docs/EJEMPLOS.md, «mundo: luces, ambiente y zonas creadas por el script»:

ts
// Una zona de premio que aparece en un lugar nuevo cada vez.
const zona = mundo.crearZona({ id: 'premio', forma: 'esfera', posicion: [5, 1, 5], tamano: [3, 3, 3], datos: { puntos: 5 } });

mundo.mapa#

ts
mundo.mapa(id: string): Mapa

Un mapa de baldosas de la escena actual. Falla si no existe.

De docs/EJEMPLOS.md, «Láminas y mapas de baldosas (2D)»:

ts
// Abre un paso: borra tres baldosas del piso.
const nivel = mundo.mapa('nivel');

mundo.mapas#

ts
mundo.mapas(): Mapa[]

Los mapas de baldosas de la escena actual.

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
mundo.mapas();

mundo.luz#

ts
mundo.luz(id: string): Luz

La luz con ese id (declarada en la escena o creada con mundo.crearLuz).

mundo.luces#

ts
mundo.luces(): Luz[]

Todas las luces que el script puede cambiar.

De docs/EJEMPLOS.md, «mundo: luces, ambiente y zonas creadas por el script»:

ts
// Anochece en 20 segundos y se encienden los faroles (luces con "id" en la escena).
reloj.despues(30, () => {
  mundo.ambiente({ cielo: 'noche', luzAmbiente: 0.25, sol: { intensidad: 0.1 }, niebla: { desde: 10, hasta: 60 } }, 20);
  for (const luz of mundo.luces()) luz.encendida = true;
  sonido.ruido({ duracion: 3, filtro: 600, volumen: 0.4, ataque: 1 });
});

mundo.crearLuz#

ts
mundo.crearLuz(def: LuzDef): Luz

Crea una luz puntual. Hay un máximo de 8 a la vez.

De docs/EJEMPLOS.md, «mundo: luces, ambiente y zonas creadas por el script»:

ts
const faro = mundo.crearLuz({ id: 'faro', posicion: [0, 8, 0], color: 'ambar', intensidad: 3, alcance: 30 });

mundo.ambiente#

ts
mundo.ambiente(cambios: { cielo?, luzAmbiente?, sol?, niebla? }, duracion?: number): void   // día y noche, tormenta

Cambia el ambiente mientras se juega: cielo, luz ambiente, sol y niebla. Solo lo que se indique. duracion, en segundos, lo hace gradual.

De docs/EJEMPLOS.md, «mundo: luces, ambiente y zonas creadas por el script»:

ts
// Anochece en 20 segundos y se encienden los faroles (luces con "id" en la escena).
reloj.despues(30, () => {
  mundo.ambiente({ cielo: 'noche', luzAmbiente: 0.25, sol: { intensidad: 0.1 }, niebla: { desde: 10, hasta: 60 } }, 20);
  for (const luz of mundo.luces()) luz.encendida = true;
  sonido.ruido({ duracion: 3, filtro: 600, volumen: 0.4, ataque: 1 });
});

mundo.modelo#

ts
mundo.modelo(id: string): ModeloDelJuego

Datos de un modelo del juego (modelos/<id>.glb). Falla con un mensaje claro si no existe.

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
mundo.modelo('robot');  // { id, tamano, clips: [{ nombre, duracion }], triangulos }

mundo.modelos#

ts
mundo.modelos(): string[]

Ids de los modelos del juego.

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
mundo.modelos();  // ids de los modelos del juego

mundo.crearModelo#

ts
mundo.crearModelo(modelo: string, def: ModeloEnMundo): Pieza

Crea una pieza con forma 'modelo' a su tamaño natural (por escala). Atajo de crear para no calcular tamano.

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
const robot = mundo.crearModelo('robot', { id: 'r1', posicion: [0, 0.9, 5], escala: 1 });  // pieza a tamaño natural

Pieza#

Una pieza del mundo: una de las que declara la escena o una creada con mundo.crear.

PropiedadTipoDescripción
pieza.idstringSolo lectura. Id de la pieza.
pieza.formaFormaSolo lectura. Forma de la pieza.
pieza.fisicaFisicaSolo lectura. Cómo participa en la física: 'fija', 'dinamica' o 'ninguna'.
pieza.existebooleanSolo lectura. false cuando la pieza fue eliminada. Usar una pieza eliminada no falla, pero no hace nada.
pieza.posicionVec3Centro de la pieza. Leerla da una copia; para moverla, asigna un vector nuevo.
pieza.rotacionVec3Grados.
pieza.tamanoVec3Ancho (X), alto (Y) y largo (Z).
pieza.materialMaterialDefEl nombre de un color de la paleta, o un objeto con color, acabado y patrón.
pieza.visiblebooleanfalse: no se dibuja.
pieza.solidabooleanfalse: los jugadores la atraviesan, pero sigue avisando toques.
pieza.velocidadVec3Solo piezas dinámicas.
pieza.etiquetasreadonly string[]Solo lectura. Las etiquetas de la pieza. Se cambian con etiquetar y desetiquetar.
pieza.datosRecord<string, unknown>Datos libres del script. Parte con los datos declarados en la escena.
pieza.grupostringSolo lectura. Nombre del grupo al que pertenece, o ''.
pieza.rebotenumberCuánto rebota (0 a 1.5).
pieza.friccionnumberRoce (0 a 2).
pieza.opacidadnumberDe 0, invisible, a 1, opaca.
pieza.emisionnumberDe 0 a 1: brillo propio.
pieza.velocidadDeGiroVec3Solo dinámicas: velocidad de giro en grados por segundo.
pieza.laminastringSolo piezas con forma lamina: id de la lámina que muestra ('' en las demás piezas). Se puede cambiar por otra del paquete.
pieza.animacionstring | number | nullSolo piezas con forma lamina: animación (nombre) o cuadro fijo (número) que muestra. null: la primera animación. Asignarla la reinicia.
pieza.espejobooleanSolo piezas con forma lamina: dibujada espejada en horizontal.
pieza.modelostringSolo lectura. Solo modelos: id del modelo que muestra ('' en las demás piezas).
pieza.clipsreadonly string[]Solo lectura. Solo modelos: nombres de los clips del modelo (vacío en las demás piezas).

pieza.tiene#

ts
pieza.tiene(etiqueta: string): boolean

Dice si la pieza tiene esa etiqueta.

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Una pelota (pieza dinamica con "rebote": 0.8) toca el arco.
mundo.alChocar('pelota', (_pelota, otra) => { if (otra.tiene('arco')) juego.registro('gol'); });

pieza.etiquetar#

ts
pieza.etiquetar(etiqueta: string): void

Le pone una etiqueta. Los eventos con selector '#etiqueta' la toman en cuenta desde ese momento.

De docs/EJEMPLOS.md, «azar»:

ts
for (const trampa of losas.slice(0, 3)) trampa.etiquetar('peligro');     // tres trampas distintas cada partida

pieza.desetiquetar#

ts
pieza.desetiquetar(etiqueta: string): void

Le quita una etiqueta.

De games/circuito-luz-verde/scripts/principal.ts:

ts
const semaforo = mundo.pieza('semaforo-corredor');
// ...
reloj.despues(0.3, () => semaforo.desetiquetar('recien-verde'));

pieza.moverA#

ts
pieza.moverA(destino: Vec3, opciones: { duracion: number; suavizado?: 'lineal' | 'suave'; vaiven?: boolean; espera?: number; alLlegar?: () => void }): void

Mueve la pieza con suavidad hasta destino. Reemplaza cualquier movimiento anterior.

De docs/EJEMPLOS.md, «mundo: mover piezas»:

ts
// Ida y vuelta para siempre.
mundo.pieza('plataforma').moverA([0, 0, 20], { duracion: 3, vaiven: true, espera: 0.5 });

pieza.girar#

ts
pieza.girar(gradosPorSegundo: Vec3): void

Gira sin parar a gradosPorSegundo en cada eje. [0, 0, 0] detiene el giro.

De docs/EJEMPLOS.md, «mundo: crear piezas desde el script»:

ts
for (let i = 0; i < 12; i++) {
  const moneda = mundo.crear({
    forma: 'cilindro',
    posicion: [azar.entre(-10, 10), 1, azar.entre(-10, 10)],
    rotacion: [90, 0, 0],
    tamano: [0.9, 0.16, 0.9],
    material: { color: 'ambar', acabado: 'brillante' },
    solida: false,
    etiquetas: ['moneda']
  });
  moneda.girar([0, 160, 0]);
}

pieza.detener#

ts
pieza.detener(): void

Detiene moverA y girar.

De games/copa-de-bloques/scripts/principal.ts:

ts
for (const p of publico) {
  p.pieza.detener();
  p.pieza.posicion = p.base;
}

pieza.impulsar#

ts
pieza.impulsar(velocidad: Vec3): void

Solo piezas dinámicas: empujón instantáneo (cambio de velocidad).

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
for (const caja of mundo.piezasEn(bomba.posicion, 4, 'caja')) caja.impulsar([0, 12, 0]);

pieza.alTocar#

ts
pieza.alTocar(fn: (jugador: Jugador) => void): void

Atajo de mundo.alTocar(pieza.id, ...).

pieza.eliminar#

ts
pieza.eliminar(): void

Quita la pieza del mundo. Los eventos registrados con pieza.alTocar se van con ella.

De docs/EJEMPLOS.md, «jugadores: mover, teletransportar y cambiar habilidades»:

ts
mundo.alTocar('#botas', (botas, j) => { botas.eliminar(); j.saltosExtra = 1; j.rapidez = 11; });

pieza.reproducir#

ts
pieza.reproducir(clip: string, opciones?: OpcionesClip): void

Solo modelos: reproduce un clip desde el principio. Falla con un mensaje claro si el modelo no lo tiene. La animación se dibuja: no mueve la pieza ni cambia cómo choca.

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
const robot = mundo.crearModelo('robot', { id: 'r1', posicion: [0, 0.9, 5], escala: 1 });  // pieza a tamaño natural
// ...
robot.reproducir('saludar', { repetir: false, mezcla: 0.2, velocidad: 1 });  // desde el principio

pieza.pararAnimacion#

ts
pieza.pararAnimacion(mezcla?: number): void

Solo modelos: deja de animar y vuelve al reposo, mezclando durante mezcla segundos (0,2 por defecto).

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
const robot = mundo.crearModelo('robot', { id: 'r1', posicion: [0, 0.9, 5], escala: 1 });  // pieza a tamaño natural
// ...
robot.pararAnimacion(0.3);  // vuelve al reposo mezclando 0,3 s

pieza.alTerminarClip#

ts
pieza.alTerminarClip(fn: (clip: string) => void): void

Solo modelos: avisa cuando un clip que no se repite llega a su fin. El momento sale de la duración del clip y del reloj de la partida: es el mismo en todos los equipos, dibujen a la velocidad que dibujen.

De docs/CONTRATO.md, «Modelos, texturas y sonidos (versión 1.2)»:

ts
const robot = mundo.crearModelo('robot', { id: 'r1', posicion: [0, 0.9, 5], escala: 1 });  // pieza a tamaño natural
// ...
robot.alTerminarClip((clip) => robot.reproducir('quieto'));  // solo clips que no se repiten

Grupo#

Varias piezas que se manejan juntas. Se arma con la propiedad grupo de cada pieza. No hay jerarquía: son operaciones en lote.

PropiedadTipoDescripción
grupo.nombrestringSolo lectura. Nombre del grupo.
grupo.centroVec3Solo lectura. Centro del grupo (promedio de las posiciones).
grupo.visiblebooleanSe aplica a todas las piezas del grupo.
grupo.solidabooleanSe aplica a todas las piezas del grupo.
grupo.materialMaterialDefSe aplica a todas las piezas del grupo.

grupo.piezas#

ts
grupo.piezas(): Pieza[]

Las piezas vivas del grupo.

grupo.mover#

ts
grupo.mover(delta: Vec3): void

Desplaza todas las piezas.

grupo.moverA#

ts
grupo.moverA(destino: Vec3, opciones: OpcionesMover): void

Mueve el grupo, con suavidad, hasta que su centro quede en destino, conservando la forma.

De docs/EJEMPLOS.md, «mundo: grupos, consultas, choques, permanencia»:

ts
// Las piezas con "grupo": "puerta" en la escena se mueven juntas.
const puerta = mundo.grupo('puerta');
// ...
mundo.alTocar('boton', () => puerta.moverA([0, 6, 10], { duracion: 1.5 }));

grupo.girarY#

ts
grupo.girarY(grados: number, pivote?: Vec3): void   // gira el conjunto alrededor de un eje vertical

Gira el grupo grados alrededor del eje Y que pasa por pivote (por defecto, su centro).

grupo.detener#

ts
grupo.detener(): void

Detiene el movimiento del grupo.

grupo.etiquetar#

ts
grupo.etiquetar(etiqueta: string): void

Pone la etiqueta a todas las piezas del grupo.

grupo.desetiquetar#

ts
grupo.desetiquetar(etiqueta: string): void

Quita la etiqueta a todas las piezas del grupo.

grupo.eliminar#

ts
grupo.eliminar(): void

Elimina todas las piezas del grupo.

Luz#

Una luz puntual que el script puede cambiar.

PropiedadTipoDescripción
luz.idstringSolo lectura. Id de la luz.
luz.posicionVec3Dónde está la luz.
luz.colorstringColor de la paleta.
luz.intensidadnumberCuánto ilumina, de 0 a 10.
luz.alcancenumberUnidades hasta donde llega.
luz.encendidabooleanApagada no ilumina (conserva su intensidad para volver a encenderla).

luz.eliminar#

ts
luz.eliminar(): void

Quita la luz del mundo.

Zona#

Una zona de la escena, o una creada con mundo.crearZona: avisa cuando alguien entra o sale.

PropiedadTipoDescripción
zona.idstringSolo lectura. Id de la zona.
zona.posicionVec3Solo lectura. Centro de la zona.
zona.tamanoVec3Solo lectura. Ancho (X), alto (Y) y largo (Z).
zona.etiquetasreadonly string[]Solo lectura. Las etiquetas de la zona.
zona.activabooleanUna zona desactivada no avisa entradas ni salidas.
zona.datosRecord<string, unknown>Datos libres del script. Parte con los datos declarados en la escena.

zona.tiene#

ts
zona.tiene(etiqueta: string): boolean

Dice si la zona tiene esa etiqueta.

zona.jugadores#

ts
zona.jugadores(): Jugador[]

Jugadores que están dentro ahora.

De games/circuito-luz-verde/scripts/principal.ts:

ts
jugadores.alAccion('accion', (j) => {
  if (ascensorOcupado || !mundo.zona('zona-ascensor').jugadores().includes(j)) return;
  subirAscensor();
});

zona.actores#

ts
zona.actores(): Actor[]

Actores que están dentro ahora.

zona.eliminar#

ts
zona.eliminar(): void

Quita la zona del mundo.

De docs/EJEMPLOS.md, «mundo: luces, ambiente y zonas creadas por el script»:

ts
mundo.alEntrar('premio', (z, j) => { j.puntos += z.datos.puntos as number; z.eliminar(); });

Mapa#

Un mapa de baldosas de la escena. Las columnas crecen hacia +X; las filas, hacia abajo ('xy') o hacia +Z ('xz').

PropiedadTipoDescripción
mapa.idstringSolo lectura. El id del mapa en la escena.
mapa.plano'xy' | 'xz'Solo lectura. 'xy': mapa vertical, el de la vista lateral. 'xz': mapa de suelo, el de las vistas desde arriba y los mundos 3D.
mapa.columnasnumberSolo lectura. Cuántas columnas tiene: el largo de la fila más larga.
mapa.filasnumberSolo lectura. Cuántas filas tiene.
mapa.celdanumberSolo lectura. Tamaño de una baldosa, en unidades.

mapa.leer#

ts
mapa.leer(columna: number, fila: number, capa?: number | string): string | null

Carácter de la baldosa en esa celda, o null si está vacía. capa: número o id; por defecto, la primera que no es fondo.

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
const nivel = mundo.mapa('nivel');  // falla con un mensaje claro si no existe
// ...
nivel.leer(3, 2);  // '=' o null. Tercer parámetro: capa (número o id); por defecto, la primera que no es fondo

mapa.poner#

ts
mapa.poner(columna: number, fila: number, baldosa: string | null, capa?: number | string): void

Cambia una baldosa (null la borra). La colisión y el dibujo se actualizan solos en el cuadro siguiente.

De docs/EJEMPLOS.md, «Láminas y mapas de baldosas (2D)»:

ts
mundo.alTocar('#vida', (vida, j) => {
  vida.eliminar();
  // Abre un paso: borra tres baldosas del piso.
  const nivel = mundo.mapa('nivel');
  for (let c = 9; c <= 11; c++) nivel.poner(c, 5, null);
  sonido.tocar('poder', { para: j });
});

mapa.celdaEn#

ts
mapa.celdaEn(punto: Vec3): { columna: number; fila: number }

Celda que contiene un punto del mundo (puede caer fuera del mapa).

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
const nivel = mundo.mapa('nivel');  // falla con un mensaje claro si no existe
// ...
nivel.celdaEn(jugador.posicion);  // { columna, fila }

mapa.centro#

ts
mapa.centro(columna: number, fila: number): Vec3

Centro de una celda en el mundo. En mapas de suelo ('xz') queda a ras del piso.

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
const nivel = mundo.mapa('nivel');  // falla con un mensaje claro si no existe
// ...
nivel.centro(3, 2);  // Vec3 del centro de la celda

mapa.buscar#

ts
mapa.buscar(baldosa: string, capa?: number | string): { columna: number; fila: number }[]

Todas las celdas que tienen esa baldosa.

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
const nivel = mundo.mapa('nivel');  // falla con un mensaje claro si no existe
// ...
nivel.buscar('^');  // todas las celdas con esa baldosa

mapa.colision#

ts
mapa.colision(columna: number, fila: number): ColisionBaldosa

Cómo choca esa celda, mirando todas las capas que no son fondo. 'ninguna' si está vacía.

De docs/CONTRATO.md, «Mapas de baldosas»:

ts
const nivel = mundo.mapa('nivel');  // falla con un mensaje claro si no existe
// ...
nivel.colision(3, 2);  // 'plataforma', 'ninguna', ...

Terreno#

Terreno de cubos: un mundo generado a partir de una semilla y de la tabla de biomas de la escena. Las coordenadas de un bloque son enteras: el bloque [x, y, z] ocupa de x a x+1, de y a y+1 y de z a z+1. El mundo mide 64 bloques de alto (y de 0 a 63). Se obtiene con mundo.terreno.

PropiedadTipoDescripción
terreno.activobooleanSolo lectura. La escena tiene terreno.
terreno.semillanumberSolo lectura. Semilla del mundo: la de terreno.semilla de la escena o, si no tiene, la de la partida.
terreno.nivelDelMarnumberSolo lectura. Altura del mar: el generador llena de agua hasta ahí.
terreno.altonumberSolo lectura. Alto del mundo en bloques (64).
terreno.limitenumberSolo lectura. El mundo llega hasta esta distancia del origen en X y en Z.
terreno.cambiosnumberSolo lectura. Cuántos bloques difieren hoy del mundo generado.

terreno.bloques#

ts
terreno.bloques(): string[]

Ids de los bloques declarados, en orden.

terreno.biomas#

ts
terreno.biomas(): string[]

Ids de los biomas declarados, en orden.

terreno.info#

ts
terreno.info(bloque: string): InfoBloque

Los datos de un bloque declarado: id, solido, transparente, liquido, dureza, luz, color y etiquetas.

De games/valle-abierto/scripts/comun.ts:

ts
export const terreno = mundo.terreno;
// ...
const info = terreno.info(id);

terreno.bloque#

ts
terreno.bloque(x: number, y: number, z: number): string

Id del bloque en esa celda. 'aire' si está vacía o fuera del mundo.

terreno.poner#

ts
terreno.poner(x: number, y: number, z: number, bloque: string, por?: Jugador): boolean

Pone un bloque ('aire' lo quita). Devuelve false si la celda ya tenía ese bloque o queda fuera del mundo. Avisa a alPoner.

terreno.romper#

ts
terreno.romper(x: number, y: number, z: number, por?: Jugador): string

Quita el bloque de la celda y devuelve su id ('aire' si no había nada). Avisa a alRomper.

terreno.llenar#

ts
terreno.llenar(desde: Vec3, hasta: Vec3, bloque: string): number

Llena la caja entre dos celdas (incluidas) con un bloque. Devuelve cuántas cambiaron. No avisa a alPoner ni a alRomper. Hasta 4096 celdas.

terreno.altura#

ts
terreno.altura(x: number, z: number): number

Altura del bloque sólido más alto de la columna, más uno: donde quedan los pies. Cuenta árboles y lo construido.

terreno.suelo#

ts
terreno.suelo(x: number, z: number): number

Altura del suelo natural que da el generador en esa columna, sin árboles ni cambios. Es barata: sirve para lugares lejanos.

terreno.bioma#

ts
terreno.bioma(x: number, z: number): string

Id del bioma de esa columna.

terreno.clima#

ts
terreno.clima(x: number, z: number): { temperatura: number; humedad: number }

Clima de esa columna, de 0 a 1.

terreno.buscarBioma#

ts
terreno.buscarBioma(bioma: string, desde: Vec3, radio?: number): Vec3 | null

La columna del bioma pedido más cercana a desde, o null si no hay ninguna a menos de radio (512 por defecto, hasta 2000). Devuelve los pies: [x + 0.5, suelo, z + 0.5].

terreno.buscarBloque#

ts
terreno.buscarBloque(bloque: string, desde: Vec3, radio?: number, alto?: number): Vec3 | null

La celda más cercana a desde con ese bloque, a menos de radio en horizontal (24 por defecto, hasta 48), o null. Con alto, solo cuentan las celdas a no más de esa distancia vertical de desde (lo que queda al alcance de la mano). Genera los trozos que revisa.

terreno.rayo#

ts
terreno.rayo(origen: Vec3, direccion: Vec3, distancia: number): BloqueApuntado | null

Primer bloque que toca un rayo (los líquidos y el aire no cuentan), hasta 64 unidades.

terreno.ruido#

ts
terreno.ruido(x: number, z: number, escala?: number, canal?: number): number

Ruido reproducible de -1 a 1 en un punto del plano, atado a la semilla del mundo. escala es el tamaño de sus manchas en bloques (32 por defecto); canal da ruidos independientes.

terreno.alRomper#

ts
terreno.alRomper(fn: (cambio: CambioDeBloque) => void): void

Se llama cada vez que romper quita un bloque, con la posición, lo que había, lo que quedó y el jugador que lo hizo, si lo hubo.

terreno.alPoner#

ts
terreno.alPoner(fn: (cambio: CambioDeBloque) => void): void

Se llama cada vez que poner cambia un bloque, con los mismos datos que alRomper.

terreno.alGenerar#

ts
terreno.alGenerar(fn: (trozo: { x: number; z: number }) => void): void

Gancho de generación: se llama cuando la sala carga un trozo de 16 x 16 por primera vez (cerca de un jugador). Recibe la esquina del trozo. Lo que el script cambie ahí con poner o llenar queda como parte del mundo.

terreno.guardar#

ts
terreno.guardar(jugador: Jugador): { bloques: number; bytes: number }

Guarda los cambios del mundo (solo lo que difiere del generado) en el progreso del jugador. Devuelve cuántos bloques y cuántos bytes ocupó.

terreno.restaurar#

ts
terreno.restaurar(jugador: Jugador): number

Vuelve a aplicar los cambios guardados por guardar. Devuelve cuántos bloques restauró (0 si no había nada o la semilla es otra).

terreno.borrarGuardado#

ts
terreno.borrarGuardado(jugador: Jugador): void

Borra los cambios guardados de ese jugador.

terreno.regenerar#

ts
terreno.regenerar(semilla: number): void

Reemplaza el mundo por el de otra semilla (un "mundo nuevo"): olvida los cambios y devuelve a los jugadores a su aparición.

Notas de comportamiento#

Un selector es un id ('meta') o una etiqueta con numeral ('#moneda'). Las etiquetas se evalúan en el momento del toque: puedes etiquetar y desetiquetar piezas mientras se juega (pieza.etiquetar('peligro')).

alTocar se dispara cuando un jugador empieza a tocar una pieza, sea sólida o no (pararse encima cuenta). rayo choca solo con piezas sólidas e ignora a los jugadores.

Ojo: pieza.posicion[0] = 5 no mueve nada (modifica una copia). Escribe pieza.posicion = [5, y, z].

Cómo se usa el terreno#

El terreno de cubos de la escena. Existe siempre: en una escena sin terreno, activo es false y sus métodos fallan con un mensaje que dice cómo activarlo. Los bloques se nombran por su id ('pasto'); el vacío es 'aire'.

Romper y poner los decide el script: el motor da el rayo, cambia el bloque y avisa. El esqueleto de toda construcción (está completo en la plantilla mundo-abierto):

ts
juego.cadaCuadro(() => {
  for (const j of jugadores.todos()) {
    const a = j.apuntaBloque(5);
    interfaz.marcarBloque(a ? a.posicion : null, {}, j);
    if (a && j.pulsa('primaria')) terreno.romper(a.posicion[0], a.posicion[1], a.posicion[2], j);
  }
});
jugadores.alAccion('secundaria', (j) => {
  const a = j.apuntaBloque(5);
  if (a) terreno.poner(a.vecino[0], a.vecino[1], a.vecino[2], 'tabla', j);
});

Lo que conviene saber:

  • poner no deja encerrar a nadie: devuelve false si en esa celda hay un jugador o un actor. A quien solo la roza (hasta un quinto de bloque) lo corre ese poquito. llenar no revisa.
  • El fondo del mundo no se toca (y = 0), ni lo que queda fuera de limite o sobre y = 63.
  • Guardado. El mundo no se guarda: se vuelve a generar con su semilla. terreno.guardar(jugador) escribe en el progreso de ese jugador solo lo que difiere del mundo generado, por trozo, a 4 caracteres por bloque, junto con la semilla. terreno.restaurar(jugador) vuelve a ese mundo (si la escena no fija semilla, cambia a la guardada) y le pone los cambios encima. Topes: 60 000 bloques cambiados a la vez (al llegar, poner y romper fallan con un mensaje claro: revisa terreno.cambios) y 320 KB de guardado, aparte de los 64 KB de progreso. La clave que usa (_terreno:<escena>) está reservada.
  • Costo. bloque, altura y buscarBloque generan el trozo si no estaba (cerca de 1 ms la primera vez); suelo, bioma, clima y buscarBioma no generan nada.
  • Intervenir la generación. El autor que quiere más que la tabla tiene tres caminos: estructuras en los datos, cambios directos al empezar (llenar un campamento, un faro), y alGenerar, que corre cuando la sala carga cada trozo cerca de un jugador (lo que se cambie ahí queda como parte del mundo y viaja a todos). El gancho no corre para los trozos lejanos que solo se dibujan: lo que agrega aparece al acercarse.

Ejemplos completos#

Scripts completos de docs/EJEMPLOS.md. Cada uno supone una escena con lo que nombra, y las claves de texto (interfaz.t('...')) deben existir en textos de juego.json.

mundo: tocar piezas por etiqueta y por id#

scripts/principal.ts
import { juego, mundo } from '@greengames/sdk';

juego.alIniciar(() => {
  mundo.alTocar('#moneda', (moneda, j) => {      // todas las piezas con la etiqueta "moneda"
    moneda.eliminar();
    j.puntos++;
  });
  mundo.alTocar('boton-rojo', (boton) => {        // la pieza con id "boton-rojo"
    boton.material = { color: 'verde', acabado: 'neon' };
    mundo.pieza('puerta').solida = false;
    mundo.pieza('puerta').visible = false;
  });
  mundo.alDejarDeTocar('#placa', (placa) => (placa.material = 'gris'));
});

mundo: crear piezas desde el script#

scripts/principal.ts
import { azar, juego, mundo } from '@greengames/sdk';

juego.alIniciar(() => {
  for (let i = 0; i < 12; i++) {
    const moneda = mundo.crear({
      forma: 'cilindro',
      posicion: [azar.entre(-10, 10), 1, azar.entre(-10, 10)],
      rotacion: [90, 0, 0],
      tamano: [0.9, 0.16, 0.9],
      material: { color: 'ambar', acabado: 'brillante' },
      solida: false,
      etiquetas: ['moneda']
    });
    moneda.girar([0, 160, 0]);
  }
});

mundo: mover piezas#

scripts/principal.ts
import { juego, mundo, reloj } from '@greengames/sdk';

juego.alIniciar(() => {
  // Ida y vuelta para siempre.
  mundo.pieza('plataforma').moverA([0, 0, 20], { duracion: 3, vaiven: true, espera: 0.5 });
  // Una vez, con aviso al llegar.
  mundo.pieza('puente').moverA([0, 0, 8], { duracion: 1.5, alLlegar: () => juego.registro('puente listo') });
});

// A mano, cuadro a cuadro: una pieza en órbita. Se asigna un vector nuevo.
juego.cadaCuadro(() => {
  const a = reloj.ahora * 2;
  mundo.pieza('satelite').posicion = [Math.cos(a) * 5, 3, Math.sin(a) * 5];
});

mundo: zonas#

scripts/principal.ts
import { interfaz, juego, mundo } from '@greengames/sdk';

juego.alIniciar(() => {
  mundo.alEntrar('#control', (zona, j) => {
    j.aparicion = zona.id;                         // hay una aparición con el mismo id que la zona
    interfaz.mensaje(interfaz.t('control'), { para: j, tipo: 'exito' });
  });
  mundo.alSalir('zona-segura', (_zona, j) => interfaz.mensaje(interfaz.t('cuidado'), { para: j, tipo: 'peligro' }));
  mundo.alEntrar('meta', (_zona, j) => juego.terminar({ resultado: 'victoria', puntaje: j.puntos, de: j }));
});

mundo: rayo#

scripts/principal.ts
import { juego, jugadores, mundo } from '@greengames/sdk';

// ¿La torre ve al jugador? Un rayo desde la torre hasta su pecho: si choca antes con una pieza, está oculto.
juego.cadaCuadro(() => {
  const ojo: [number, number, number] = [0, 6, 0];
  for (const j of jugadores.todos()) {
    const p = j.posicion;
    const hacia: [number, number, number] = [p[0] - ojo[0], p[1] + 1 - ojo[1], p[2] - ojo[2]];
    const distancia = Math.hypot(hacia[0], hacia[1], hacia[2]);
    const impacto = mundo.rayo(ojo, hacia, distancia);
    j.datos.visto = impacto === null || impacto.distancia > distancia - 0.6;
  }
});

mundo: grupos, consultas, choques, permanencia#

scripts/principal.ts
import { juego, mundo } from '@greengames/sdk';

juego.alIniciar(() => {
  // Las piezas con "grupo": "puerta" en la escena se mueven juntas.
  const puerta = mundo.grupo('puerta');
  mundo.alTocar('boton', () => puerta.moverA([0, 6, 10], { duracion: 1.5 }));

  // Explosion: todo lo que haya a 4 unidades con la etiqueta "caja".
  mundo.alTocar('bomba', (bomba) => {
    for (const caja of mundo.piezasEn(bomba.posicion, 4, 'caja')) caja.impulsar([0, 12, 0]);
    bomba.eliminar();
  });

  // Una pelota (pieza dinamica con "rebote": 0.8) toca el arco.
  mundo.alChocar('pelota', (_pelota, otra) => { if (otra.tiene('arco')) juego.registro('gol'); });

  // Lava: quita vida mientras se pisa. dt siempre vale 1/60.
  mundo.mientrasToca('#lava', (_lava, j, dt) => { j.datos.vida = (j.datos.vida as number) - 10 * dt; });
});

mundo: luces, ambiente y zonas creadas por el script#

scripts/principal.ts
import { juego, mundo, reloj, sonido } from '@greengames/sdk';

juego.alIniciar(() => {
  // Anochece en 20 segundos y se encienden los faroles (luces con "id" en la escena).
  reloj.despues(30, () => {
    mundo.ambiente({ cielo: 'noche', luzAmbiente: 0.25, sol: { intensidad: 0.1 }, niebla: { desde: 10, hasta: 60 } }, 20);
    for (const luz of mundo.luces()) luz.encendida = true;
    sonido.ruido({ duracion: 3, filtro: 600, volumen: 0.4, ataque: 1 });
  });
  const faro = mundo.crearLuz({ id: 'faro', posicion: [0, 8, 0], color: 'ambar', intensidad: 3, alcance: 30 });
  reloj.cada(0.5, () => (faro.encendida = !faro.encendida));

  // Una zona de premio que aparece en un lugar nuevo cada vez.
  const zona = mundo.crearZona({ id: 'premio', forma: 'esfera', posicion: [5, 1, 5], tamano: [3, 3, 3], datos: { puntos: 5 } });
  mundo.alEntrar('premio', (z, j) => { j.puntos += z.datos.puntos as number; z.eliminar(); });
  juego.registro('zona creada', zona.id);
});

Sigue con#