Skip to content

Tutorials · Navigation and AI · Chapter 13

A gallery of navigation and AI

80 minutes Uses: NavRegion, NavAgent, NavObstacle, NavCost, Path, PathFollow, CharacterBody, Collider, Sprite, SpriteShape, TileMap, Camera, Confiner, angle_of, anneal_rotation, can_reach, emit_to, heard, heard_value, nearest, nearest_seen, node, position, position_of, random, set_flag, set_number, stop_walking, translate, vec2, wait, walk_to

By the end

  • Six scenes in scenes, each opened from Assets and played on its own: rat, companion, turret, ghost, pen and yard
  • Four new regions on the keep’s sheet, rat, ghost, companion and gate, and the three made pictures the starter brought for them: cart.png, sheep.png and turret.png
  • The fields the watch never set: a Nav Region’s Allow Diagonals off, a Nav Obstacle’s Enabled saved off, and a Path Follow’s Rotate, Playing saved off and At End
  • Eight short scripts, each printed whole, and for each case its values in a table, who moves what, and which field to change in your own game
On this page

Six cases

The watch is finished. This chapter builds six small things the keep did not need, each a case you will meet again in a game of your own. Each is a scene of its own in scenes, on one room of the keep’s own tilemap, opened from Assets and played with F5. The startup scene stays keep.ron.

CaseWhat it is forWhat it teaches
A rat that fleesVermin, a startled bird, a coward that runs from the player rather than at her.A spot chosen with random and can_reach, and a Nav Region with Allow Diagonals off, so a way runs in straight lines.
A companionA follower, an escort, a pet at the player’s heels.A Nav Agent sent a little short of her, and stopped where it stands when she stops; Use Links off.
A turretA sentry gun, a watching statue, an eye on a wall.nearest_seen over a group, barrels that hide what is behind them, and a guard pacing a rail with At End PingPong.
A ghostAnything that goes through walls: a spirit, a homing spell, a bird above the rooftops.When not to navigate: no region, no agent, a straight line by translate.
A sheep penA herd, a crowd, a flock that should not walk through itself.Each sheep a Nav Obstacle while it grazes and not while it walks, with Enabled saved off, and a ditch a Nav Cost makes dear.
A delivery cartA train, a lift, a cart on rails: something that goes round a fixed way and stops.A Path Follow on a closed, smooth track with Rotate on; stops heard as path:point; a gate whose Path Follow is saved with Playing off and At End Stop.

Every case reads the same way: what it is for, the steps, the script whole, the fields and words defined, its values in a table, who moves what, and a note on which field to change in your own game.

The room every case stands in

All six scenes start from the same room: a root called gallery, the keep’s own tilemap under it as the floor, and a camera with the keep’s settings. Four of them also hold the thief. You make the room once and save it six times: twice before the thief is added, four times after.

  1. Choose File → New scene. Click + Add node, choose Node, and rename it gallery.
  2. Select gallery, click + Add node and choose Tilemap. Rename it floor. Drag keep.tilemap from art in Assets onto its Tilemap, and set Sort Layer to -3, the keep’s floor’s. Collision stays ticked: its walls hold the thief, and its walls layer’s Blocks nav takes them off any map baked over it, as in chapter 2.
  3. Select gallery, click + Add node and choose Camera. Rename it camera. In its Camera, set Scaling to Letterbox, Extent to 180 and Width to 320. Click Background: if the button at the top left of the picker that opens reads U8, press it so that it reads F, as in chapter 12, and set it to 0.1812, 0.0437, 0.0369, alpha 1. That is the keep’s camera, which came with the starter. Pixel Snap stays ticked. Its Position waits for each case.
  4. Press Ctrl+S and save the scene in the scenes folder as pen.ron. Then choose File → Save as… and save it in scenes as yard.ron. Each save leaves the editor holding the copy it wrote.
  5. Select gallery, click + Add node and choose Sprite. Rename it thief. Set Texture to tilemap_packed.png, Size to 16, 16, Anchor to 0.5, 1, her feet, Sort Layer to 2 and Atlas Region to thief. Under Groups, type thief and press Enter.
  6. With thief selected, click Add component and choose CharacterBodyComponent. Set Size to 8, 6, Motion Mode to Floating, Speed to 70 and Layer to 2 (untick the box labelled 0, tick the one labelled 1). Driven By stays Keys: WASD and the arrows walk her, as in the keep.
  7. Choose File → Save as… four times, and save the copy each time in scenes, as rat.ron, companion.ron, turret.ron and ghost.ron. pen.ron and yard.ron, saved before her, have no thief.
Camera Scaling, Extent and Width
How much of the world the camera shows. Letterbox shows exactly Width by Extent world units whatever the window’s shape, and fills what is left with bars of the Background colour. 320 by 180 is one room of the keep, which a 1280 by 720 window draws four times its size. A new camera’s are Pixels, 360 and 640.
Camera Background
The colour behind everything the camera shows, and in a Letterbox’s bars. 0.1812, 0.0437, 0.0369 is the keep’s dark red rock, so the world past the walls reads as rock.
Tilemap Sort Layer
Which layer the tilemap draws on: lower draws first. -3 puts the floor under everything else in the room. A Sprite’s Sort Layer works the same way: -2 for a ditch or a rut on the floor, 1 for furniture, 2 for whoever walks.
NodeComponentFieldValue
galleryNodeno other field
floorTilemapTilemapkeep.tilemap
Sort Layer-3
cameraCameraScalingLetterbox
Extent180
Width320
Background0.1812, 0.0437, 0.0369, alpha 1
thiefnodeGroupsthief
SpriteTexturetilemap_packed.png
Size16, 16
Anchor0.5, 1
Sort Layer2
Atlas Regionthief
Character BodySize8, 6
Motion ModeFloating
Speed70
Layer2: the box labelled 1

