Skip to content

jengine-scaler

Pseudo-3D sprite scaler - a road bent at projection time, and a hill that hides the road behind it.

KindProject package: linked into the builds of the projects that list it
Sourcepackages/jengine-scaler
Version0.1.0
Entry pointjengine_scaler::ScalerPackage
Script namespaceScaler
Listed byprojects/arcade-classics, projects/car-classics

List it in the top-level "packages" array of project.jengine:

{
"packages": [
{ "name": "jengine-scaler", "source": { "type": "path", "path": "../../packages/jengine-scaler" } }
]
}

Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the editor’s Rebuild viewport banner). Native and web builds link it on their own.

A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field for a package component and keeps it as inert custom data.

A script can check that the package is linked with if Scaler == nil then return end.

Scene keyRust type
cd_driveDrivepackage only: needs a v2 scene

Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.

CallFlat name
Scaler.start(e)cd_start
Scaler.next(e)cd_next
Scaler.quit(e)cd_quit
Scaler.gear(e, gear)cd_gear
Scaler.input(e, steer, throttle, brake)cd_input
  • projects/arcade-classics/assets/scenes/coral.scene
  • projects/car-classics/assets/scenes/outrun.scene

From packages/jengine-scaler/README.md.

A pseudo-3D sprite scaler - the road of OutRun and Pole Position - and the two arcade games built on it: Coral Drive (projects/arcade-classics, three stages against the clock) and Sunset Coast (projects/car-classics, an OutRun: fifteen stages in a pyramid of forks, five goals).

Many drawables a frame, each with its own scale. Every sprite’s size comes from its own distance, and the road itself is six hundred more - all written straight into a package-owned pool of BulkEntity sprites (and, for the sky and tunnels, Mesh2D quads), so nothing crosses the script bridge per drawable. And a first-person view can be computed rather than drawn: the projection, the hill clip and the scanline rasteriser are pure functions over a segment table, tested as geometry rather than squinted at.

  • The road is straight in world space and bent at projection time (road). A segment’s curve is not a position: walking outward from the camera each slice shifts by an accumulated dx that grows by its curve, so a constant curve bends the road quadratically, as a real corner looks.
  • A hill hides the road behind it - a row is drawn only if it rises above every row already drawn (the classic maxy clip).
  • One sprite strip per screen scanline (view), not polygons: the count is bounded by the view’s height, so a longer view costs nothing.
  • Every corner has a top speed, and it is one division (run): centrifugal force goes with the square of speed, so a stage’s fastest possible time is a sum, and content refuses a stage nobody could finish on its clock, or whose crest hides the road just ahead.
  • A fork is two segment tables projected from the car (fork). The road widens to two roads side by side, splits, each branch moves out (shift) and bends away (an extra curve eased in and out), and whichever side of the gap the car is on at the end is the branch taken; the run then rebases onto it and the branch not taken stays on screen as a ghost until it has gone. The bend’s tail belongs to the next stage and depends on which side it was entered from, so the refusal checks every way the route can drive a stage: each side it can be entered from, times each branch it leaves by.
  • Tunnels are meshes - walls, roof and lamps as exact trapezoids between projected rows, a rock face round the mouth from outside - sorted against the scenery by distance.

cd_drive, on an entity with a transform (the centre of the view):

"cd_drive": {
"tables": { "stages": "data/cd_stages" },
"which": 0, "sheet": "atlases/coral.png",
"props": ["cd_palm", "cd_sign", "cd_rock", "cd_bush"],
"cars": ["cd_car_a", "cd_car_b", "cd_truck"],
"aspects": [], "car_aspects": [],
"route": false, "crashes": false, "z_base": -30
}

route: true drives on through each stage’s fork into the next, the clock topped up by that stage’s seconds. crashes: true flips the car when it meets a solid prop off the road, or another car at speed (Phase::Crashed for three seconds, then it is put back on the road). aspects give each art’s width over height. Everything the package draws sits between z_base - 70 (the sky) and z_base plus the number of sprites in view.

A recipe - s:N straight, c:CURVE:N a bend, h:HEIGHT:N a hill, ch:C:H:N both, t:N / tc:C:N a tunnel - plus its clock, traffic and colours. Every field Sunset Coast added defaults off, so a Coral Drive row reads as it always did: srgb (colours decoded from sRGB), theme (for the game’s backdrop), sky_top / sky_mid / sky_low (a gradient the package draws), haze / haze_amount, tunnel_* colours, scenery (rules - an art, how often, how far out, which side, solid or not, and curves for chevrons on the outside of a bend), left / right (the route), fork_slices, and gate / goal_gate / fork_sign arts.

CallDoes
Scaler.start(e)begin the selected stage (a route from its first)
Scaler.input(e, steer, throttle, brake)one frame of driving, -1..1 each
Scaler.gear(e, g)0 no gearbox, 1 low (pulls hard, tops out at 62%), 2 high
Scaler.next(e)move to the next stage
Scaler.quit(e)abandon the drive (it stops ticking until the next start)

The package publishes a cd_drive custom bag each frame: the phase, clock, speed, the car’s offset and lean, the stage and its theme, the palette blend between stages, the road ahead (curve, hold_ahead, the fork’s distances and its branches’ names), the nearest cars, and the frame’s events (extended, fork, crashed, recovered, checkpoint, finished, timeup, …).

tests/coral_scene.rs and tests/sunset_route.rs load the two games’ real stage tables and scenes through the refusal, and the latter drives all five of Sunset Coast’s goals through the commands its Lua sends.