Módulo Scene
El módulo Scene proporciona un sistema completo de gestión de escenas para juegos y aplicaciones interactivas en JARU. Una escena agrupa sprites, gestiona una cámara con seguimiento suave y límites configurables, integra física con gravedad y resolución de colisiones, e incorpora un GridMap opcional para mapas de tiles.
La escena actúa como contenedor central que coordina la actualización de física, el movimiento de cámara y el culling de sprites fuera de pantalla, simplificando enormemente el desarrollo de juegos con scroll.
Uso
use Display
use Sprite
use Scene
Funciones del Módulo
new
La función Scene.new() crea una nueva escena vacía con valores por defecto.
var scene = Scene.new()
La escena se crea con:
- Cámara en posición (0, 0)
- Sin gravedad
- Sin límites de cámara
- Sin sprites ni GridMap asignados
- Modo de cámara CLAMP en ambos ejes
- 3 iteraciones del solver de física
Constantes
Modos de Cámara
| Constante | Valor | Descripción |
|---|---|---|
Scene.CLAMP | 0 | La cámara se detiene en los límites |
Scene.FREE | 1 | La cámara se mueve libremente sin restricciones |
Scene.WRAP | 2 | La cámara envuelve (movimiento toroidal) |
Propiedades de Instancia
gravity
Obtiene o establece la gravedad de la escena en píxeles/segundo².
// Obtener gravedad actual
var g = scene.gravity
// Establecer gravedad (simula caída)
scene.gravity = 500
// Sin gravedad (juego espacial)
scene.gravity = 0
iters
Obtiene o establece el número de iteraciones del solver de física. Más iteraciones producen colisiones más precisas pero consumen más CPU.
// Obtener iteraciones actuales
var i = scene.iters
// Establecer más iteraciones para mayor precisión
scene.iters = 5
// Valor mínimo: 1
scene.iters = 1
El valor por defecto es 3, que ofrece un buen equilibrio entre precisión y rendimiento.
Métodos de Instancia
Gestión de Sprites
addSprite
Añade un sprite a la escena. Los sprites añadidos participan en la física y se actualizan con update().
var player = Sprite.load("player.spr")
scene.addSprite(player)
| Parámetro | Tipo | Descripción |
|---|---|---|
sprite | Sprite | Sprite a añadir |
Retorno
Devuelve true si se añadió correctamente, false en caso de error.
removeSprite
Elimina un sprite de la escena.
scene.removeSprite(enemy)
| Parámetro | Tipo | Descripción |
|---|---|---|
sprite | Sprite | Sprite a eliminar |
Retorno
Devuelve true si se eliminó correctamente, false si no se encontró.
clearSprites
Elimina todos los sprites de la escena.
scene.clearSprites()
spriteCount
Obtiene el número de sprites en la escena.
var count = scene.spriteCount()
println("Sprites en escena: ", count)
Retorno
Devuelve un entero con la cantidad de sprites.
GridMap
setGridMap
Asigna un GridMap a la escena. Se utiliza para calcular los límites de cámara automáticos con setBoundsFromGrid().
var map = GridMap.new(20, 15)
scene.setGridMap(map)
| Parámetro | Tipo | Descripción |
|---|---|---|
gridmap | GridMap / nil | GridMap a asignar, o nil para desasignar |
getGridMap
Obtiene el GridMap asignado a la escena.
var map = scene.getGridMap()
if (map != nil) then
println("GridMap asignado")
end
Retorno
Devuelve el GridMap asignado, o nil si no hay ninguno.
Cámara - Posición Manual
setCamera
Establece la posición de la cámara directamente. Al llamar a este método, se desactiva el seguimiento automático (follow).
scene.setCamera(100, 50)
| Parámetro | Tipo | Descripción |
|---|---|---|
x | number | Posición X de la cámara |
y | number | Posición Y de la cámara |
getCamera
Obtiene la posición actual de la cámara.
var cam = scene.getCamera()
println("Cámara en: ", cam[0], ", ", cam[1])
Retorno
Devuelve una lista [x, y] con la posición de la cámara.
Cámara - Seguimiento
follow
Configura la cámara para seguir automáticamente a un sprite. Opcionalmente se puede especificar un factor de suavizado.
// Seguimiento instantáneo
scene.follow(player)
// Seguimiento suave (0.1 = muy suave, 1.0 = instantáneo)
scene.follow(player, 0.1)
| Parámetro | Tipo | Descripción |
|---|---|---|
sprite | Sprite | Sprite objetivo a seguir |
smoothFactor | number | (Opcional) Factor de suavizado entre 0.0 y 1.0. Por defecto: 1.0 |
Un smoothFactor bajo (como 0.05 o 0.1) produce un efecto de cámara que se desplaza suavemente hacia el objetivo, muy utilizado en plataformas y juegos de aventuras. Un valor de 1.0 centra la cámara instantáneamente.
unfollow
Detiene el seguimiento automático de la cámara. La cámara se queda en su posición actual.
scene.unfollow()
Cámara - Límites
setBounds
Establece los límites manuales de la cámara. La cámara no podrá salir de esta región (dependiendo del modo de cámara).
scene.setBounds(0, 0, 640, 480)
| Parámetro | Tipo | Descripción |
|---|---|---|
minX | number | Límite izquierdo |
minY | number | Límite superior |
maxX | number | Límite derecho |
maxY | number | Límite inferior |
setBoundsFromGrid
Calcula y establece automáticamente los límites de la cámara a partir del GridMap asignado a la escena. Los límites se ajustan para que la cámara no muestre áreas fuera del mapa.
scene.setGridMap(map)
scene.setBoundsFromGrid()
Si los límites calculados resultan menores que cero (mapa más pequeño que la pantalla), la cámara se centra sobre el mapa automáticamente.
Este método también configura automáticamente el modo de cámara por eje: si el GridMap tiene wrap activado en un eje, el modo de cámara para ese eje se establece como FREE (la cámara se mueve sin restricciones y el GridMap gestiona el tileado toroidal).
Si no hay ningún GridMap asignado a la escena, setBoundsFromGrid() lanzará un error. Llama siempre a setGridMap() antes.
clearBounds
Elimina los límites de la cámara, permitiendo movimiento libre.
scene.clearBounds()
cameraMode
Obtiene o establece el modo de comportamiento de la cámara para los ejes X e Y de forma independiente.
Getter — devuelve una lista [modeX, modeY]:
var mode = scene.cameraMode()
println("Modo X: ", mode[0], " Modo Y: ", mode[1])
Setter — recibe una lista [modeX, modeY]:
// Ambos ejes con clamp
scene.cameraMode([Scene.CLAMP, Scene.CLAMP])
// Wrap horizontal, clamp vertical
scene.cameraMode([Scene.WRAP, Scene.CLAMP])
| Modo | Constante | Comportamiento |
|---|---|---|
| 0 | Scene.CLAMP | La cámara se detiene al llegar a los límites |
| 1 | Scene.FREE | La cámara se mueve libremente sin restricciones |
| 2 | Scene.WRAP | La cámara envuelve de un extremo al otro (toroidal) |
Los modos solo tienen efecto cuando hay límites activos (setBounds() o setBoundsFromGrid()). En modo FREE los límites se ignoran aunque estén definidos.
Actualización
update
Actualiza toda la escena: ejecuta el paso de física para todos los sprites, actualiza la posición de la cámara (seguimiento y límites) y recalcula el área de culling.
scene.update()
El orden interno de operaciones en cada llamada es:
- Guarda las posiciones previas de todos los sprites (para sweep de colisiones)
- Ejecuta
PhysicsStepcon la lista de sprites, gravedad e iteraciones del solver - Actualiza la posición de la cámara (follow + smoothing)
- Aplica los límites de cámara según el modo configurado
- Recalcula la región de culling visible
Llama a update() una vez por frame, antes de dibujar. Este método se encarga de toda la simulación de física, incluyendo la gravedad, velocidades, aceleraciones, colisiones y resolución de penetraciones.
Ejemplo Completo: Plataformas con Scroll
use Display, GridMap, Sprite, Scene, Input
const ACT_LEFT = 0
const ACT_RIGHT = 1
const ACT_JUMP = 2
var draw = Display.draw
const ANCHO = 320
const ALTO = 240
const VIEW_W = 280
const VIEW_H = 240
// ------------------------------------------------
// Pantalla
// ------------------------------------------------
Display.viewWidth = VIEW_W
Display.viewHeight = VIEW_H
Display.open(ANCHO, ALTO)
Display.orientation(0)
Display.mode(2)
draw.colorBG = 0x4040C0
Display.autoClear = true
Display.showBG = false
// ------------------------------------------------
// Controles
// ------------------------------------------------
Input.map(ACT_LEFT, "LEFT")
Input.map(ACT_RIGHT, "RIGHT")
Input.map(ACT_JUMP, "SPACE")
// ------------------------------------------------
// Tilemap
// ------------------------------------------------
var tileset = Bitmap.load("Images/TileSet02.bmp")
var grid = GridMap.load("Maps/prumap_tiles.gmap")
grid.tileset = tileset
grid.wrapX = true
grid.bouncy = true
grid.elasticity = 0.9
grid.solids = [15, 18, 23, 0]
grid.platforms = [13, 3]
// ------------------------------------------------
// Player
// ------------------------------------------------
var player = Sprite.load("Sprites/player.spr")
player.bodyType = Sprite.DYNAMIC
player.mass = 5
player.elasticity = 0.7
player.pivot = [8, 8]
player.x = 200
player.y = 20
player.friction = 0.1
player.velocity = [0, 0]
player.collider = [player.width div 2, player.height div 2, 6]
// ------------------------------------------------
// Escena
// ------------------------------------------------
var scene = Scene.new()
scene.setGridMap(grid)
scene.addSprite(player)
scene.gravity = 200
scene.follow(player, 0.1)
// ------------------------------------------------
// Bucle principal
// ------------------------------------------------
while (true)
var vel = player.velocity
if (Input.pressed(ACT_LEFT)) then
vel[0] = -120
elsif (Input.pressed(ACT_RIGHT)) then
vel[0] = 120
else
vel[0] = 0
end
if (Input.justPressed(ACT_JUMP) and player.onGround()) then
vel[1] = -250
end
player.velocity = vel
// Física de la escena
scene.update()
// Colisión contra el mapa
grid.hits(player)
// Render completo: mapa + sprites + cámara
draw.scene(scene)
// Debug
var cam = scene.getCamera()
draw.text(5, 5, "CAM: " + int(cam[0]).toString() + "," + int(cam[1]).toString(), 0xFFFFFF)
if (player.onGround()) then
draw.text(5, 15, "GROUND", 0x00FF00)
else
draw.text(5, 15, "AIR", 0xFF0000)
end
Display.update()
pause(16)
end
Consideraciones de Rendimiento
- Usa
spriteCount()para monitorear la cantidad de sprites activos en la escena. setBoundsFromGrid()solo necesita llamarse una vez al cargar el nivel, no en cada frame.- El número de iteraciones del solver (
iters) afecta directamente al rendimiento: usa el mínimo necesario para tu juego (el valor por defecto de 3 es adecuado para la mayoría de casos). - Para optimizar el dibujado, comprueba la posición del sprite frente a la cámara antes de llamar a
draw(). La región de culling se actualiza automáticamente en cadaupdate()y está disponible internamente; puedes consultar la cámara congetCamera()y las dimensiones de pantalla para hacer tus propias comprobaciones.
Plataformas Soportadas
| Plataforma | Soporte | Notas |
|---|---|---|
| Windows | ✅ | Soporte completo |
| ESP32 | ✅ | Soporte completo con LittleFS/SD |
| Emscripten (Web) | ✅ | Soporte completo |
| SiFive | 🚧 | En desarrollo |