The six scenes now exist. From here, every case opens its own: double-click it under scenes in Assets.

Four more pictures on the sheet

Tiny Dungeon’s sheet, tilemap_packed.png, holds a rat, a ghost, a villager and a stretch of fence that nothing in the keep used. Each gets a region, a named rectangle of the sheet, as door_closed did in chapter 10. The sheet is cut into cells of 16 pixels, with no gap between them.

  1. Double-click tilemap_packed.png under art in Assets. It opens in the Sprite sheet tab. Under Grid, set Cell to 16 and 16, with Margin and Spacing at 0.
  2. Hover over the sheet: the bar under it reads Cell and the column and row of the cell under the pointer, each counted from 0. Click the cell 3, 10, the rat: its outline turns orange, and the bar says Selected 1 x 1. In the Regions band, click the Name box, type rat and press Name it.
  3. Click the cell 1, 10, the ghost, and name it ghost the same way. Click the cell 1, 7, the villager, and name it companion.
  4. Drag across the cells 5, 6 and 6, 6, the fence: Selected 2 x 1. Name it gate.
The Sprite sheet tab on tilemap_packed.png at 300%: on the left the Grid and Clips bands folded and the Regions band open, an empty Name box and the Name it button above eleven regions, thief, guard, table, door_open, ladder, barrel, door_closed, rat, ghost, companion and gate; on the sheet the two fence cells of row 6 outlined in orange; the bar under it reads Cell 6, 6 and Selected 2 x 1
The sheet after the steps, with Grid and Clips folded so the whole list shows: the last four regions are this chapter’s. The pointer rests on the fence’s second cell, so the bar reads its column and row.
RegionFrom the sheet’s top left, in pixelsSize
rat48, 16016 × 16
ghost16, 16016 × 16
companion16, 11216 × 16
gate80, 9632 × 16

The regions are saved in the sheet’s .meta file. A sprite whose Atlas Region names one draws only that part of the sheet; Sprites and art covers regions whole. The turret, the cart and the sheep are pictures of their own, made for this tutorial: turret.png, cart.png and sheep.png came with the starter, in art.

A rat that flees

What it is for. Something that runs from the player instead of at her: vermin, a bird on a path, a thief of your own who will not be caught. When she comes near, the rat looks about for spots it could run to, keeps the one farthest from her on its own side of her, and runs there. Its region does not allow diagonals, so its runs are straight, the way a rat keeps to the walls.

  1. Double-click scenes/rat.ron in Assets. Select camera and set its Position to 152, 112: the great hall. Select thief and set her Position to 56, 160, the hall’s south-west corner.
  2. Select gallery, click + Add node and choose Nav Region. Rename it nav. Set its Position to 152, 112 and its Size to 272, 160, the hall’s floor. Set Cell Size to 8. Untick Allow Diagonals. Click Mask’s button, untick every box but the first, 0, and click outside the list: the value saved is 1. Set Clearance to 3.
  3. Select gallery, add a Sprite and rename it hearth. Set its Position to 144, 24, Texture to hearth.png, Size to 32, 16 and Sort Layer to 1: the keep’s hearth, on the hall’s north wall.
  4. Select gallery, add a Sprite and rename it table_west. Set its Position to 136, 104, Texture to tilemap_packed.png, Size to 16, 16, Sort Layer to 1 and Atlas Region to table. Click Add component, choose NavObstacleComponent and set Radius to 12.
  5. Do the same for table_east, at 168, 104.
  6. Select gallery, add a Sprite and rename it rat. Set its Position to 232, 64, Texture to tilemap_packed.png, Size to 16, 16, Sort Layer to 2 and Atlas Region to rat. Its Anchor stays 0.5, 0.5.
  7. With rat selected, click Add component and choose NavAgentComponent. Set Speed to 60 and tick Face Walk.
  8. In Assets, press Create, choose New script, type rat, and press Enter. In the Code tab, replace everything in rat.rgs with the script below, and press Ctrl+S.
rgs rat.rgs
/// A rat that flees. When the thief comes near, it looks about for the spot
/// it can reach that lies farthest from her on its own side of her, so it
/// never runs past her, and runs there.
component Rat {
    /// How near she must come before it runs.
    #[suffix("u")]
    fear: f32 = 64.0,
    /// How far about it looks for a spot, each way.
    #[suffix("u")]
    look: f32 = 96.0,
    /// How many spots it weighs up before it runs.
    tries: f32 = 8.0,
    /// Whether it is running.
    #[readonly]
    running: bool = false,

    fn loop() {
        let thief = nearest("thief");
        let her = position_of(thief);
        if !running && thief != node(0) && (her - position()).length() < fear {
            let best = position();
            let farthest = (best - her).length();
            let from_her = position() - her;
            for tried in 0.0..tries {
                let spot = position() + vec2(random(-look, look), random(-look, look));
                let ahead = spot - position();
                let far_side = ahead.x * from_her.x + ahead.y * from_her.y > 0.0;
                let away = (spot - her).length();
                if far_side && away > farthest && can_reach(spot) {
                    best = spot;
                    farthest = away;
                }
            }
            if farthest > (position() - her).length() {
                running = true;
                walk_to(best);
            }
        }
    }

    fn on_signal() {
        if heard("nav:ended") {
            running = false;
        }
    }
}
  1. Select rat, click Add component and choose Rat. Fear 64, Look 96 and Tries 8 stay as the script declares them; Running is greyed.
  2. Press Ctrl+S.
