Skip to content

Tutorials · Cameras and Framing · Chapter 9

What the camera sees

By the end

  • An arrow that reads camera_view() and camera_fit() to sit at the edge of the screen nearest the beacon, turned toward it, and hides once the whole beacon is in view
  • A second Camera, gate-camera, before the gate: off, FixedVertical, tilted -2.98°, in the group gate the lever speaks to
  • make_current(): the lever’s open switches the game to the gate camera, and three seconds later the town’s camera takes it back, each switch a cut
  • What the game does with two active cameras, and the Problems row for it
On this page

An arrow to the beacon

The beacon burns on its pillar at the street’s east end, far out of the picture from where he starts, and nothing on the screen says so. An arrow at the screen’s edge can: it sits on the edge nearest the beacon and points at it, and once the whole beacon is in the picture there is nothing to point at, and it goes. For that, a script has to ask the camera what it shows.

  1. With town.ron open, select town, press + Add node and choose Sprite. Rename it arrow and set its Position to 276, -80: where its script puts it at the start.
  2. In its Sprite, drag art/arrow.png onto Texture. Set Size to 12, 12 and Sort Layer to 10, over the watchman’s 5, and leave Anchor at 0.5, 0.5: the arrow turns about its middle.
rgs arrow.rgs
/// An arrow at the edge of the screen that points at the beacon while the
/// beacon is out of sight. Once all of the beacon is in view there is nothing
/// to point at, and the arrow hides. It reads the camera the game is seen
/// through, whichever that is.
component Arrow {
    /// The node the arrow points at: the beacon on its pillar.
    #[node_path]
    beacon: i32 = 0,
    /// How big the beacon's picture is: the box that has to be in view.
    size: Vec2 = vec2(32.0, 64.0),
    /// How far in from the edge of the view the arrow sits.
    #[suffix("u")]
    margin: f32 = 12.0,

    fn loop() {
        // The beacon's picture stands on its node, so its middle is half its
        // height above the node.
        let middle = position_of(node(beacon)) - vec2(0.0, size.y * 0.5);
        if camera_fit(middle, size) == .Whole {
            // All of it is on screen.
            hide();
        } else {
            show();
            // The arrow goes where the beacon is, held inside the view's edges
            // by the margin: at the edge nearest the beacon.
            let view = camera_view();
            let spot = vec2(
                clamp(middle.x, view.x + margin, view.z - margin),
                clamp(middle.y, view.y + margin, view.w - margin),
            );
            move_to(spot);
            // And it turns to face the beacon from there.
            anneal_rotation(angle_of(middle - spot), 0.1, .OutQuad);
        }
    }
}
  1. Make arrow.rgs with the script above, as in chapter 7: in Assets, click Assets in the path bar, then Create, New script, arrow and Enter, the script in the Code tab, and Ctrl+S.
  2. Select arrow, press Add component and choose Arrow. Click its Beacon and choose beacon, the furnace on the pillar. Leave Size at 32, 64 and Margin at 12.
  3. Press Ctrl+S.
camera_view()
What the camera the game is seen through shows, in world units: left, top, right and bottom, as x, y, z and w. It is where the camera rests, after the room has held it: a shake does not move it.
camera_fit(point, size)
How much of a box of size, centred on point, that camera shows: .Whole, .Partial or .Outside. The beacon’s picture stands on its node, so its middle is half its height, 32, above it.
anneal_rotation(angle, seconds, ease) and angle_of(direction)
angle_of is the angle a direction points at, in radians: 0 to the right, and a quarter turn straight down. anneal_rotation turns this node to face an angle, the short way, over some seconds: a tenth of a second here, so the arrow swings rather than snaps.
The game a second in: the watchman at the town’s west end on the street, the west wall’s dark stone at the left, the houses, rooftops and church tower behind him; at the top right of the picture, just in from the edge, a small orange arrow pointing right
At the start: the arrow 12 in from the right edge, level with the beacon’s middle, pointing at it.
The game: the watchman walks east along the street, the orange arrow held at the right edge of the picture, level with the beacon; the beacon’s pillar comes into the picture at the right and the arrow sits on the beacon at the edge, then, with all of the beacon in the picture, the arrow is gone
The arrow on the way. Watch it ride the right edge while the beacon is out of sight, sit on the beacon as it comes in, and go the moment all of it is in the picture.

