Script-owned cinematics¶
An entity or scene script can temporarily take control of simulation from its update function:
local player = game.this_player
if game.begin_cinematic(player.pack_name, "Police cinematic REV00", player.object_index, 1800) then
return false
end
The script name has no .lua extension. Use the runtime pack_name of an entity
from the target pack; this also works with Editor [draft] pack identities.
Acquisition requires an existing player, enemy, or item owner, the InGame scene
state, a loadable script, a positive deadline, and no active cinematic.
Starting the event stops the remaining normal actor/world update pipeline. Its script begins on the next host frame. While active, the normal scene hook, input processing, Twitch spawns, slow motion, actors, camera, animations, stage effects, and game clock remain suspended. There is no update whitelist or blacklist. The cinematic script's update function runs once per host frame and chooses every update.
Select existing enemies using is_enabled/is_alive and the intended entity
classification, not is_active. The latter is an item/projectile flag; ordinary
enemy spawning leaves it clear. Damage and death handling belong to the selected
enemy controller: after queuing a hit, advance that controller and its animation
while the event owns simulation. Clearing is_blocking alone does not change
which native move controller runs. For an impact that must interrupt the current
move, queue its hit fields and use check_being_hit() to consume the shared
reaction before selectively advancing the resulting controller. Test blocking,
airborne and boss reactions as well as standing enemies.
Include enemies with extra health bars in the verification; current health alone does not represent their remaining life.
| API | Behavior |
|---|---|
game.is_cinematic |
Whether an event owns simulation. |
game.cinematic_tick |
Zero-based host tick, saved in rollback snapshots. |
game.cinematic_owner_id |
Owner's object ID; -1 outside a cinematic. |
game.get_cinematic_value(index) / game.set_cinematic_value(index, value) |
32 event-local integers, indexed 0..31. The optional presentation API reserves 29..31 when used. Values reset per event and do not overwrite scene custom values. |
game.update_entity_controller(id) |
Advance one player, enemy, or item through its full controller, including its usual physics/position resolution and pack scripts. Does not advance animation. |
game.update_entity_animation(id) |
Advance only that object's normal animation/hit-pause logic. Also supports render-only subitems. |
game.is_cinematic_actor(id) |
Whether the event owns this helper slot. Select event helpers by ownership rather than matching their type to the owner. |
game.schedule_cinematic_quake(amplitude_pixels, duration_frames) |
Schedule an exact alternating background-plane quake for this event. Amplitude is 1..8 pixels and duration is 1..600 display frames; invalid values and calls outside a cinematic are ignored. The event passes through the rollback-aware visual effect queue. |
game.get_entity(id) |
Resolve a player, enemy, item, or subitem with its actual Lua wrapper; invalid IDs return nil. |
game.first_item_id, game.item_capacity |
Item slot range. |
game.first_sub_item_id, game.sub_item_capacity |
Render-only subitem slot range. |
helper.initialize(pack_name, entity_name, animation_id, mode) |
Initialize a free item/subitem returned by a free-slot lookup; default mode is 2. Retains the slot's object ID and resets previous data. |
game.end_cinematic(keep_camera) |
Release control, restore the camera state from acquisition (unless keep_camera is true: the camera stays where the script moved it, for a walk into the next room), and remove helpers initialized during this event. |
For gravity without AI, adjust the actor's height/impulse fields directly, then
resolve any required world collision through update_position_and_check_world_collisions.
Do not also call the full controller unless a second physics step is intended.
Render-only subitems have no built-in controller; a cinematic can execute its own
Lua actor routine and selectively call the animation update.
Nested actor scripts temporarily change game.this_player, game.this_enemy, or
game.this_item. The selective controller API restores the cinematic's context
afterward. Cinematic scripts themselves have no implicit current entity; resolve
the owner by ID. Re-entering an actor's controller recursively returns false.
The deadline, script failure, owner removal/identity change, and scene/level change also release control. No normal simulation update runs on that release frame. The cleanup list records created helper ownership only; it never determines which actors update. Existing actors and scene custom values remain pack-owned.
Keep all mutable state in cinematic values or actor model fields. Lua VM globals,
module caches, and retained mutable upvalues are not rollback state. The update
function's return value does not end the event; call end_cinematic explicitly. Cinematic
state is included in GameModel copying, raw session serialization, and the session
checksum. During a cinematic the checksum also includes helper and actor custom
state used by selective simulation.
Stage presentation without gameplay transitions¶
A cinematic can temporarily display other stage art without changing the live
fight's level, wave, loadcues, music or actor state. Do not use change_scene for
this: a gameplay scene change ends the cinematic and reinitializes the scene.
| API | Behavior |
|---|---|
game.get_scene_camera_path(level_id, scene_id) |
Read-only stage-wave geometry, or nil for invalid IDs. Coordinates are viewport pixels, positive right/down. |
game.get_previous_presentation(level_id, scene_id) |
Previous wave's geometry; at level start, previous playable level's final valid wave; nil if absent. |
game.get_previous_known_presentation(level_id, scene_id) |
Most recent earlier wave with known directional scroll geometry, scanning earlier scenes/levels and skipping unknown routes; nil if none. |
game.set_cinematic_presentation(level_id, scene_id) |
Select resolved replacement/inherited art for valid loaded stage IDs. Returns false outside a cinematic or for invalid IDs. It does not change gameplay scene IDs. |
game.set_cinematic_dark_presentation() |
Hide stage art and use a black backdrop. Clear the global cinematic fade to show owned helpers against that backdrop. Existing entities are masked separately. |
game.restore_cinematic_presentation() |
Select the presentation captured at acquisition. |
game.cinematic_fade |
Read/write integer 0..1000, clear to black. Snapshot-driven; the script owns fade timing. |
game.cinematic_hide_existing_entities |
Read/write view mask for existing actors, bodies, shadows and effects. Cinematic-owned helpers remain visible. Original actor rendering flags are preserved. |
Camera-path records expose level_id, scene_id, start_x, start_y, end_x,
end_y, min_x, max_x, min_y, max_y, has_diagonal, diagonal_x,
diagonal_y, previous_level_id, previous_scene_id, contiguous_previous,
scripted, scroll_direction (+1 right, -1 left, 0 unknown),
is_known_directional, has_authored_route, route_actor_y (the route's actorY, 0 when unset), playable_width, floor_far_y and floor_near_y. The floor values describe the entry
viewport; its near edge at another camera Y is camera_y + 216.
path.floor_far_at(world_x) also accounts for the native sloped wall segment
and post-segment horizon at that world X. Vertical blockers, custom polygons
and pits are separate collision geometry: path.is_ground_obstructed(x, y, margin)
is true when world point (x, y), in pixels, is inside or within margin pixels of
one of the wave's Wall, Pit or Custom2 custom wall shapes (the same test that
keeps native jump-in respawns off holes). Use it to stage helpers on solid ground,
for example by sampling a car's path across the view and moving its depth until clear. When the shown
scene's route sets actorY, use camera_y + path.route_actor_y instead: the Streets of Rage 1
police car keeps the original's fixed line there (round 7), whatever the caller's depth. diagonal_x
retains the native signed threshold, not a positive viewport coordinate.
path.sample_y(x) returns unobstructed one-pixel native forward geometry.
Player positioning can delay actual camera Y, and scripted stages can follow
arbitrary paths; anchor a retreat to the actual acquired camera and do not infer
a scripted route from the native fields. Optional scene cinematicCameraRoute
declares absolute start/end viewport pixels for a known scripted direction;
sample_y then samples its bounded linear Y instead of the native diagonal gate.
Use playable_width for placement in the displayed level, whose rendering scale
can differ from that of the live fight.
A fixed-camera wave (an elevator) can declare a cinematicCameraRoute too, over art drawn
beside its room, so a retreat has somewhere to go; a round-clear scene that keeps its last
wave's art should repeat that wave's route. When a cinematic has no room in the current wave
and must borrow other art, walk back with get_previous_presentation while each previous
wave shares the current art (contiguous_previous) and has no known route, and take the
first wave with other art (the previous background or level, even a fixed-camera one, whose
end_x is where its camera stopped). The Streets of Rage 1 police call does this.
Because an authored route gives absolute camera positions, a camera that is off it (an
elevator high above the street it starts from: its route lies at the street's camera Y) should
first move onto it vertically and come back the same way: the Streets of Rage 1 police call
scrolls down, then along the route, and back along it, then up. A route's actorY also places
lanes that depend on the depth below the caller's camera (the police fire). Staging actors
on walls: when no line across the shown view is clear of walls, rails and pits (a wave walled
in, like an elevator), place the actor already inside, on ground clear under its whole length
(and a band in front of it), instead of driving it in; and let effects that land on the ground
(the police fire and rain) land only on walkable ground, falling on through a pit otherwise.
An unhooked stationary final combat wave can retain the direction of its
contiguous native scene while keeping its actual zero-span bounds. Exit barriers,
script hooks and fixed-room/elevator cameras stop that inheritance.
The presentation API reuses cinematic values 29..31 rather than adding game or
character state fields. Once used, keep script-owned values in 0..28. Helpers
still use ordinary actor custom fields. IDs, fade and visibility therefore copy,
serialize and hash through the existing cinematic state. Cleanup also restores
presentation when the owner disappears, the deadline expires or Lua fails.
Encoded zero IDs in values 29 and 30 select the dark backdrop; use the named API
rather than writing this encoding yourself. It remains distinct from a full-screen
fade, which also covers cinematic helpers.
Switch distant scenes while fully dark. Their render camera snaps under black, then follows the ordinary scripted-camera interpolation. Resource preparation does not unload music or change the currently displayed palette. Readiness stays local to the view: it holds black until requested art is loaded and never becomes a Lua simulation branch. Preserved native fade callbacks resume after cinematic control is released.