Scripts
What a game's script is like: where it runs, how the API is imported, the eight rules, a skeleton, selectors, translatable texts, several players and what the API does not offer yet.
DescribesFormat 1, API 1 For those who write code
On this page
The script is the game's logic: what happens when someone touches a coin, enters the goal, presses a button or runs out of time. You write it in TypeScript or JavaScript, in the file named by script in the manifest (usually scripts/principal.ts).
The API names are in Spanish (juego.alIniciar, mundo.alTocar) and stay that way in every language. The code on this page is quoted as it is in its sources.
Where it runs#
The script is a program that runs inside the room: the simulation of the match, which decides everything that happens (physics, players, scores). The room runs isolated, in a Web Worker inside a sandboxed iframe. There is no page there, no network, no site storage, no browser timers and no eval. Only standard JavaScript and the greengames object with the API services exist.
That has two practical consequences:
fetch,document,window,localStorage,setTimeoutandsetIntervaldo not exist. Naming them is a validator error and a type error.- Everything the game wants to do (show a text, play a sound, save a record) it asks of a service. Why it is like this: Isolation.
The import and the greengames global#
import { juego, mundo, jugadores, interfaz, sonido, progreso, puntajes, azar, reloj, actores, camara } from '@greengames/sdk';Import only the services you use. Inside the .ggx the script ends up as classic JavaScript and those names come from the global greengames object. A script written by hand, with no tools, can start like this:
const { juego, mundo, jugadores } = greengames;| Service | What for | Reference |
|---|---|---|
juego | Start, every frame, finish, change scene, log | juego |
mundo | Pieces, zones, spawn points, lights, tile maps, touches, rays | mundo |
jugadores | Who is there, their actions, their falls, their state | jugadores |
interfaz | Texts, HUD, stopwatches, messages, scoreboard, menus | interfaz |
sonido | Preset sounds, tones and note sequences | sonido |
progreso | Save the player's data between matches | progreso |
puntajes | Local score tables | puntajes |
azar | Random numbers that come from the seed | azar |
reloj | Match time and timers | reloj |
actores | Characters the script drives: rivals, neighbors, enemies | actores |
camara | Each player's view and camera, and temporary shots | camara |
All eleven services, with everything that versions 1.1 (the cube terrain, the pointer and the hotbar) and 1.2 (models, textures and recorded sounds) of the contract add, are in the portal, in the greengames tool, in launcher 0.4.0 and in editor 0.4.0. The earlier apps had less: the 0.3.0 ones, without the version 1.2 additions; the 0.2.0 ones, the eleven services without the version 1.1 additions; the 0.1.0 ones, nine services. What each one lacked is in Availability.
The eight rules of the script#
- It can only import
@greengames/sdkand relative files of the game itself (./niveles.ts). - The whole API is synchronous. There is no
async, noawaitand no promises to wait for. - To wait, use
reloj.despues(segundos, fn). To repeat,reloj.cadaorjuego.cadaCuadro. - For randomness, use
azar, which comes from the match's seed.Math.randominside the room also comes from the seed, but the validator warns. - There is no "the player": there is a list. Each event brings the player who caused it, and whatever is shown or played accepts
para: jugador. That way the game already works when there are bots in the match and when several people play on the same computer: see Several players. - Each player's own state is kept in
jugador.datos. - Register events inside
juego.alIniciar. The changes of one frame are seen in the next. - Every match must end with
juego.terminar({ resultado, puntaje }).
A skeleton to start from#
This skeleton is the one from the Green Games author's guide. It passes greengames validar and greengames probar on a scene that has pieces with the tag moneda, a zone meta and a table puntos in the manifest.
import { interfaz, juego, jugadores, mundo, sonido, type Jugador } from '@greengames/sdk';
const AJUSTES = [{ segundos: 60 }, { segundos: 45 }, { segundos: 35 }][Math.max(0, juego.nivelDificultad)];
juego.alIniciar(() => {
jugadores.alEntrar((j) => { j.datos = { vidas: 3 }; });
mundo.alTocar('#moneda', (moneda, j) => { moneda.eliminar(); j.puntos++; sonido.tocar('moneda', { para: j }); });
mundo.alEntrar('meta', (_zona, j) => juego.terminar({ resultado: 'victoria', puntaje: j.puntos, tabla: 'puntos', de: j }));
});How to read it:
AJUSTESpicks a row according to the difficulty.juego.nivelDificultadis the index of the difficulty in the manifest (0, 1, 2), or -1 if the game declares no difficulties: hence theMath.max(0, ...).jugadores.alEntrarruns for each player, including those who were already there. It is the place to give each one their initial state.mundo.alTocar('#moneda', ...)handles all the pieces with that tag. It receives the touched piece and the player who touched it.juego.terminarwithtablaandderecords that player's score in that table.
A complete, commented 60-line script comes in the template of greengames crear. A bigger one, explained part by part: Case study.
Habits that prevent errors#
Register everything inside
juego.alIniciar. Top-level code runs before the match is ready.Per-player state in
j.datos; match state in variables of the file.Texts always with
interfaz.t('clave', { variables }), and the key intextos.esandtextos.enof the manifest. No text written directly in the script.Time with
relojandinterfaz.cronometro; randomness withazar. NosetTimeout,DateorMath.random.To move a piece, assign a new vector:
pieza.posicion = [x, y, z], or usepieza.moverA(...). Readingpieza.posiciongives a copy:pieza.posicion[0] = 5moves nothing.Always finish:
juego.terminar({ resultado, puntaje, tabla }).A menu does not pause the match. If nobody should move while it is open, freeze the players (
j.congelado = true).On phones, long texts get cut off. A HUD chip comfortably shows about 24 characters per line.
Characters that are not players:
actores.crear(...), not boxes animated by hand.Several pieces that move together (a door, a vehicle): the same
grupoproperty in the scene andmundo.grupo(nombre).Text over something in the world (a tower's health, the name of a place):
interfaz.rotulo. Numbers that pop up when you collect or hit:interfaz.flotante.In a puzzle seen from above, fix the camera:
camara.fijar({ inclinacion: 70, distancia: 24, libre: false }). If the whole game looks like that, declare the view in the scene: Views.
Selectors#
The world events (mundo.alTocar, mundo.alDejarDeTocar, mundo.alEntrar, mundo.alSalir) take a selector:
| Selector | Example | What it responds to |
|---|---|---|
| An id | 'meta' | That piece or that zone. |
| A tag, with a hash sign | '#moneda' | All the pieces or zones that have that tag. |
Tags are evaluated at the moment of the touch. You can tag and untag pieces while the game is played (pieza.etiquetar('peligro')) and the event picks them up or drops them at once.
alTocar fires when a player starts touching a piece, solid or not; standing on it counts. A piece with "fisica": "ninguna" does not report touches: for something you pass through but that still reports, use "solida": false.
Translatable texts#
An interface text can be three things:
| Form | Example | When |
|---|---|---|
| A key of the manifest | interfaz.t('monedas', { n: 3, total: 6 }) | Whenever you can. Each game player shows it in the player's language. |
| An object with both languages | { es: 'Meta', en: 'Finish' } | Texts that are not in the manifest. |
| A plain string | '3', jugador.nombre | Only for numbers and proper names. |
interfaz.t returns an object, not a string. Do not concatenate it: interfaz.t('ganaste') + ' algo' shows [object Object]. Put the whole sentence in the template, with its variables: "monedas": "{n} de {total}".
Several players#
A game declares in the manifest how it is played by several people: multiplayer. With multijugador.local, 2 to 4 people play on the same computer, with a split or a shared screen. One script serves one person, several people and the bots if it follows these rules, which are the usual ones:
| What | Where it lives | How it is shown |
|---|---|---|
| One person's state (lives, coins, a key) | jugador.puntos, jugador.datos, jugador.equipo | With para: jugador (HUD, messages, sounds, menus, camera) |
| Everyone's state (clock, round, an open door) | Variables of the script, pieza.datos | Without para |
| Who is there | jugadores.todos(), never "the player" | interfaz.marcadorDePuntos, interfaz.marcador |
- Joining and leaving mid-match.
jugadores.alEntrar(fn)runs for those already there and for each one who arrives later;jugadores.alSalir(fn)tells you when someone leaves. Do not keep your own lists of players: ask every time. - Teams.
jugador.equipo = 'rojo'(free text); actors haveequipotoo. - Views. They are per player: on a split screen each part can be in a different view.
- Shared screen. Only the first player's HUD and whatever is for everyone are shown; show each player's own data with the scoreboard or with labels over the avatars.
- Controls on the same computer. With gamepads, one per person. Without enough gamepads, the first two players share the keyboard (W A S D with Space, E and Q; the arrow keys with right Shift, Enter and right Ctrl) and whatever gamepads there are go to the last players. The mouse and the touch screen belong to the first player.
To test it: greengames probar <slug> --jugadores 4 runs the match with four players that follow the bot script, and in the preview ?auto=1&locales=2 opens the split screen. In the editor: Local multiplayer.
What survives a scene change#
juego.cambiarEscena(id) unloads the scene, loads the other one on the next frame, takes the players to its first spawn point and calls whatever was registered with juego.alCambiarEscena.
| Kept | Lost |
|---|---|
All registered events (alTocar, alEntrar, cadaCuadro, alAccion...): those with a selector are applied again to the matching pieces and zones of the new scene | The pieces, zones, lights, actors and labels of the previous scene, including those created by script |
Timers (reloj.despues, reloj.cada) and stopwatches | Events registered with pieza.alTocar(fn): they go with the piece |
| HUD, scoreboard, open menus and sounds | Groups, which belong to the pieces |
Players: datos, puntos, rapidez, salto, progress | Each player's aparicion: it becomes the first one of the new scene |
Signals from juego.senalar and camera settings | The environment and the view: they take those of the new scene |
What the API does not offer yet#
So that you do not waste time looking for it.
- Piece hierarchy. There are groups (
mundo.grupo) to move, rotate and hide several pieces together, but no "child" piece that follows another on its own. - Particles and trails. You simulate them with small pieces or with
interfaz.flotante. - Camera shake.
- Objects held in the hand. A player or an actor drawn with a model only uses its clips
quieto,caminar,saltarandcaer; other clips play on pieces with a model, not on the character. - Physical collision between characters. Players and actors pass through each other; you detect them with events.
- Actors that jump gaps or use platforms on their own. They walk and go around; they do not plan jumps.
- Free input: arbitrary keys, the position of the mouse arrow over the world, typed text. There is movement, camera, three actions and, since version 1.1, two more (
primariaandsecundaria, the mouse clicks) with a pointer that aims through the crosshair at the center of the screen: Players service. - HUD with a custom layout: grids, images, on-screen game buttons outside the menus.
- Chat, friends and global tables. There is no server.
- In tile maps: tiles that animate on their own, and slopes.
3D models, image textures and recorded sounds are there, since version 1.2 of the contract: Models, textures and sounds.
Which of these are in development: Roadmap.
Behaviors worth knowing#
- A piece created with
mundo.crearexists at once, but touches, rays,mundo.piezasEnand collisions see it from the next frame. - Moving platforms carry players and actors, but not the dynamic pieces on top of them.
- A platform that moves sideways pushes the player only approximately; one that comes down on them does not crush them. To crush or push for certain, use a danger tag and
jugador.reaparecer(). - The
rotacionof a dynamic piece is approximate: it comes from its physical orientation. mundo.rayohits only solid pieces and ignores players.- The camera treats each piece as its bounding box when deciding whether it blocks the view;
cristalpieces and small ones do not count. If it gets in the way next to tall pieces:camara.fijar({ evitarObstaculos: false }). - Labels are drawn on top of the world: pieces in front of them do not cover them.
Next#
- API reference: each service, function by function.
- Testing with the bot: check that the game can be played and finished.
- Good practices: the checklist and the common errors.