Along the street the beacon is always east, and level with the arrow, so the arrow points straight right. Where the beacon is up and away, it turns to face it. Here is a copy whose pillar stands 600 units higher: the arrow is at the view’s top right corner, 12 in each way, turned up toward the beacon.

The game a second in on a copy whose beacon is far above the street: the same view of the town’s west end, and the orange arrow at the very top right corner of the picture, turned to point up and to the right
The beacon 600 higher: the arrow in the corner, turned toward it.

A second camera

Throwing the lever raises the bars at the far end of the street, out of sight. A second camera, standing before the gate, can show it happening: the game is seen through it for three seconds, then through the town’s camera again. A camera with its Active off draws nothing until a script switches the game to it.

  1. Select town, press + Add node and choose Camera. Rename it gate-camera and set its Position to 2425, -30: in the arch’s opening, a little above the bars’ middle. Its place among town’s children does not matter; the checkpoint lists it before arrow.
  2. In its Node fold, type gate into the Groups box and press Enter: the group the lever speaks to, the bars’ group.
  3. In its Camera, untick Active. Set Rotation to -2.98, Scaling to FixedVertical and Extent to 180, and untick Pixel Snap. Leave Zoom at 1, Offset at 0, 0 and Width as it comes: under FixedVertical it is not read.
  4. Click Background and, with the picker reading F, set it to 0.012, 0.020, 0.040, alpha 1: the town camera’s.
The Inspector of gate-camera: Name gate-camera, Visible ticked, Groups gate with a cross beside it, Position 2425.0 and -30.0, Rotation 0°, Skew 0°, Scale 1.0 and 1.0; its CameraComponent: Active unticked, Zoom 1, Rotation -2.98°, Offset 0.0 and 0.0, Scaling FixedVertical, Extent 180.00 u, Width 640.00 u greyed, Pixel Snap unticked, Background a dark night blue, and the Make current button; under it the GateCamera script
The gate camera: off, FixedVertical 180, Rotation -2.98°.
The Camera’s Active
Whether this is the camera the game is seen through. A scene has one active camera at a time: the gate camera is made with it off, and a script switches it.
The Camera’s Rotation
Tilts the view, the picture turning with it. The Inspector shows it in degrees and you type it in degrees: -2.98 is a slight tilt. A script takes radians. Unlike the town’s camera, which a rig turns, this one has no rig, so its own Rotation is the one that counts.

It has no Camera Rig, no Pixel Perfect and no Confiner of its own. At FixedVertical 180 it shows 320 across at 1280 by 720, and tilted, the box round its view, which camera_view() returns, is 329 by 196.4: x 2260.5 to 2589.5, y -128.2 to 68.2. It holds the arch, the bars, the pillar and the whole beacon, the passage beyond and a strip of the east wall, and it lies inside the town’s walls, so the room never moves it. It is a child of the town’s root, not of the node gate: the group gate is what the lever’s open reaches.

The switch

The gate camera’s script hears the lever, makes itself the camera the game is seen through, waits three seconds, and tells the group watch:

rgs gate_camera.rgs
/// The camera that looks at the castle gate. It is in the group `gate`, so the
/// lever's `open` reaches it as it reaches the bars. It is off until then.
/// When the gate is told to open, the game is seen through it for three
/// seconds, and then the town's camera, which is in the group `watch`, is told
/// to take the game back.
component GateCamera {
    fn on_signal() {
        // The lever said `open`: this camera becomes the one the game is seen
        // through, and every other camera in the scene goes off, in the same
        // step.
        if heard("open") {
            make_current();
            // Three seconds of the bars rising and the gate standing open.
            await wait(3.0);
            // Then the town's camera is told to take the game back.
            emit_to("watch", "back");
        }
    }
}

The town’s camera, in watch, hears back in shots.rgs, which gains a branch at its end:

rgs shots.rgs
/// The camera's shots. The camera is in the group `watch`, so the vista spot
/// and the gate camera can speak to it; this script is what it does when it
/// hears. The two shots are framings of the Camera Rig on this node, switched
/// by name.
component Shots {
    fn on_signal() {
        // The spot said `vista`, as the watchman walked into it: the vista
        // framing is switched on. It outranks `follow`, so the camera blends
        // to it over the vista's own Duration.
        if heard("vista") {
            set_flag("CameraRigComponent", "framings.vista.active", true);
        }
        // The spot said `follow`, as he walked out of it: the vista is
        // switched off, and the camera cuts to him instead of gliding there.
        if heard("follow") {
            set_flag("CameraRigComponent", "framings.vista.active", false);
            cut();
        }
        // The gate camera said `back`, three seconds after the lever: the game
        // is seen through this camera again. Its rig was on him the whole
        // time, so the game comes back to him at once.
        if heard("back") {
            make_current();
        }
    }
}
  1. Make gate_camera.rgs with the first script above. Select gate-camera, press Add component and choose GateCamera.
  2. Double-click shots.rgs in Assets to open it in the Code tab, and make it the second script above: the new lines are the back branch at its end and the words of its first lines.
  3. Press Ctrl+S.
