Views

Implemented in src/lua/luaviewapi.cpp.

A view is a camera position, a viewport rectangle, a zoom and a scroll step. The renderer owns one built-in view, the default view (id 0, from renderer_get_default_view), and it is the one the older global camera_*, zoom_* and viewport_* functions act on — those keep working unchanged. The view_* functions here take the view first, so that when more views exist (several views in one window: a minimap, picture-in-picture, split screen) the same calls address any of them. view_create adds further views over the same world (a minimap, a close-up, picture-in-picture, split screen), each with its own rectangle, camera and zoom, and each drawn every frame.

View handles are opaque integers. Every function reports failure as nil, "message" (never a Lua error) — an unknown view is nil, "unknown view N".

Zoom is always a factor — a whole multiple of ZOOM_STEP (0.0625) between ZOOM_FACTOR_MIN and ZOOM_FACTOR_MAX; 3.8125 is valid, 3.8 is rejected with the nearest valid factor named. Changing a view's zoom or viewport does not move its camera: the camera position is not re-clamped, so set it again afterwards (view_set_camera_position).

What an extra view is for, and its limits. Every view shows the same map, sprites and layers — there is one world seen through several windows. A view can hide layers (view_show_layer, view_show_only_layers), be hidden (view_set_visible), be drawn above or below another (view_set_draw_order) and have its own backdrop (view_set_clear_background, view_set_background_color). Each visible extra view costs one more full pass over the map per frame. What is not per-view: collisions and input stay in the default view's space, and an ordinary sprite's position is relative to the view it is drawn in — a sprite meant to appear at the same map position in every view needs sprite_set_world_space. Image layers are drawn only in the default view.

view_create(renderer, x, y, w, h) -> view | nil, message

Create an additional view over the same world and return its handle (an integer of at least 1; 0 is the default view). The rectangle [x, x + w) × [y, y + h) is where in the render area the view is drawn: all four are integers, x/y at least 0, w/h at least 1, and it must fit the render area (x + w <= width, y + h <= height) — otherwise nil and the reason.

A new view starts visible, with its camera at the map's origin, zoom 1.0, scroll step the map's tile size, every layer shown, an opaque backdrop in the map's own background colour, and a draw order equal to its own id (so it is drawn on top of the default view, and later views on top of earlier ones). Configure it with the other view_* functions — for a minimap that is usually view_fit_to_map( view ) once the map is loaded. Ids are never reused: a destroyed view's id answers nil, "unknown view N" from then on.

Each visible extra view costs one more full pass over the map per frame.

local renderer = renderer_get_current()
local minimap, err = view_create( renderer, 1000, 20, 240, 240 )
assert( minimap, err )
view_fit_to_map( minimap )   -- once a map is loaded

view_destroy(view) -> true | nil, message

Remove a view: it is no longer drawn and no longer counted (renderer_get_view_count). The default view can never be removed (nil, "the default view cannot be removed"), and a view that was already destroyed is nil, "unknown view N".

local ok, err = view_destroy( minimap )

view_set_zoom(view, factor) -> true | nil, message / view_get_zoom(view) -> factor | nil, message

Set (or read) a view's current zoom factor. The factor must be a whole multiple of ZOOM_STEP (0.0625) within the view's zoom limits (by default ZOOM_FACTOR_MIN to ZOOM_FACTOR_MAX); anything else returns nil and the reason, naming the nearest valid factor. This sets only the current zoom — view_zoom_reset still returns to the preferred zoom (view_set_preferred_zoom). The camera is not moved.

local ok, err = view_set_zoom( view, 3.8125 )
assert( ok, err )
assert( view_get_zoom( view ) == 3.8125 )

view_set_preferred_zoom(view, factor) -> true | nil, message / view_get_preferred_zoom(view) -> factor | nil, message

