Skip to main content

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

ConstantValueDescription
Scene.CLAMP0Camera stops at bounds
Scene.FREE1Camera moves freely without restrictions
Scene.WRAP2Camera 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.

Savings on small boards

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)
ParameterTypeDescription
spriteSpriteSprite to add

Return

Returns true if added successfully, false on error.

removeSprite

Removes a sprite from the scene.

scene.removeSprite(enemy)
ParameterTypeDescription
spriteSpriteSprite 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.

Applied only once

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)
ParameterTypeDescription
gridmapGridMap / nilGridMap 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)
ParameterTypeDescription
xnumberCamera X position
ynumberCamera 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)
ParameterTypeDescription
spriteSpriteTarget sprite to follow
smoothFactornumber(Optional) Smooth factor between 0.0 and 1.0. Default: 1.0
Camera Smoothing

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)
ParameterTypeDescription
minXnumberLeft bound
minYnumberTop bound
maxXnumberRight bound
maxYnumberBottom 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.

Automatic Mode Configuration

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).

Requires Assigned GridMap

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])
ModeConstantBehavior
0Scene.CLAMPCamera stops when reaching bounds
1Scene.FREECamera moves freely without restrictions
2Scene.WRAPCamera wraps from one edge to the other (toroidal)
note

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:

  1. Saves the previous positions of all sprites (for collision sweep)
  2. Runs PhysicsStep with the sprite list, gravity, and solver iterations
  3. Updates the camera position (follow + smoothing)
  4. Applies camera bounds according to the configured mode
  5. Recalculates the visible culling region
Important

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

Optimization
  • 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 each update() call; you can query the camera with getCamera() and the screen dimensions to perform your own checks.

Supported Platforms

PlatformSupportNotes
WindowsFull support
ESP32Full support with LittleFS/SD
Emscripten (Web)Full support