El código que genera
El editor de escenas no necesita nada especial de la VM: cada sala se traduce a código JARU normal, que tu juego importa y llama. Por eso funciona igual en Windows y en todas las placas.
Esta página cuenta qué ficheros escribe, qué funciones tiene cada sala y cómo se mezclan con tu código. El recorrido de un juego entero, con su bucle paso a paso, está en Tu primer juego con salas.
En los nombres de esta página, <Sala> es el nombre de la sala (su campo Name, ver La sala y el juego) y <SALA>, el mismo en mayúsculas. Para la sala Fase1 son Fase1Load y FASE1_WORLD_W.
Los ficheros que escribe
| Fichero | Qué lleva | Cuándo se escribe |
|---|---|---|
Code/<Sala>.gen.aru | Una sala: sus tablas, su cargador y sus funciones. | Al guardar la sala (Ctrl+S), con el Play y en cada Build, si tiene Generate .aru encendido. Generate (Ctrl+G) lo escribe aunque esté apagado. |
Code/SceneRuntime.gen.aru | Lo que comparten las salas: las variables del juego, los controles, el ritmo y el código de los comportamientos que usan. | Con cada sala. Si el Build no genera ninguna sala, lo quita. |
Code/Drivers/DRV_SEESAW_GAMEPAD.aru | El driver del mando I2C. | La primera vez, con el mando encendido en la pestaña Game. Después no se toca. |
Code/_Play_<Sala>.gen.aru y Code/_Play_Display_.gen.aru | El programa del Play: abre la pantalla y juega la sala. | En cada Play. No es parte del proyecto, y el siguiente Play lo borra. |
Build/flash/Scenes/World.gmap | Todas las salas, como datos. | En cada Build, solo en modo datos. |
El Build es Build › Compile, o el botón Compile del editor de escenas. Lo que hace con las salas sale en Output: las que genera, las que se salta y los avisos del panel de problemas de cada una.
Los .gen.aru y el runtime aparecen en la carpeta Code del explorador, como tu código.
El IDE vuelve a escribir los .gen.aru al guardar cada sala y en cada Build: lo que cambies en ellos se pierde. Tu código va en tus propios .aru, en el código de creación de la sala o en el código de cada objeto.
Las funciones de cada sala
| Función | Cuándo existe | Qué hace |
|---|---|---|
<Sala>Load(spawn) | Siempre | Monta la sala y devuelve room. spawn puede ser nil: ver El gancho spawn. |
<Sala>FollowCamera(room) | Si Room › Camera › Follow tiene un objeto | Hace que la cámara siga a ese objeto. Una vez, después de <Sala>Load. |
<Sala>Update(room) | Si la sala tiene comportamientos, reglas, estados, plantillas, variables o código en algún objeto | Un fotograma de lo que se mueve solo: el mando I2C, el reloj de la sala, los estados, los comportamientos y las reglas. En cada fotograma, antes de room.scene.update(). |
<Sala>Resolve(room) | Siempre | Saca los cuerpos dinámicos de los tiles sólidos del mapa de colisión, y deja dentro de la sala los que tienen Keep in room. En cada fotograma, después de room.scene.update(). Devuelve lo que devuelve grid.hits, o nil si la sala no tiene mapa de colisión. |
<Sala>Draw(room) | Siempre | Pinta todas las capas en el orden del editor, con su parallax, sus formas y su decorado, y encima de todo los textos de Show a variable. Úsala en vez de Draw.scene, que solo pinta el mapa de escena y los sprites. |
<Sala>Zone(name) | Si la sala tiene zonas o formas, y tu código nombra <Sala>Zone | Busca una zona o una forma por su nombre. Devuelve su fila, [name, tag, kind, shape, x, y, w, h, color, filled], o nil. |
Llamar a una función que no existe compila, pero el juego se para al llegar a esa línea.
La sala tiene más funciones (<Sala>Events, <Sala>Spawn, <Sala>Code0, <Sala>Fsm0_Step…): son las reglas, las plantillas, el código de los objetos y los estados. Las llama la propia sala, no tú.
Las constantes y las tablas
| Nombre | Qué es |
|---|---|
<SALA>_WORLD_W, <SALA>_WORLD_H | Room (px) |
<SALA>_TILE_W, <SALA>_TILE_H | Tile size |
<SALA>_VIEW_W, <SALA>_VIEW_H | Screen (px) |
<SALA>_MUSIC, <SALA>_COLLMAP | La ruta de la música y la del mapa de colisión. |
<SALA>_TAG_<ETIQUETA> | Cada etiqueta de la sala, como texto. |
<SALA>_I_<OBJETO> | El número de cada objeto con nombre en la tabla de objetos. |
<SALA>_ENTITIES, <SALA>_DECOR, <SALA>_ZONES | Las tablas de los objetos, del decorado y de las zonas y formas. |
Solo se escriben las constantes que nombra tu código o el de la propia sala: en la placa, cada constante gasta RAM aunque no se use. Las demás se quedan en un comentario, con su valor. Nombra una en tu código y la próxima vez que se genere la sala estará. Lo mismo pasa con <SALA>_ZONES y <Sala>Zone.
Lo que lleva room
| Campo | Qué es |
|---|---|
room.scene | El Scene de la sala, con su física, su cámara y sus sprites. Ver el módulo Scene. |
room.grids | Un GridMap por cada capa de tiles. |
room.collision | El GridMap del mapa de colisión, o nil. |
room.sprites | Los sprites de los objetos colocados. |
room.named | Los objetos con nombre: room.named["jugador"] es su sprite. |
room.game | Las variables del juego. Es el mismo objeto en todas las salas. |
room.nextScene | La sala que ha pedido una regla Ask for another room, o "". |
room.vars | Las variables de la sala. |
room.tagged | Los sprites vivos de cada etiqueta, en un mapa: etiqueta → lista. |
room.dt, room.elapsed | Los segundos de este fotograma (salen de Display.dt, con 0,1 como mucho) y los que lleva cargada la sala. |
Las tres últimas filas solo están en una sala con <Sala>Update.
El runtime compartido
Cada sala importa Code/SceneRuntime.gen.aru. Importarlo desde varias salas no cuesta nada: el preprocesador se queda con el primero.
| Nombre | Qué es | Cuándo está |
|---|---|---|
sceneGame | Las variables del juego: sceneGame.lives. Es el mismo objeto que room.game. | Siempre |
SceneInputMap() | Mapea los controles de la pestaña Game: el GPIO en cada placa, el mando I2C y, en Windows, las teclas. Nadie la llama por ti: llámala una vez, al arrancar. | Siempre |
SceneInputRead() | Lee el mando I2C. Llámala en cada fotograma donde no corre ninguna sala, como un título o un fin. | Si tu código la nombra |
SceneGameReset() | Una partida nueva: todas las variables del juego vuelven a su valor de salida. | Si la usa tu código o una regla |
SceneGameLoseLife() | Una vida menos, y health vuelve a su valor de salida. | Si la usa tu código o una regla |
SceneGameClamp() | Deja health entre 0 y maxHealth. Llámala si tu código cambia alguna de las dos. | Si la usa tu código o una regla |
SCENE_GAME_HEALTH0 | La salud con la que empieza cada vida. | Si la usa tu código o una regla |
Además lleva:
Display.fps, si Rate (fps) de la pestaña Game no es 0. Se pone una vez, al arrancar.- Lo del mando I2C, si está encendido.
- El código de los comportamientos y de las ayudas que usan las salas, una sola vez.
Lo que nadie nombra no se escribe: en la placa, cada función y cada constante gasta RAM aunque no se use. Un comentario del propio fichero nombra las que faltan. Para tener una, nómbrala en tu código (vale hasta un comentario) y la próxima vez que se genere estará.
El gancho spawn
<Sala>Load(spawn) recibe una función, o nil. Si le das una, la sala la llama con cada objeto (e) antes de crearlo, y lo que devuelve decide qué pasa:
| Devuelve | Qué pasa |
|---|---|
| Un sprite | La sala usa ese sprite para el objeto: le pone su posición, su volteo, su giro, su fotograma, su tipo de cuerpo y, si lo tiene, su collider propio; lo mete en la escena, en room.sprites y en room.named, y le engancha sus comportamientos. |
false | La sala no crea el objeto ni sus comportamientos: es todo tuyo. |
nil | La sala lo crea como siempre, con su sprite o su imagen. |
Pasan por spawn los sprites y las imágenes visibles de las capas de objetos. No pasan las formas, las zonas, las rutas, el decorado ni las copias de una plantilla.
e es su fila de la tabla de objetos:
| Campo | Qué es |
|---|---|
e.name, e.tag | Su nombre y su etiqueta. |
e.kind | "sprite" o "image". |
e.res | La ruta de su sprite o de su imagen. |
e.x, e.y | Su posición en la sala: el pivote de un sprite, el centro de una imagen. |
e.w, e.h | Su ancho y su alto. |
e.angle, e.flipX, e.flipY, e.frame | Su giro, su volteo y su fotograma de salida. |
e.layer | El número de su capa. |
e.body | Su tipo de cuerpo, el valor de sprite.bodyType. |
e.coll | Su collider propio, [x, y, w, h], o nil. |
e.props | Sus propiedades (PROPERTIES), en un mapa, o nil. |
Un ejemplo: el juego crea los enemigos y se los guarda en su lista, la meta la lleva él y lo demás lo crea la sala.
import "Pantalla.aru" // abre la pantalla: va el primero
import "Fase1.gen.aru"
use Sprite
var enemigos = []
// La sala la llama con cada objeto, antes de crearlo.
func CrearObjeto(e)
if (e.tag == "enemigo") then
var s = Sprite.load(e.res)
enemigos.append(s) // el juego los tiene en su propia lista
return s // la sala lo coloca y lo mete en la escena
end
if (e.tag == "meta") then
return false // la sala no lo crea: es del juego
end
return nil // lo demás lo crea la sala
end
var room = Fase1Load(CrearObjeto)
En modo datos, SceneLoad(name, spawn) usa spawn igual.
Con tu código
Tu primer juego con salas cuenta el bucle de un juego con varias salas, el cambio de sala y las pantallas de título y de fin. Aquí va lo que hay que saber para escribir el tuyo.
Al arrancar:
- Abre la pantalla lo primero, con
Display.openen un fichero pequeño tuyo que se importa el primero. La cabecera de cada.gen.aruexplica por qué, y la guía lo enseña paso a paso. - Llama una vez a
SceneInputMap(): el Play lo hace, pero tu juego tiene que hacerlo él.
Al entrar en una sala: <Sala>Load y, si existe, <Sala>FollowCamera.
En cada fotograma, en este orden:
<Sala>Update(room), si existe: los comportamientos dan velocidad a los cuerpos, y la física de este mismo fotograma la usa.room.scene.update(): la física, la cámara y las animaciones.<Sala>Resolve(room): saca a los cuerpos de los tiles en los que la física los acaba de meter.<Sala>Draw(room).Display.update(). Ni las salas ni el runtime lo llaman. Es el que espera al siguiente fotograma, al ritmo de Rate (fps).
Para cambiar de sala, lee room.nextScene: la regla Ask for another room solo escribe ahí el nombre que pide, y tu bucle decide cuándo cargarla. (this room) pide la misma sala, para empezarla otra vez.
import "Pantalla.aru" // abre la pantalla: va el primero
import "Fase1.gen.aru"
use Display
SceneInputMap() // los controles de la pestaña Game
var room = Fase1Load(nil)
Fase1FollowCamera(room) // si la cámara sigue a alguien
while (true)
Fase1Update(room) // si la sala tiene comportamientos o reglas
room.scene.update() // la física, la cámara y las animaciones
Fase1Resolve(room) // saca los cuerpos de los tiles sólidos
Fase1Draw(room) // todas las capas
Display.update() // presenta y espera al siguiente fotograma
if (room.nextScene != "") then
// Una regla ha pedido una sala. Aquí solo hay una: se empieza otra vez.
room = Fase1Load(nil)
Fase1FollowCamera(room)
end
end
Dónde va tu código:
| Dónde | Cuándo corre |
|---|---|
Tus .aru | Cuando los llames: el bucle, los menús y las funciones de la acción Call a function of yours. |
| Room › Creation code... | Una vez, al final de <Sala>Load, con la sala montada. Ver La sala y el juego. |
| El código de un objeto | Una vez, al crear el objeto y cada copia de su plantilla. Ver Objetos. |
Las funciones que llama Call a function of yours van en un .aru del proyecto, fuera de cualquier otra función, y reciben (spr, other, room): ver Reglas y estados.
Cada control del juego ocupa un número de acción de Input, y la ventana Game controls dice cuál. Si tu juego mapea más acciones con Input.map, usa números que no tenga ningún control.
En un título o un fin, donde no corre ninguna sala, las variables del juego son sceneGame, SceneGameReset() empieza una partida nueva y, con el mando I2C, SceneInputRead() lo lee en cada fotograma.
Las salas como datos
Como código, cada sala es su .gen.aru y el juego lleva el código de todas. En una placa, eso gasta RAM aunque la sala no se esté jugando.
Como datos, el Build escribe todas las salas en una tabla y un solo cargador las carga por su nombre. En memoria solo está la sala que se juega, y lo que sale en todas las salas (el mismo sprite o la misma imagen) se carga una vez y se queda.
Cuándo se usa
El modo datos se enciende cuando el código de tu juego nombra SceneLoad: cualquier .aru del proyecto que no sea un .gen.aru. Cuenta también un comentario o una cadena. Si no lo nombra, las salas son código, como siempre.
El Build lo dice en Output: Data mode: the game names SceneLoad, so every scene also goes to Build\flash\Scenes\World.gmap as data.
Qué escribe el Build
Build/flash/Scenes/World.gmap: todo lo de todas las salas con Generate .aru encendido, menos sus mapas de tiles. Es una tabla de bytes: no se edita.- Los mapas de tiles siguen en sus
.gmap, con los demás mapas, enBuild/flash. De cada mapa solo se exporta la mitad que se usa: los tiles, los valores de celda o las dos. - El cargador, en
SceneRuntime.gen.aru. - Las salas que son solo datos no tienen
.gen.aru: el Build quita el que tuvieran. El Play escribe el suyo cuando le hace falta. - Una sala se queda su
.gen.arusi el juego lo importa, o si usa algo que solo funciona como código (ver Lo que no hace como datos).
Las funciones
Tu juego no importa ninguna sala: importa SceneRuntime.gen.aru, que trae estas funciones. Como las del runtime, solo se escriben si tu código las nombra.
| Función | Qué hace |
|---|---|
SceneLoad(name, spawn) | Carga la sala name y devuelve room, o nil si no está (y lo dice en la consola). name es el nombre del fichero .scn, sin la extensión y con sus mayúsculas. spawn, como en <Sala>Load. |
SceneUpdate(room) | Un fotograma de las reglas de la sala y del mando I2C. Antes de room.scene.update(). Solo existe si alguna sala tiene reglas que van como datos, sin contar When the room starts. |
SceneResolve(room) | Como <Sala>Resolve. |
SceneDraw(room) | Como <Sala>Draw. |
SceneFollowCamera(room) | Como <Sala>FollowCamera. En una sala que no sigue a nadie, no hace nada. |
SceneZone(room, name) | Como <Sala>Zone. |
import "Pantalla.aru" // abre la pantalla: va el primero
import "SceneRuntime.gen.aru" // trae el cargador de las salas
use Display
SceneInputMap()
var room = SceneLoad("Fase1", nil)
SceneFollowCamera(room)
while (true)
SceneUpdate(room) // solo si hay reglas que van como datos
room.scene.update()
SceneResolve(room)
SceneDraw(room)
Display.update()
if (room.nextScene != "") then
room = SceneLoad(room.nextScene, nil)
SceneFollowCamera(room)
end
end
Lo que no hace como datos
- Comportamientos, estados, plantillas, variables de la sala, código de creación y código de los objetos. Una sala que los usa se queda su
.gen.aru, y el Build avisa en Output:as data, this room does not run its … yet. Para tenerlos, importa su.gen.aruy cárgala con<Sala>Load. - Reglas: valen todas menos When it leaves the room.
- Acciones: valen Ask for another room, Change a variable (solo con variables del juego), Play a sound effect, Call a function of yours, Destroy, Lose a life y Start a new game. Las demás se quedan fuera, con un aviso en Output.
- La música de Room › Music no suena (ver Problemas conocidos).
- Solid values y One-way values van de 0 a 255: los demás no cuentan, y el panel de problemas lo avisa.
- El Play de una sala solo juega esa sala: no pasa a las que pide.
Problemas conocidos
- En modo datos,
SceneUpdateyroom.nextScenepueden no existir.SceneUpdatesolo está si alguna sala tiene reglas que van como datos (sin contar When the room starts), yroom.nextScene, si alguna tiene reglas. Sin ellas, el juego se para al llegar a esas líneas (Undefined variable). Quítalas de tu bucle mientras tus salas no tengan reglas, y si usas el mando I2C, llama aSceneInputRead()en cada fotograma: solo lo leeSceneUpdate. - En modo datos, importa las salas con comillas dobles y con su extensión:
import "Fase1.gen.aru". Con comillas simples o sin.gen.aru, el Build no ve que la importas, borra ese fichero y el juego no compila. - En modo datos, la música de Room › Music no suena:
SceneLoadsolo deja su ruta enroom.music. Si no está vacía, ponla tú después de cargar la sala, conSound.loadMusic(room.music)ySound.playMusic(true); antes, llama aSound.init(), también en Windows. - En modo datos, el primer Play de una sala que es solo datos, después de cambiar un recurso (una imagen, un sprite, un mapa o una música), puede fallar porque no encuentra su
.gen.aru. Pulsa Play otra vez. - Nombrar
SceneLoaden cualquier.arudel proyecto, aunque sea en un comentario, enciende el modo datos. Si no lo quieres, no escribas ese nombre. - Generate .aru apagado en una sala que tu juego importa: el Build deja su
.gen.arucomo estaba y puede quitar del runtime lo que esa sala necesita. El juego compila y se para al ejecutar. Apágalo solo en salas que el juego no importa. - La cabecera de cada
.gen.aruno nombra<Sala>FollowCameraniDisplay.update(). Sigue el orden de esta página, en «En cada fotograma». - Si el Build de los recursos falla (una sala que no se puede generar, por ejemplo), el botón Compile del editor deja la barra de estado en
Compiling the game... (the details go to Output). El error está en Output.