nearest and position_of
nearest("thief") is the nearest other shown node in the group thief as the step began, by straight distance, or node(0), nobody, when there is none; position_of is where that node stood. The rat compares her distance with fear.
for tried in 0.0..tries
Runs the lines inside once for each number from 0 up to, not including, tries: eight times. Each time it rolls a spot with random up to look from the rat each way.
far_side
Whether a spot lies on the rat’s side of her. ahead.x * from_her.x + ahead.y * from_her.y is positive when the step to the spot points away from her, more or less, and negative when it points back past her. So a spot that is far from her because it is beyond her is never chosen.
can_reach and walk_to
Whether the rat’s Nav Agent could walk to a spot, round walls and the tables, from where it stood as the step began; and the order to walk there, which sets its Target and ticks Moving.
nav:ended
Heard on the step after the rat’s walk ends, however it ended. running goes off, and the next time she is within fear it looks again.
Fear, Look, Tries, Running
The Rat’s fields. Fear is how near she comes before it runs, in world units: 64. Look is how far it rolls a spot each way: 96. Tries is how many spots it weighs: 8; more finds a better spot and costs more checks on the step it runs. Running is greyed: the script ticks it while it runs.
The Nav Region’s Allow Diagonals
Whether a way over this region may step from a cell to the one at its corner. Ticked, as it comes and as in the keep, a way cuts across open floor on a slant. Unticked here: every step of the rat’s way goes along a row or down a column, so its runs are straight lines with square turns. It changes the map, not the walker: every walker on this region walks so.
The Nav Region’s Size, Cell Size, Mask and Clearance
As in chapter 2. Size 272, 160 is the hall’s floor, centred on the node. Cell Size 8 gives 680 cells. Mask 1, the box labelled 0, is the walls’ collision layer. Clearance 3 keeps a way off the walls.
The Nav Obstacle’s Radius
As in chapter 3: how far round the table its node takes cells off the map, 12, the table and a little room. The keep’s two tables, at the same places.
The Nav Agent’s Speed and Face Walk
Speed 60, in world units a second: quicker than a guard’s walk, slower than the thief. Face Walk ticked mirrors the rat to face left while it runs left.

Bake the region to see its map: select nav and press Bake on the strip. The status bar says 680 cells, 24 of them blocked.

  1. Press F5. Walk the thief up the hall towards the rat, until she is a little west of it. Select rat, and watch its way. Press F5 again to stop.
Play, looking through the scene’s camera at the great hall: the hearth on the north wall, the two tables with their discs in the middle, the thief near the top a little west of the rat; the rat, selected, has set off east, and a lime line runs from it east along one row and then straight down to a cross in the hall’s south-east; the Inspector shows its Nav Agent, Target 259.218 and 135.562, Moving ticked, Speed 60.00, Face Walk ticked, and its Rat, Fear 64.00 u, Look 96.00 u, Tries 8.00, Running ticked
After 20 steps, a third of a second, of Play: the rat’s way, east and then south, away from her. For this picture the thief was placed 40 west of the rat from the start.

Of the eight spots it rolled, the farthest from her on its own side was down in the hall’s south-east. Its way there runs along a row and turns once, square, down a column.

The same room and the same moment with the region’s Allow Diagonals ticked: the rat a little lower than before, and its lime line running straight down from it and then on a slant to the same cross
For comparison, the same 20 steps with Allow Diagonals ticked: the same spot, and the way cuts the corner on a slant.
The game: the great hall, the hearth on its north wall, the thief near the top, and the rat far from her at the hall’s east side, below the opening to the kitchen
After 120 steps, 2 s: the rat has run to its spot, away from her. For this picture the thief was placed 40 west of the rat from the start.
NodeComponentFieldValue
cameranodePosition152, 112
navnodePosition152, 112
Nav RegionSize272, 160
Cell Size8
Allow Diagonalsunticked
Mask1, the box 0
Clearance3
hearthnodePosition144, 24
SpriteTexture, Size, Sort Layerhearth.png, 32, 16, 1
table_westnodePosition136, 104
SpriteTexture, Size, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 1, table
Nav ObstacleRadius12
table_eastnodePosition168, 104
Sprite, Nav Obstacleas table_west
ratnodePosition232, 64
SpriteTexture, Size, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 2, rat
Nav AgentSpeed60
Face Walkticked
Ratadded
thiefnodePosition56, 160

A companion

What it is for. A follower: an escort, a pet, a friend who comes along. He keeps a few steps behind her while she walks, and waits where he is when she stops, rather than crowding her. His scene is the whole keep, with the view on her, as the keep’s is.

  1. Double-click scenes/companion.ron in Assets. Select thief and set her Position to 600, 312, at the courtyard’s gate, where she starts in the keep.
  2. In the Hierarchy, drag camera onto thief, and set the camera’s Position to 0, 0: on her, so the view follows her.
  3. Select gallery, click + Add node and choose Camera Confiner. Rename it room. Set its Position to 320, 176 and its Bounds to 640, 352: the whole keep, so the view stops at its edges.
  4. Select gallery, add a Nav Region and rename it nav. Set Position to 320, 176, Size to 640, 352, Cell Size to 8, Mask to the box 0 alone, a value of 1, and Clearance to 3: the keep’s map. Allow Diagonals stays ticked.
  5. Select gallery, add a Sprite and rename it companion. Set its Position to 584, 312, beside her, Texture to tilemap_packed.png, Size to 16, 16, Anchor to 0.5, 1, Sort Layer to 2 and Atlas Region to companion.
  6. With companion selected, click Add component and choose NavAgentComponent. Tick Face Walk and untick Use Links. Speed stays 80.
  7. In Assets, press Create, choose New script, type companion, and press Enter. In the Code tab, replace everything in companion.rgs with the script below, and press Ctrl+S.