Set (or read) the zoom view_zoom_reset returns to. Validated exactly like view_set_zoom (a valid factor, within the view's zoom limits).

view_set_preferred_zoom( view, 1.0 )
view_zoom_reset( view )   -- back to 1.0

view_set_zoom_limits(view, min_factor, max_factor) -> true | nil, message / view_get_zoom_limits(view) -> min_factor, max_factor | nil, message

Set (or read) the lowest and highest zoom factor a view may be at (both inclusive). Each must be a valid zoom factor and min_factor must not exceed max_factor. Limits can be widened again as well as narrowed. Narrowing them clamps the view's current and preferred zoom into the new range. view_get_zoom_limits returns both.

view_set_zoom_limits( view, 1.0, 4.0 )
local lo, hi = view_get_zoom_limits( view )   -- 1.0, 4.0

view_set_zoom_enabled(view, enabled) -> true | nil, message / view_get_zoom_enabled(view) -> enabled | nil, message

Enable or disable a view's zoom stepping — view_zoom_in/view_zoom_out (and the global zoom_in/zoom_out on the default view) do nothing while it is off. Setting an exact zoom with view_set_zoom and view_zoom_reset still work. view_get_zoom_enabled reads it back.

view_set_zoom_enabled( view, false )   -- lock the zoom against zoom_in/zoom_out

view_zoom_in(view) -> true | nil, message

Step a view's zoom in by one ZOOM_STEP (a no-op at the zoom limit, or while zoom is disabled — see view_set_zoom_enabled).

view_zoom_in( view )

view_zoom_out(view) -> true | nil, message

Step a view's zoom out by one ZOOM_STEP (a no-op at the zoom limit, or while zoom is disabled — see view_set_zoom_enabled).

view_zoom_out( view )

view_zoom_reset(view) -> true | nil, message

Return a view's zoom to its preferred zoom (see view_set_preferred_zoom) — not to whatever zoom the view was created with.

view_zoom_reset( view )

view_set_dimension(view, x, y, w, h) -> true | nil, message / view_get_dimension(view) -> x, y, w, h | nil, message

Set (or read) a view's viewport: the rectangle [x, x + w) × [y, y + h) of the render area it is shown in — w and h are a real width and height. All four must be integers, x/y at least 0, w/h at least 1, and the rectangle must fit the render area (x + w <= width, y + h <= height, the width/height the renderer was created with). The camera is not moved: set it again (view_set_camera_position) if the new rectangle needs a different one. view_get_dimension returns x, y, w, h.

local ok, err = view_set_dimension( view, 10, 10, 1240, 900 )
assert( ok, err )

view_set_scroll_step(view, w, h) -> true | nil, message / view_get_scroll_step(view) -> w, h | nil, message

Set (or read) how many pixels one view_move_camera_* call moves a view's camera: w horizontally, h vertically, both integers of at least 1. view_get_scroll_step returns w, h — the step the next move would use. It is -1 only while no step was ever set and no map is loaded yet (the default is the loaded map's tile size).

view_set_scroll_step( view, 4, 4 )
view_move_camera_down( view )   -- moves 4 pixels

view_set_camera_position(view, x, y) -> true | nil, message / view_get_camera_position(view) -> x, y | nil, message

Jump a view's camera so the given world-space coordinate is shown at the top-left of its viewport, and (with view_get_camera_position) read it back. Both integers. Unlike the view_move_camera_* calls this does not clamp to the map, and it is not re-clamped when the view's zoom or viewport changes — set it again afterwards.

view_set_camera_position( view, 0, 0 )
local x, y = view_get_camera_position( view )

Moving the camera

Move a view's camera up by its scroll step, stopping at the map's edge (unlike view_set_camera_position). Also view_move_camera_down, view_move_camera_left and view_move_camera_right. Up and left need a loaded map (their limit is its size): with none, they return nil, "no map is loaded...".

view_move_camera_up( view )

view_reset_camera(view) -> true | nil, message

Return a view's camera to the origin of the map.

view_reset_camera( view )

view_set_visible(view, visible) -> true | nil, message / view_get_visible(view) -> visible | nil, message

