Saltar al contenido
Docs

El formato .ggx

Qué hay dentro de un archivo .ggx, cómo se arma, cómo abrirlo y leerlo con herramientas comunes, sus límites de tamaño y qué garantiza el número de formato.

Corresponde aFormato 1, API 1 Para todos

En esta página

Un juego de Green Games se comparte como un solo archivo .ggx. No es un ejecutable ni un instalador: es un documento JSON comprimido con gzip. Se puede abrir y leer con herramientas que ya tienes.

Qué hay dentro#

Un .ggx es un objeto llamado paquete, escrito como JSON y comprimido con gzip. El paquete tiene estos campos (están definidos en packages/sdk/src/formato.ts del repositorio de Green Games):

CampoTipoQué es
gbx1Marca del contenedor. Siempre 1.
manifiestoobjetoEl contenido de juego.json: nombre, dificultades, controles, tablas, textos.
escenasobjetoLas escenas, por id: cada escenas/<id>.json.
scripttextoEl script unido: todo el código del juego en un solo texto de JavaScript clásico, que usa el objeto global greengames.
recursosobjetoLos archivos de recursos/, por ruta. Cada uno con su tipo (MIME) y sus datos en base64.
botobjeto, opcionalEl guion del bot: pruebas/bot.json.
laminasobjeto, opcionalLas láminas, por id: cada laminas/<id>.json. Son los dibujos de píxeles de piezas, baldosas y personajes.
guionesobjeto, opcionalGuiones adicionales (pruebas/bot-<nombre>.json), por nombre.
modelosobjeto, opcionalLos modelos 3D, por id: cada modelos/<id>.glb en base64, con su colisión de modelos/<id>.json. Desde la versión 1.2.
texturasobjeto, opcionalLas imágenes de texturas/, por id, con su tipo y sus datos en base64. Desde la versión 1.2.
sonidosobjeto, opcionalLos sonidos grabados de sonidos/, por id, en base64. Desde la versión 1.2.
creditoslista, opcionalEl contenido de creditos.json: autor, licencia y origen de cada modelo, textura y sonido. Desde la versión 1.2.

Eso es todo. Dentro de un .ggx no hay nada más: ni programas, ni bibliotecas, ni el motor. El motor lo pone quien abre el archivo. Los modelos, las imágenes y los sonidos van como datos, en base64, y un modelo no puede traer código.

laminas y guiones se agregaron después de la primera versión del contrato, y modelos, texturas, sonidos y creditos, con la versión 1.2. Un paquete que no los usa no los trae. Los documentos del taller del editor (taller/) no entran nunca.

Dos cosas cambian entre la carpeta del juego y el paquete:

  • El script se une y se convierte. Si escribiste TypeScript en varios archivos, dentro del .ggx hay un solo texto de JavaScript. La línea import { ... } from '@greengames/sdk' desaparece: los servicios salen del global greengames.
  • Los recursos se codifican en base64, para caber dentro del JSON. Por eso una portada de 25 KB ocupa unos 34 KB de texto antes de comprimir.

Cómo se arma#

ConCómo
El editorEl botón Exportar. Con errores de validación, no exporta. Ver Ficha y exportar.
La herramienta greengamesgreengames empaquetar mi-juego -o mi-juego.ggx. Valida antes; con errores, no escribe nada. Ver La herramienta greengames.

En los dos casos el camino es el mismo: leer la carpeta, unir el script, validar, convertir el paquete a JSON y comprimirlo con gzip.

El diálogo Exportar del editor al terminar: los pasos en verde y la ruta del archivo.

Cómo inspeccionarlo#

Como es gzip con JSON dentro, alcanza con gunzip y, para leerlo con comodidad, Node. Los comandos de esta sección se corrieron sobre el .ggx que arma greengames empaquetar para el juego de ejemplo patio-de-monedas, el del tutorial del editor. El archivo pesa 24 783 bytes.

Ver que es un .ggx#

Un .ggx empieza con los dos bytes de gzip, 1f 8b:

sh
head -c 2 patio-de-monedas.ggx | od -An -tx1
Salida
 1f 8b

Ver cuánto ocupa descomprimido#

sh
gunzip -c patio-de-monedas.ggx | wc -c
Salida
40803

24 783 bytes comprimido, 40 803 descomprimido. La mayor parte es la portada.

Ver el comienzo#