rgs companion.rgs
/// A companion who follows the thief, a few steps behind her, and waits
/// where he is when she stops.
component Companion {
    /// How far behind her he keeps.
    #[suffix("u")]
    behind: f32 = 24.0,
    /// Where she stood the step before.
    #[hidden]
    was: Vec2,

    fn loop() {
        let thief = nearest("thief");
        let her = position_of(thief);
        let moved = (her - was).length();
        was = her;
        let gap = her - position();
        if thief != node(0) {
            if moved > 0.1 && gap.length() > behind {
                let short = 1.0 - behind / gap.length();
                walk_to(position() + vec2(gap.x * short, gap.y * short));
            } else if moved <= 0.1 {
                stop_walking();
            }
        }
    }
}
  1. Select companion, click Add component and choose Companion. Behind stays 24.
  2. Press Ctrl+S.
moved and was
How far she went since the step before, and where she stood then. was is #[hidden]: the script’s own memory, no row in the Inspector. More than 0.1 of a unit is walking.
short
The share of the gap between them he walks: all of it but behind. A gap of 100 sends him 76 along it, so he arrives 24 behind her. Each step she walks, he is sent again, a little further on.
stop_walking
Stops his Nav Agent where he stands. When she stops, he stops too, wherever he is, and waits.
Behind
The Companion’s field: how far behind her he keeps, in world units, 24, a step and a half.
The Nav Agent’s Use Links
Unticked: his way may not cross a Nav Link. This scene has none; in the keep, unticked, his way would never take the hole or the ladder, as the corridor guard’s never does. The agent’s Speed stays 80, quicker than her 70, so he catches up after a turn.
Camera Confiner Bounds
The room the camera may show, centred on the confiner’s node, in world units: 640, 352, the keep. The camera on her stops at its edges, so the view never shows past the keep’s walls.
  1. Press F5. Walk the thief west along the courtyard, then stop. Walk her up through the gate to the corridor, and round. Press F5 again to stop.
The game: the courtyard under the corridor’s wall, the gap in the wall up to the corridor above them; the thief standing in the courtyard with the companion just east of her, a step and a half behind, facing her way
After 120 steps, 2 s: she walked west for 100 steps, from step 10, and stopped; he followed and stands behind her. For this picture the left arrow was held from step 10 to step 110.
NodeComponentFieldValue
roomnodePosition320, 176
Camera ConfinerBounds640, 352
navnodePosition320, 176
Nav RegionSize640, 352
Cell Size8
Mask1, the box 0
Clearance3
thiefnodePosition600, 312
cameranodeParentthief
Position0, 0
companionnodePosition584, 312
SpriteTexture, Size, Anchor, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 0.5, 1, 2, companion
Nav AgentFace Walkticked
Use Linksunticked
Companionadded

A turret

What it is for. A sentry gun, a statue whose eyes follow you, a camera on a wall. It turns to face the nearest target it can see, and two barrels hide what is behind them. Its targets are the thief and a guard pacing a rail behind the barrels, so it has something to turn between while she stands still.

  1. Double-click scenes/turret.ron in Assets. Select camera and set its Position to 552, 112: the store room. Select thief, set her Position to 600, 176, and under Groups add targets beside thief.
  2. Select gallery, add a Sprite and rename it barrel_west. Set its Position to 520, 88, Texture to tilemap_packed.png, Size to 16, 16, Sort Layer to 1 and Atlas Region to barrel. Click Add component, choose ColliderComponent and set its Size to 16, 16. Solid stays ticked and Layer stays as it comes, 1, the walls’ layer, which the row shows as the box 0.
  3. Do the same for barrel_east, at 560, 88.
  4. Select gallery, click + Add node and choose Path. Rename it pace_line. Leave its Position at 0, 0. Press the + beside Points twice and type 528, 56, then 608, 56: a rail behind the barrels. Leave Closed unticked.
  5. Select pace_line, add a Sprite and rename it pacer: it goes under pace_line. Set its Position to 528, 56, Texture to tilemap_packed.png, Size to 16, 16, Anchor to 0.5, 1, Sort Layer to 2 and Atlas Region to guard. Under Groups, type targets and press Enter.
  6. With pacer selected, click Add component and choose PathFollowComponent. Leave Path as (none), its parent’s. Set Speed to 24 and choose PingPong in At End.
  7. Select gallery, add a Sprite and rename it turret. Set its Position to 496, 128, by the room’s west wall, Texture to turret.png, Size to 16, 16 and Sort Layer to 2.
  8. In Assets, press Create, choose New script, type turret, and press Enter. In the Code tab, replace everything in turret.rgs with the first script below, and press Ctrl+S. Make pacer.rgs the same way, with the second.
rgs turret.rgs
/// A turret that turns to face the nearest target it can see.
component Turret {
    /// The collision layers that hide a target from it.
    walls: i32 = 1,
    /// Whether it sees a target, as this step began.
    #[readonly]
    sees: bool = false,

    fn loop() {
        let target = nearest_seen("targets", walls);
        sees = target != node(0);
        if sees {
            anneal_rotation(angle_of(position_of(target) - position()), 0.1, .OutQuad);
        }
    }
}
rgs pacer.rgs
/// A guard pacing behind the barrels on a path. He turns round at each end
/// of it, and faces the way he walks.
component Pacer {
    /// Whether he faces left.
    #[hidden]
    left: bool = false,

    fn on_signal() {
        if heard("path:turned") {
            left = !left;
            set_flag("SpriteComponent", "flip.x", left);
        }
    }
}
  1. Select turret, click Add component and choose Turret. Walls stays 1; Sees is greyed.
  2. Select pacer, click Add component and choose Pacer. It has no field to set.
  3. Press Ctrl+S.