Show or hide a view (and read it back with view_get_visible). A hidden view keeps all its state — camera, zoom, layer mask — and is simply skipped by the frame; the sprites on a layer that only hidden views show are not advanced either, exactly as for a hidden layer. Every view is visible when created. Hiding the default view hides the game's main picture, which is rarely what you want.

view_set_visible( minimap, false )   -- toggle the minimap off

view_set_draw_order(view, order) -> true | nil, message / view_get_draw_order(view) -> order | nil, message

Set the order views are drawn in (and read it back with view_get_draw_order): ascending, so a view with a higher order is drawn later — on top of — one with a lower order; views with the same order are drawn by ascending id. order is an integer. The default view starts at 0 and a view made by view_create starts at its own id, so by default extra views are on top of the default one, later ones on top of earlier ones.

view_set_draw_order( closeup, 10 )   -- above every view with a lower order

view_set_clear_background(view, clear) -> true | nil, message / view_get_clear_background(view) -> clear | nil, message

Choose whether a view's rectangle is filled with its background colour before its layers are drawn (and read it back with view_get_clear_background). It is on by default. Turn it off for a transparent overlay; leave it on (with an opaque colour, see view_set_background_color) so the scene underneath does not show through, which is what a minimap normally wants. For the default view this is the whole frame's background, cleared once before any view is drawn.

view_set_clear_background( hud, false )   -- draw the view over what is below it

view_set_background_color(view, r, g, b [, a]) -> true | nil, message

Fill a view's backdrop with this colour instead of the map's own. Each of r, g, b and the optional a (default 255, opaque) is an integer 0–255; an alpha below 255 blends the backdrop over what is already drawn there. It only matters while view_set_clear_background is on. There is no getter. For the default view this overrides the whole frame's background colour.

view_set_background_color( minimap, 0, 0, 0, 160 )   -- translucent black

view_use_map_background_color(view) -> true | nil, message

Go back to the default backdrop colour after view_set_background_color: the loaded map's own background colour, or the window's when no map is loaded.

view_use_map_background_color( minimap )

view_show_layer(view, layer, show) -> true | nil, message

Show or hide one layer in this view only (the layer itself, and what every other view shows, are untouched). layer is the layer's integer id, or its name as a string — a name needs a loaded map with a layer of that name, otherwise nil and the reason; an id may be set before the map is loaded (the mask is kept by id, and an id that names no layer is simply never matched). show is a boolean. Every layer is shown by default.

A hidden layer's sprites are not drawn in this view either — sprites belong to their layer. A hidden group layer hides everything inside it whatever its children's own settings, so to show a child, its group must be shown too.

view_show_layer( minimap, "clouds", false )
view_show_layer( minimap, 3, true )

view_show_only_layers(view, layer_ids) -> true | nil, message

Show only the layers whose ids are in the table layer_ids (a list of integers) in this view; every other layer — including any added to the map later — is hidden in it. Remember that a hidden group hides its children, so list a child's group as well. An empty table hides every layer. view_show_all_layers undoes it.

view_show_only_layers( minimap, { 1, 2, 5 } )

view_show_all_layers(view) -> true | nil, message

Clear the view's layer mask: every layer is shown again.

view_show_all_layers( minimap )

view_is_layer_shown(view, layer_id) -> shown | nil, message

Whether this view's layer mask shows the layer with this integer id. It reports only what the mask says — a layer's own visible flag (tilemap_set_layer) is a separate thing and applies to every view.

if not view_is_layer_shown( minimap, 3 ) then print( "layer 3 is masked out" ) end

view_fit_to_map(view) -> true | nil, message

Zoom the view to the largest zoom at which the whole loaded map fits inside its rectangle, and put its camera at the map's top-left — the usual setup for a minimap. The zoom is clamped to the view's zoom limits, so a map too large to fit even at the lowest zoom shows as much as that zoom allows. Nothing else about the view changes. With no map loaded it returns nil, "no map is loaded".

assert( tilemap_load_map( map_path, MAP_ALIGNMENT_TOP_LEFT ) )
view_fit_to_map( minimap )