Scene Module
The Scene module provides a complete scene management system for games and interactive applications in JARU. A scene groups sprites together, manages a camera with smooth tracking and configurable bounds, integrates physics with gravity and collision resolution, and incorporates an optional GridMap for tile maps.
The scene acts as a central container that coordinates physics updates, camera movement, and off-screen sprite culling, greatly simplifying the development of scrolling games.
Usage
use Display
use Sprite
use Scene
Module Functions
new
The Scene.new() function creates a new empty scene with default values.
var scene = Scene.new()
The scene is created with:
- Camera at position (0, 0)
- No gravity
- No camera bounds
- No sprites or GridMap assigned
- Camera mode CLAMP on both axes
- 3 physics solver iterations
Constants
Camera Modes
| Constant | Value | Description |
|---|---|---|
Scene.CLAMP | 0 | Camera stops at bounds |
Scene.FREE | 1 | Camera moves freely without restrictions |
Scene.WRAP | 2 | Camera wraps around (toroidal movement) |
Instance Properties
gravity
Gets or sets the scene gravity in pixels/second².
// Get current gravity
var g = scene.gravity
// Set gravity (simulates falling)
scene.gravity = 500
// No gravity (space game)
scene.gravity = 0
iters
Gets or sets the number of physics solver iterations. More iterations produce more accurate collisions but consume more CPU.
// Get current iterations
var i = scene.iters
// Set more iterations for higher precision
scene.iters = 5
// Minimum value: 1
scene.iters = 1
The default value is 3, which offers a good balance between precision and performance.
sleeping
Gets or sets sleep mode. With sleeping enabled, bodies that have not moved for half a second fall asleep: they stop consuming CPU and are not recalculated until something touches them. Disabled by default.
// Enable automatic sleeping
scene.sleeping = true
// Check whether it is enabled
var sleeps = scene.sleeping
A sleeping body wakes up on its own when another body hits it, when a moving platform pushes it, or when the program changes one of its physics properties. STATIC and KINEMATIC bodies never sleep, because they do not consume simulation either.
In a scene with objects that end up still — crates, rubble, fallen fruit — sleeping removes nearly all the physics cost once they settle. It is especially useful on ESP32.
Instance Methods
Sprite Management
addSprite
Adds a sprite to the scene. Added sprites participate in physics and are updated with update().
var player = Sprite.load("player.spr")
scene.addSprite(player)
| Parameter | Type | Description |
|---|---|---|
sprite | Sprite | Sprite to add |
Return
Returns true if added successfully, false on error.
removeSprite
Removes a sprite from the scene.
scene.removeSprite(enemy)
| Parameter | Type | Description |
|---|---|---|
sprite | Sprite | Sprite to remove |
Return
Returns true if removed successfully, false if not found.
clearSprites
Removes all sprites from the scene.
scene.clearSprites()
spriteCount
Gets the number of sprites in the scene.
var count = scene.spriteCount()
println("Sprites in scene: ", count)
Return
Returns an integer with the sprite count.
Physics
sleepAll
Puts every body in the scene to sleep at once on the next physics step. Requires scene.sleeping to be enabled.
var scene = Scene.new()
scene.physics = true
scene.sleeping = true
// ...place the crates of the tower here...
scene.sleepAll() // the tower stays frozen until something hits it
This is how you build a structure and leave it completely still without spending CPU. The bodies still wake up normally as soon as they take a hit.
sleepAll() affects the next physics step, not later ones. If you add new bodies afterwards and want them asleep too, call it again.
GridMap
setGridMap
Assigns a GridMap to the scene. Used for calculating automatic camera bounds with setBoundsFromGrid().
var map = GridMap.new(20, 15)
scene.setGridMap(map)
| Parameter | Type | Description |
|---|---|---|
gridmap | GridMap / nil | GridMap to assign, or nil to unassign |
getGridMap
Gets the GridMap assigned to the scene.
var map = scene.getGridMap()
if (map != nil) then
println("GridMap assigned")
end
Return
Returns the assigned GridMap, or nil if none is assigned.
Camera - Manual Position
setCamera
Sets the camera position directly. Calling this method disables automatic tracking (follow).
scene.setCamera(100, 50)
| Parameter | Type | Description |
|---|---|---|
x | number | Camera X position |
y | number | Camera Y position |
getCamera
Gets the current camera position.
var cam = scene.getCamera()
println("Camera at: ", cam[0], ", ", cam[1])
Return
Returns a list [x, y] with the camera position.
Camera - Tracking
follow
Configures the camera to automatically follow a sprite. Optionally a smooth factor can be specified.
// Instant tracking
scene.follow(player)
// Smooth tracking (0.1 = very smooth, 1.0 = instant)
scene.follow(player, 0.1)
| Parameter | Type | Description |
|---|---|---|
sprite | Sprite | Target sprite to follow |
smoothFactor | number | (Optional) Smooth factor between 0.0 and 1.0. Default: 1.0 |
A low smoothFactor (like 0.05 or 0.1) produces a camera that smoothly glides toward the target, commonly used in platformers and adventure games. A value of 1.0 centers the camera instantly.
unfollow
Stops automatic camera tracking. The camera stays at its current position.
scene.unfollow()
Camera - Bounds
setBounds
Sets manual camera bounds. The camera cannot leave this region (depending on the camera mode).
scene.setBounds(0, 0, 640, 480)
| Parameter | Type | Description |
|---|---|---|
minX | number | Left bound |
minY | number | Top bound |
maxX | number | Right bound |
maxY | number | Bottom bound |
setBoundsFromGrid
Automatically calculates and sets camera bounds from the GridMap assigned to the scene. Bounds are adjusted so the camera doesn't show areas outside the map.
scene.setGridMap(map)
scene.setBoundsFromGrid()
If the calculated bounds result in values less than zero (map smaller than the screen), the camera is automatically centered over the map.
This method also automatically configures the camera mode per axis: if the GridMap has wrap enabled on an axis, the camera mode for that axis is set to FREE (the camera moves without restrictions and the GridMap handles toroidal tiling).
If no GridMap is assigned to the scene, setBoundsFromGrid() will throw an error. Always call setGridMap() first.
clearBounds
Removes camera bounds, allowing free movement.
scene.clearBounds()
cameraMode
Gets or sets the camera behavior mode for the X and Y axes independently.
Getter — returns a list [modeX, modeY]:
var mode = scene.cameraMode()
println("Mode X: ", mode[0], " Mode Y: ", mode[1])
Setter — receives a list [modeX, modeY]:
// Both axes with clamp
scene.cameraMode([Scene.CLAMP, Scene.CLAMP])
// Horizontal wrap, vertical clamp
scene.cameraMode([Scene.WRAP, Scene.CLAMP])
| Mode | Constant | Behavior |
|---|---|---|
| 0 | Scene.CLAMP | Camera stops when reaching bounds |
| 1 | Scene.FREE | Camera moves freely without restrictions |
| 2 | Scene.WRAP | Camera wraps from one edge to the other (toroidal) |
Modes only take effect when bounds are active (setBounds() or setBoundsFromGrid()). In FREE mode, bounds are ignored even if defined.
Update
update
Updates the entire scene: runs the physics step for all sprites, updates the camera position (tracking and bounds), and recalculates the culling area.
scene.update()
The internal order of operations on each call is:
- Saves the previous positions of all sprites (for collision sweep)
- Runs
PhysicsStepwith the sprite list, gravity, and solver iterations - Updates the camera position (follow + smoothing)
- Applies camera bounds according to the configured mode
- Recalculates the visible culling region
Call update() once per frame, before drawing. This method handles all physics simulation, including gravity, velocities, accelerations, collisions, and penetration resolution.
Complete Example: Platformer with Scrolling
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
// ------------------------------------------------
// Screen
// ------------------------------------------------
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
// ------------------------------------------------
// Controls
// ------------------------------------------------
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]
// ------------------------------------------------
// Scene
// ------------------------------------------------
var scene = Scene.new()
scene.setGridMap(grid)
scene.addSprite(player)
scene.gravity = 200
scene.follow(player, 0.1)
// ------------------------------------------------
// Main loop
// ------------------------------------------------
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
// Scene physics update
scene.update()
// Collision with tilemap
grid.hits(player)
// Full render: tilemap + sprites + camera
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
Performance Considerations
- Use
spriteCount()to monitor the number of active sprites in the scene. setBoundsFromGrid()only needs to be called once when loading the level, not every frame.- The number of solver iterations (
iters) directly affects performance: use the minimum needed for your game (the default value of 3 is suitable for most cases). - To optimize rendering, check the sprite's position against the camera before calling
draw(). The culling region is automatically updated on eachupdate()call; you can query the camera withgetCamera()and the screen dimensions to perform your own checks.
Supported Platforms
| Platform | Support | Notes |
|---|---|---|
| Windows | ✅ | Full support |
| ESP32 | ✅ | Full support with LittleFS/SD |
| Emscripten (Web) | ✅ | Full support |