nearest_seen
The nearest other shown node in a group that this node could see as the step began: nothing on the collision layers walls names, a solid collider or a solid tile layer, stands on the straight line between them. node(0), nobody, when it sees none. The barrels’ colliders and the keep’s walls are on layer 1, so they hide; the thief and the pacer are in targets.
angle_of and anneal_rotation
angle_of is the angle a direction points at, in radians: 0 to the right, a quarter turn straight down. anneal_rotation turns the node to it, the short way, over 0.1 of a second, easing out with .OutQuad, as the cone of chapter 7 turns. Called every step, it chases a moving target.
path:turned
The signal a Path Follow’s node hears on the step after it turns round at an end of an open path with At End PingPong, its Speed flipped. The pacer flips his picture with set_flag, so he faces the way he walks.
Walls, Sees
The Turret’s fields. Walls is the collision layers that hide a target, as a number whose binary digits are the layers, as a Mask is: 1, the walls’ layer. Sees is greyed: whether it saw a target as the step began.
The Path Follow’s At End
What a follower does at the end of an open path. Stop, as it comes, stands there and says path:ended. PingPong turns round: its Speed flips sign and it walks back, saying path:turned, and so on for ever. A closed path has no end, and goes round whatever At End says.
The Path Follow’s Speed
How fast the follower walks its path, in world units a second: 24, a slow pace. Negative walks it backwards, which is what PingPong does on the way back.
Collider Size, Solid and Layer
The barrel’s box, 16 by 16, solid, on layer 1, which the row shows as 0: an unnamed layer’s box is labelled by its place from 0, as the region’s Mask is. It holds the thief’s body as a wall does, and hides what is behind it from nearest_seen. A new Collider’s is 64 by 64.
  1. Press F5. Watch the pacer walk his rail and the turret follow him; watch it turn to the thief when he goes behind a barrel. Select turret and watch Sees. Press F5 again to stop.
Play, looking through the scene’s camera at the store room, the right third of the view the dark red rock past its east wall: the pacer at the top on his rail, drawn as a thin line, above the two barrels; the turret, selected, by the west wall, turned up and to the right towards him; the thief at the bottom; the Inspector narrowed with the search box, turret, to its Turret: Walls 1 and Sees ticked
After 120 steps, 2 s, of Play: the pacer is in the open and nearer than her, and the turret faces him. The store room is narrower than the view, so rock fills its right side.
Two pictures of the store room side by side. In the first the pacer stands at the top to the right of the east barrel and the turret by the west wall points up towards him; in the second he stands above the east barrel, the west barrel lies on the line between him and the turret, and the turret points down to the right, at the thief
After 300 steps, 5 s, he is in the open and it faces him; after 340, about 5.7 s, the west barrel hides him and it faces her, though he is still the nearer.
NodeComponentFieldValue
cameranodePosition552, 112
barrel_westnodePosition520, 88
SpriteTexture, Size, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 1, barrel
ColliderSize16, 16
barrel_eastnodePosition560, 88
Sprite, Collideras barrel_west
pace_linePathPoints528, 56; 608, 56
pacernodeParentpace_line
Groupstargets
Position528, 56
SpriteTexture, Size, Anchor, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 0.5, 1, 2, guard
Path FollowSpeed24
At EndPingPong
Paceradded
thiefnodeGroupsthief, targets
Position600, 176
turretnodePosition496, 128
SpriteTexture, Size, Sort Layerturret.png, 16, 16, 2
Turretadded

A ghost

What it is for. Knowing when not to navigate. A ghost drifts straight at the thief, through the wall between the kitchen and the hall. It needs no map and no Nav Agent: a script moves it a little each step along the straight line to her. So do a homing spell, a bird above the rooftops, and anything else walls do not stop.

  1. Double-click scenes/ghost.ron in Assets. Select camera and set its Position to 312, 112: the hall’s east half and the kitchen, the wall between them in the middle. Select thief and set her Position to 232, 152, in the hall.
  2. Add the hearth as in the rat’s scene: a Sprite called hearth at 144, 24, Texture hearth.png, Size 32, 16, Sort Layer 1.
  3. Select gallery, add a Sprite and rename it ghost. Set its Position to 392, 152, in the kitchen, Texture to tilemap_packed.png, Size to 16, 16, Sort Layer to 2 and Atlas Region to ghost.
  4. In Assets, press Create, choose New script, type ghost, and press Enter. In the Code tab, replace everything in ghost.rgs with the script below, and press Ctrl+S.
rgs ghost.rgs
/// A ghost goes straight for the thief, through walls: it needs no map, and
/// no Nav Agent.
component Ghost {
    /// How fast it drifts.
    #[suffix("u")]
    speed: f32 = 20.0,

    fn loop(dt: f32) {
        let thief = nearest("thief");
        if thief != node(0) {
            let toward = position_of(thief) - position();
            let far = toward.length();
            if far > 1.0 {
                let step = speed * dt / far;
                translate(vec2(toward.x * step, toward.y * step));
                set_flag("SpriteComponent", "flip.x", toward.x < 0.0);
            }
        }
    }
}
  1. Select ghost, click Add component and choose Ghost. Speed stays 20.
  2. Press Ctrl+S.
fn loop(dt: f32)
loop with the step’s length in seconds, dt: 1/60 at sixty steps a second. A distance a second times dt is the distance this step.
translate
Moves this node by a step from wherever it is now, in world units. It moves the node itself: no body, so no wall holds it, and no map, so nothing plans. toward scaled by speed * dt / far is a step of speed units a second straight at her.
Speed
The Ghost’s field: how fast it drifts, in world units a second, 20, a slow menace.
  1. Press F5. Stand the thief still and watch the ghost come for her, straight through the wall. Press F5 again to stop.