sh
gunzip -c patio-de-monedas.ggx | head -c 300
Salida
{"gbx":1,"manifiesto":{"$schema":"../../packages/sdk/esquemas/juego.schema.json","formato":1,"id":"patio-de-monedas","version":"0.1.0","nombre":{"es":"Patio de monedas","en":"Coin Yard"},"descripcion":{"es":"Junta las cinco monedas del patio y llega a la meta antes de que se acabe el tiempo. Cuidado

Ver los campos#

Con Node, una línea resume cada campo del paquete:

sh
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); for (const [k, v] of Object.entries(p)) console.log(k.padEnd(11), typeof v === "string" ? v.length + " caracteres" : typeof v === "object" ? Object.keys(v).join(", ") : v);'
Salida
gbx         1
manifiesto  $schema, formato, id, version, nombre, descripcion, autor, portada, jugadores, controles, etiquetas, dificultades, dificultadInicial, puntajes, escenaInicial, script, textos
escenas     principal
script      2231 caracteres
recursos    recursos/portada.jpg
bot         $schema, formato, tiempoMaximo, esperado, pasos

Leer el script#

Lo que más conviene mirar antes de abrir un juego ajeno es su script. Está en texto, sin ofuscar:

sh
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); console.log(p.script);'
Salida (primeras líneas)
"use strict";
(() => {
  // greengames:sdk
  var g = greengames;
  var juego = g.juego;
  var mundo = g.mundo;
  var jugadores = g.jugadores;
  var interfaz = g.interfaz;
  var sonido = g.sonido;
  var progreso = g.progreso;
  var puntajes = g.puntajes;
  var azar = g.azar;
  var reloj = g.reloj;
  var actores = g.actores;
  var camara = g.camara;

  // games/patio-de-monedas/scripts/principal.ts
  var SEGUNDOS = [90, 60, 45][Math.max(0, juego.nivelDificultad)];
  var cronometro;
  juego.alIniciar(() => {
    cronometro = interfaz.cronometro("tiempo", { cuentaRegresiva: SEGUNDOS, alTerminar: () => terminar("derrota") });
    cronometro.iniciar();

Las primeras líneas son el preámbulo que pone la herramienta que unió el script: saca los servicios del global greengames. Este paquete nombra los once servicios. Un paquete exportado por el editor 0.1.0 nombraba nueve: no traía actores ni camara. Los paquetes hechos hasta la versión 0.2.0 nombran ese objeto global con el nombre anterior, que la sala sigue ofreciendo: corren sin cambios.

Ver los recursos#

sh
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); for (const [ruta, r] of Object.entries(p.recursos)) console.log(ruta, r.tipo, Math.round(r.datos.length * 3 / 4) + " bytes");'
Salida
recursos/portada.jpg image/jpeg 25692 bytes

Qué pasa al abrir un .ggx#

Quien abre el archivo (el portal, el launcher o el editor) hace siempre lo mismo, en este orden:

  1. Comprueba que el archivo no pese más de 5 MB.
  2. Comprueba que empiece con los bytes de gzip. Si no: "El archivo no es un .ggx".
  3. Lo descomprime, cortando si pasa de 20 MB. Un archivo chico que se infla sin fin no llega a ocupar memoria.
  4. Lee el JSON y comprueba que gbx sea 1.
  5. Valida el paquete completo con las mismas reglas que usa greengames validar. Un paquete inválido no se juega: se rechaza con sus motivos.

El launcher valida dos veces antes de guardar un archivo importado, y guarda la huella SHA-256 del archivo para comprobarla cada vez que lo abre.

Límites de tamaño#

QuéMáximo
El archivo .ggx5 MB
El paquete descomprimido20 MB
El script unido512 KB
Un recurso2 MB
Escenas16
Modelos, texturas y sonidos, sumados12 MB

Los juegos del catálogo quedan lejos de esos topes: empaquetados con la herramienta, Circuito Luz Verde pesa 9,0 KB y Semáforo 21,5 KB. El ejemplo de esta página pesa 24 KB porque su portada es una imagen JPG de 25 KB.

Todos los demás topes: Límites.

Compatibilidad#

Dos números identifican el contrato: formato: 1 en el manifiesto, las escenas y el guion, y greengames.version === 1 en la API de scripts.

  • Los cambios dentro del formato 1 son aditivos: propiedades y funciones nuevas, siempre opcionales. Un juego hecho antes sigue validando y corriendo igual.
  • Un cambio que rompa juegos existentes exige formato: 2, y el reproductor seguirá abriendo los paquetes de formato 1.

Lo aditivo tiene una consecuencia práctica: un .ggx de formato 1 puede usar funciones que un reproductor más viejo todavía no tiene. Pasa con el launcher 0.1.0, que trae la primera versión del contrato: un script puede llamar a un servicio que ese launcher no conoce. Y pasa con el launcher 0.2.0, que trae los once servicios pero no lo que agregó la versión 1.1: el terreno de cubos, el puntero y la barra rápida. Y pasa con el launcher 0.3.0, que no trae los modelos, las texturas ni los sonidos grabados de la versión 1.2. El portal, el launcher 0.4.0 y el editor 0.4.0 traen el contrato completo. Qué se agregó: Disponibilidad.

No se admiten propiedades desconocidas: una clave que el formato no define es un error del validador, no algo que se ignora.

Sigue con#