make_current()
Makes the Camera on this node the one the game is seen through, and switches every other camera in the scene off, in the same step: there is never a step with two cameras on, nor one with none. It is a cut: the new camera’s room is taken at once, with no slide.

Watching the switch

Walk him to the lever and press E:

The game: the watchman stands by the lever; then the picture is at the gate, tilted a little: the beacon on its pillar, the dark arch with its bars, which slide up out of the arch; after a while the picture is back on the watchman at the lever, the orange arrow at the right edge
The lever. Watch the picture cut to the gate, the bars rise in the arch, and three seconds later cut back to him.

The gate camera hears open the step after E, and the game is seen through it from then. The town’s camera is the game’s again 182 steps later: the three seconds, a step for the await and one for the town’s camera to hear back. Each switch is a cut, and the room is taken afresh. The town’s rig went on framing him the whole time, so the game comes back on him at once. Through the gate camera the beacon is whole, so the arrow, which asks whichever camera the game is seen through, hides; it is back at the edge as the game returns.

The editor in Play at step 120, gate-camera selected and looked through: the gate seen tilted, the beacon’s furnace on its pillar at the left, the arch with its bars partly risen, houses and fog behind, the street’s top slanting across; the camera’s cross in the middle, labelled gate-camera; in the Hierarchy arrow greyed with its eye crossed out; the Inspector shows Groups gate and the CameraComponent with Active ticked, Rotation -2.98°, Scaling FixedVertical, Pixel Snap unticked
In Play, two seconds in: the gate camera’s Active ticked by the switch, and the arrow hidden.
The Camera tab in Play in the cutaway: Camera gate-camera, At rest 2425.0, -30.0, As drawn 2425.0, -30.0, Node at 2425.0, -30.0, Rotation -3.0°, View 320 × 180 units, Zoom 4.00 px a unit (Zoom 1.00), camera_view() left 2260.5, top -128.2, right 2589.5, bottom 68.2; Room: Held by town-walls, Clamping No, Blend None; Rig: Shot No rig, Blend None
The Camera tab in the cutaway: Camera gate-camera, its camera_view() the box round its tilted view, and No rig.

Two active cameras

Leave the gate camera’s Active ticked and the town has two active cameras. The game does not fail: it looks through the first in the Hierarchy, the town’s camera, made first, and the second is never seen. Problems names it. Try it on the scene, then put it back.

  1. Select gate-camera, tick its Camera’s Active, and open the Problems tab.
  2. Press Undo in the toolbar: the gate camera is off again, and the row goes.
The Problems tab with one warning on gate-camera: "camera" already frames this room and only the first active camera is used — this node’s zoom, roll and position are never read, so switch one of the two off
Tried and put back: the gate camera left Active is one row in Problems, on the camera that loses.

That is why the switch is make_current(): it sets every camera’s Active in one go. A script that ticked Active on one camera and off on the other by hand would leave two on for a step.

Who moves the camera

Which camera is a question of its own now. The game is seen through the active one, and make_current() decides which that is. The town’s camera is still framed by its rig, every step, even while it is off; the gate camera is moved by nothing, and its view lies inside the town’s walls, so they never move it. The arrow moves no camera: it asks the one the game is seen through, every step.

The arrow and the gate camera

Node, last under townComponentFieldValue
gate-cameraNodePosition, Groups2425, -30; gate
CameraActive, Zoom, Rotationunticked, 1, -2.98
Scaling, Extent, WidthFixedVertical, 180, as it comes
Pixel Snap, Backgroundunticked; 0.012, 0.020, 0.040, 1
GateCamerathe script
arrowNodePosition276, -80
SpriteTexture, Size, Anchor, Sort Layerart/arrow.png; 12, 12; 0.5, 0.5; 10
ArrowBeacon, Size, Marginbeacon; 32, 64; 12
cameraShotsthe back branch

Check it works