The game: the great hall at the left, the kitchen at the right, the wall between them running down the middle with an opening halfway down it; the thief standing in the hall, and the ghost drawn on the wall itself, level with her
After 290 steps, about 4.8 s: the ghost inside the wall between the kitchen and the hall, at her height, on its straight way to her.

The ghost’s picture is drawn over the wall as it passes: nothing stops it, and nothing hides it. Its scene has no Nav Region and the ghost no Nav Agent; there is nothing to bake.

NodeComponentFieldValue
cameranodePosition312, 112
hearthnodePosition144, 24
SpriteTexture, Size, Sort Layerhearth.png, 32, 16, 1
thiefnodePosition232, 152
ghostnodePosition392, 152
SpriteTexture, Size, Sort Layer, Atlas Regiontilemap_packed.png, 16, 16, 2, ghost
Ghostadded

A sheep pen

What it is for. A herd that should not walk through itself, with no steering. Three sheep wander the kitchen: each walks to a spot near by, grazes, and walks on. While a sheep grazes it is in the way, a Nav Obstacle the others plan round; while it walks it is not, or it would block its own way. A ditch across the pen costs eight times as much to cross.

  1. Double-click scenes/pen.ron in Assets. Select camera and set its Position to 384, 112: the kitchen.
  2. Select gallery, add a Nav Region and rename it nav. Set Position to 384, 112, Size to 160, 160, the kitchen’s floor, Cell Size to 8, Mask to the box 0 alone, a value of 1, and Clearance to 3.
  3. Select gallery, add a Sprite and rename it ditch. Set its Position to 384, 112, Texture to mud.png, Size to 160, 16 and Sort Layer to -2. Set Draw Mode to Tiled and Tile Size to 16, 16: the mud repeated along it.
  4. With ditch selected, click Add component, choose NavCostComponent, and set Size to 160, 16 and Cost to 8.
  5. Select gallery, add a Sprite and rename it sheep_1. Set its Position to 344, 64, Texture to sheep.png, Size to 16, 12 and Sort Layer to 2.
  6. With sheep_1 selected, click Add component and choose NavAgentComponent. Set Speed to 16 and tick Face Walk. Click Add component again and choose NavObstacleComponent. Set Radius to 8 and untick Enabled.
  7. In Assets, press Create, choose New script, type sheep, and press Enter. In the Code tab, replace everything in sheep.rgs with the script below, and press Ctrl+S.
rgs sheep.rgs
/// A sheep in its pen. It walks to a spot near by, grazes a while, and
/// walks on. Grazing, it is in the way, so the others walk round it.
component Sheep {
    /// How far it looks for a spot, each way.
    #[suffix("u")]
    reach: f32 = 48.0,
    /// Steps left to graze before it walks on.
    #[readonly]
    grazing: i32 = 0,
    /// Whether it is walking.
    #[hidden]
    walking: bool = false,

    fn loop() {
        if !walking {
            if grazing > 0 {
                grazing -= 1;
            } else {
                let spot = position() + vec2(random(-reach, reach), random(-reach, reach));
                if can_reach(spot) {
                    walk_to(spot);
                    walking = true;
                }
            }
        }
        set_flag("NavObstacleComponent", "enabled", !walking);
    }

    fn on_signal() {
        if heard("nav:ended") {
            walking = false;
            grazing = 180;
        }
    }
}
  1. Select sheep_1, click Add component and choose Sheep. Reach stays 48; Grazing is greyed.
  2. Right-click sheep_1 in the Hierarchy and choose Duplicate, twice. Rename the copies sheep_2 and sheep_3, and set their Position to 424, 72 and 368, 160. Each copy has the sheep’s picture, its agent, its obstacle and its script.
  3. Press Ctrl+S.
The Nav Obstacle’s Enabled, saved off
Whether the Nav Obstacle takes its cells off the map right now. Saved unticked here, so no sheep blocks anyone before its script says so; on its first step the script ticks it for a sheep that is grazing and unticks it for one that walks. Switched, the map changes by chunks and every walker on it plans again, as in chapter 10.
The Nav Obstacle’s Radius
8: a sheep’s own size. With the region’s Clearance of 3 on top, a way keeps more than 10 units from a grazing sheep’s middle, so no sheep walks through another.
The Nav Cost’s Size and Cost
The ditch’s priced rectangle, 160 by 16, centred on its node, across the pen; and how many times dearer a unit of it is than open floor, 8: the planner counts each unit of ditch as eight of floor, as chapter 3’s mud counted four. It runs wall to wall, so it is never a wall to them; it is only dear.
The Nav Agent’s Speed
16, in world units a second: an amble. A sheep’s walk to a spot 48 away takes three seconds.
Sprite Draw Mode and Tile Size
Tiled repeats the picture across the Sprite’s Size instead of stretching it; Tile Size 16, 16 is how far apart the repeats sit. The ditch is ten pieces of mud in a row.
set_flag
Writes true or false onto another component on this node. set_flag("NavObstacleComponent", "enabled", !walking) ticks the sheep’s own Enabled while it grazes and unticks it while it walks.
Reach, Grazing
The Sheep’s fields. Reach is how far it looks for its next spot, each way, in world units: 48. Grazing is greyed: the steps left before it walks on, 180, three seconds, after each walk.
  1. Press F5. Select nav, press Bake on the strip, and in the strip’s Gizmos menu leave Routes and Navigation ticked. Watch the sheep for half a minute: the overlay takes each grazing sheep’s cells off the map and gives them back when it walks. Press F5 again to stop.
Play, looking through the scene’s camera at the kitchen, nav selected: the overlay’s grid over the kitchen floor, the ditch washed pink across the middle; three sheep in the upper half, the top one and the lowest one each standing in a patch of blocked cells, grazing, and between them one walking, with no patch; a lime line from the walking sheep east, then down on a slant past the right of the lowest sheep’s blocked cells to a ring and a cross beside them; the Inspector shows nav’s Nav Region, Size 160.0 and 160.0, Cell Size 8.00, Allow Diagonals ticked, Mask 0, Clearance 3.00; the status bar says 400 cells, 11 blocked, 40 priced
After 1888 steps, about 31.5 s, of Play: a sheep has just set off. Its spot lay among the blocked cells of the sheep grazing below it, so its way goes round them to the nearest floor beside them.
The game: the kitchen as a pen, the ditch of mud across its middle, one sheep near the top wall, one just above the ditch and one by the bottom wall, each well apart from the others
After 300 steps, 5 s: the three sheep apart, none on another.
NodeComponentFieldValue
cameranodePosition384, 112
navnodePosition384, 112
Nav RegionSize160, 160
Cell Size8
Mask1, the box 0
Clearance3
ditchnodePosition384, 112
SpriteTexture, Size, Sort Layermud.png, 160, 16, -2
Draw Mode, Tile SizeTiled, 16, 16
Nav CostSize160, 16
Cost8
sheep_1nodePosition344, 64
SpriteTexture, Size, Sort Layersheep.png, 16, 12, 2
Nav AgentSpeed16
Face Walkticked
Nav ObstacleRadius8
Enabledunticked
Sheepadded
sheep_2nodePosition424, 72; the rest as sheep_1
sheep_3nodePosition368, 160; the rest as sheep_1

A delivery cart

What it is for. Something that goes round a fixed way and stops: a train, a lift, a cart on rails. The cart goes round a closed, smooth track in the hall, turned to face its way. At two points it stands still a while: the depot, and before a gate across the track, which it tells to open. The gate slides open along a short rail of its own, waits, and slides shut. Nothing here needs a map: a Path Follow is the right tool for a way that never changes.

  1. Double-click scenes/yard.ron in Assets. Select camera and set its Position to 152, 112: the great hall. Add the hearth as in the rat’s scene: hearth at 144, 24, hearth.png, 32, 16, Sort Layer 1.
  2. Select gallery, click + Add node, choose Path and rename it track. Leave its Position at 0, 0. Press the + beside Points eight times and type, in order: 56, 72; 152, 56; 248, 72; 256, 112; 248, 152; 152, 168; 56, 152; 48, 112. Tick Closed and Smooth.
  3. With track selected, click Add component and choose SpriteShapeComponent: the rut the cart runs in, drawn through the same points. Press the + beside its Points eight times and type the same eight points, and tick its Closed and Smooth. Untick Collide: a rut is no wall. Set Sort Layer to -2.
  4. Press the + beside Edges once. In the new edge, set From to 0 and To to 360, Size to 16, 8, Offset to 4, and Color to 0.5089, 0.15, 0.0685, alpha 1 in the picker’s F mode, as for the camera. Leave its Texture empty: a band of plain colour.
  5. Select track, add a Sprite and rename it cart: it goes under track. Set its Position to 56, 72, the track’s first point, Texture to cart.png, Size to 16, 12 and Sort Layer to 2.
  6. With cart selected, click Add component and choose PathFollowComponent. Leave Path as (none), its parent’s. Set Speed to 32 and tick Rotate.
  7. Select gallery, add a Path and rename it gate_rail. Leave its Position at 0, 0. Press the + beside Points twice and type 256, 112, then 224, 112: from across the track to beside it. Leave Closed unticked.
  8. Select gate_rail, add a Sprite and rename it gate: it goes under gate_rail. Set its Position to 256, 112, Texture to tilemap_packed.png, Size to 32, 16, Sort Layer to 1 and Atlas Region to gate. Under Groups, type gate and press Enter.
  9. With gate selected, click Add component and choose PathFollowComponent. Leave Path as (none). Set Speed to 24 and untick Playing. At End stays Stop.
  10. In Assets, press Create, choose New script, type cart, and press Enter. In the Code tab, replace everything in cart.rgs with the first script below, and press Ctrl+S. Make gate.rgs the same way, with the second.
rgs cart.rgs
/// The delivery cart goes round its track, and stands a while at its two
/// stops: the depot, and the yard's gate, which it tells to open.
component Cart {
    /// The track's point it loads at, counted from 0.
    depot: f32 = 0.0,
    /// The track's point before the gate.
    gate: f32 = 3.0,
    /// How long it stands at a stop.
    #[suffix("s")]
    stand: f32 = 2.0,

    fn on_signal() {
        if heard("path:point") {
            let at = heard_value("path:point");
            if at == depot || at == gate {
                if at == gate {
                    emit_to("gate", "cart");
                }
                set_flag("PathFollowComponent", "playing", false);
                await wait(stand);
                set_flag("PathFollowComponent", "playing", true);
            }
        }
    }
}
rgs gate.rgs
/// The yard's gate. When the cart comes it slides open along its path,
/// stands open while the cart goes through, and slides shut again.
component Gate {
    /// How fast it slides.
    #[suffix("u")]
    speed: f32 = 24.0,
    /// How long it stands open.
    #[suffix("s")]
    open_for: f32 = 3.0,
    /// Whether it stands open.
    #[readonly]
    open: bool = false,

    fn on_signal() {
        if heard("cart") && !open {
            set_number("PathFollowComponent", "speed", speed);
            set_flag("PathFollowComponent", "playing", true);
        }
        if heard("path:ended") {
            open = !open;
            if open {
                await wait(open_for);
                set_number("PathFollowComponent", "speed", -speed);
                set_flag("PathFollowComponent", "playing", true);
            }
        }
    }
}
  1. Select cart, click Add component and choose Cart. Set Gate to 2: click its box once, type 2 and press Enter. Depot 0 and Stand 2 stay as the script declares them.
  2. Select gate, click Add component and choose Gate. Speed 24 and Open For 3 stay; Open is greyed.
  3. Press Ctrl+S.
The Path Follow’s Rotate
Whether the follower is turned to face along the path as it goes: its node’s rotation follows the curve. Ticked on the cart, which is drawn from above, so it always points the way it rolls. The cat of chapter 1 kept it off: drawn side-on, it would hang upside down on the far side of its loop. A follower’s Offset, if it has one, turns with it only when Rotate is on.
The Path Follow’s Playing, saved off
Whether the follower is moving along its path. The gate’s is saved unticked: it stands shut across the track from the first step until its script ticks it. The cart’s script unticks and ticks its own to stand at a stop; Progress stays where it was, so it goes on from there.
The Path Follow’s At End on the gate
Stop, as it comes: at either end of its open rail the gate stands and its node hears path:ended. The script turns its Speed negative to send it back the other way. The cart’s track is closed and has no end, so the cart’s At End does nothing.
The Path Follow’s Speed on the cart
32 world units a second along the curve, a slow roll.
The Path’s Points, Closed and Smooth
As in chapter 1. The track’s eight points go round the hall; Closed joins the last back to the first, and Smooth rounds every corner into a curve the cart follows.
path:point
The signal a follower’s node hears on the step after it passes a point of its path, for each point passed. heard_value gives the point’s number, counted from 0: the cart stops after point 0, the depot, and point 2, the last before the gate, 248, 72.
heard_value
The number a signal carried into this node this step: for path:point, which point. 0 for a signal that carried none, or was not heard.
path:ended
The signal a follower’s node hears on the step after it stops at an end of an open path with At End Stop. The gate hears it shut and open alike, so open flips each time.
emit_to, set_number and await wait
emit_to("gate", "cart") tells every node in the group gate, heard on the next step as cart. set_number writes a number onto another component on this node: the gate’s own Path Follow Speed. await wait(stand) holds the rest of the hook for stand seconds while everything else runs on.
Depot, Gate, Stand
The Cart’s fields. Depot and Gate are the track’s points it stops after, counted from 0: 0 and 2. The script’s own Gate is 3, for a track of your own; this track’s gate stands after point 2. Stand is how long it stands, in seconds: 2.
Speed, Open For, Open
The Gate’s fields. Speed is how fast it slides, 24 world units a second: its 32-unit rail in about one and a third seconds. Open For is how long it stands open, 3 seconds. Open is greyed: whether it stands open now.
The Sprite Shape’s Points, Closed, Smooth, Collide and Sort Layer
A Sprite Shape draws bands of art along a line of points. The same eight points, closed and smooth, put the rut exactly where the cart goes. Collide unticked: a Sprite Shape can double as a wall, and a rut is not one. Sort Layer -2 draws it on the floor, under everything.
An edge’s From, To, Size, Offset and Color
One band of the Sprite Shape’s Edges. From 0 and To 360 are the facings it covers, in degrees: all of them, so one band goes all the way round. Size 16, 8 is each piece’s length along the line and its depth. Offset 4, half the depth, centres the band on the line. Color 0.5089, 0.15, 0.0685 is a dark copper, the colour of a worn rut.
  1. Press F5. Watch the cart go round, stop before the gate, and the gate slide open for it and shut behind it. Select cart and watch its Progress. Press F5 again to stop.
Play, looking through the scene’s camera at the great hall: a copper rut in a rounded loop round the hall, the cart on its top leg turned along it, selected, and the gate, a short stretch of fence, standing across the rut’s east leg; the Inspector shows the cart’s Rotation -5.85°, its Cart, Depot 0.00, Gate 2.00, Stand 2.00 s, and its Path Follow, Path (none), Progress 64.00, Speed 32.00, Rotate ticked, Playing ticked, At End Stop
After 120 steps, 2 s, of Play: the cart on the north leg, turned along its way; the gate shut across the track.
Five pictures of the rut’s east leg side by side. First, the cart on the top leg coming to the north-east bend, the gate across the rut. Second and third, the cart standing in the bend while the gate slides left off the rut, half open and then fully open. Fourth, the gate open and the cart going down through the gap. Fifth, the gate back across the rut and the cart gone round the bottom bend
Steps 340, 400, 480, 580 and 720, about 5.7, 6.7, 8, 9.7 and 12 s: the cart comes, stands two seconds while the gate opens, goes through, and the gate shuts behind it.

The rut’s band runs round the whole loop as one ring, bent through every curve, so no gap opens on the outside of a bend. A Sprite Shape lays its edge art as one strip along its outline, mitred at every joint, and a closed, smoothed shape with one band for every face is a single ring of it.

NodeComponentFieldValue
cameranodePosition152, 112
hearthnodePosition144, 24
SpriteTexture, Size, Sort Layerhearth.png, 32, 16, 1
trackPathPoints56, 72; 152, 56; 248, 72; 256, 112; 248, 152; 152, 168; 56, 152; 48, 112
Closed, Smoothticked, ticked
Sprite ShapePoints, Closed, Smoothas the Path
Edges 0From 0, To 360, Size 16, 8, Offset 4, Color 0.5089, 0.15, 0.0685, alpha 1
Sort Layer-2
Collideunticked
cartnodeParenttrack
Position56, 72
SpriteTexture, Size, Sort Layercart.png, 16, 12, 2
Path FollowSpeed32
Rotateticked
CartGate2
gate_railPathPoints256, 112; 224, 112
gatenodeParentgate_rail
Groupsgate
Position256, 112
SpriteTexture, Size, Sort Layer, Atlas Regiontilemap_packed.png, 32, 16, 1, gate
Path FollowSpeed24
Playingunticked
Gateadded

Check it works