Skip to content

Tutorials · Navigation and AI · Chapter 10

A changing map

30 minutes Uses: NavCost, NavObstacle, NavLink, Collider, Area, Sprite, anneal_rotation, can_reach, emit_to, heard, in_group, keep_point, position, random, road_distance, set_flag, set_number, set_text, stop_walking, vec2, walk_to

By the end

  • A region of the sheet, door_closed, and the kitchen door with a Collider, a flap and door.rgs: shut, it is a wall to the thief, to the guards’ eyes and to the map
  • lever, an Area by the door that the thief pulls by brushing past it, with lever.rgs
  • A guard on his way through the door when it shuts goes round by the corridor, with no script: the map changes, and every walker on it plans again
  • cat.rgs: the cat creeps through the shut door’s flap, and while it sits it is in the way
  • pots.rgs: the pots knocked askew, and the pans they drop priced on the map
On this page

A door that shuts

Until now the keep’s map was baked once, as Play started, and never changed. In this chapter it changes while the game runs. The kitchen door shuts and opens, a cat sits in the way and gets up again, and pans fall onto the kitchen floor. The map follows each change by itself, and so does every walker on it.

The kitchen door is a picture cut from the sheet by the region door_open: a doorway with its door swung back. Shut, it needs a second picture, which is on the same sheet, two tiles below, but has no name yet. The Sprite sheet tab names it.

  1. In Assets, open art and double-click tilemap_packed.png. The Sprite sheet tab opens on it, over the Viewport.
  2. In its Grid, set Cell to 16 and 16: click the w box once, type 16 and press Enter, then the same in the h box. The tab now reads 12 x 11 cells, one for each tile.
  3. Click the cell four rows down and ten across: a wooden door shut in its arch. Its outline turns orange, and the bar under the sheet says Selected 1 x 1.
  4. In the Regions band, click the Name box, type door_closed, and press Name it. It joins the list under barrel.
The Sprite sheet tab open on tilemap_packed.png, 192 by 176, drawn at 300%: the Grid band reads Cell w 16 and h 16, Margin and Spacing 0, 12 x 11 cells; the Clips band folded; the Regions band lists thief, guard, table, door_open, ladder, barrel and door_closed under an empty Name box and its Name it button; on the sheet one tile is outlined in orange, a wooden door shut in a stone arch, fourth row down at the right, and the bar under the sheet reads Selected 1 x 1
The shut door’s tile selected, and door_closed in the Regions list. The Clips band is folded here so that the whole list shows.
The Sprite sheet tab’s Cell
The size of one cell of the tab’s grid, in the sheet’s pixels. The tab opens at 32 by 32; the keep’s tiles are 16 by 16, so 16 puts one cell round each tile.
A region
A rectangle of the sheet with a name, kept in the sheet’s .meta beside it. A Sprite shows it by name in its Atlas Region. door_closed is 144, 48, 16 × 16: its left, its top, its width and its height, in the sheet’s pixels. Naming it writes it at once; there is nothing to save.

The door itself gets three things: a box that can stop her, a flap the cat will use, and a script that shuts and opens both. The box comes first.

  1. Select door_kitchen in the Hierarchy. Click Add component and choose ColliderComponent.
  2. Set its Size to 16 and 16, clicking each box once, typing and pressing Enter: the doorway, one tile. Untick Solid: the door stands open. Layer stays as it comes, 1.
Edit mode, looking through the game camera at the kitchen: door_kitchen selected in the wall between the hall and the kitchen, its box with handles drawn round the open doorway and a handle each side of it; the lever on the kitchen floor beside it, leaning right; the thief below the lever and the bowl to the right; the Inspector narrowed with the search box, collider, to its Collider: Kind Rectangle, Size 16.0 and 16.0, Offset 0.0 and 0.0, Solid unticked, One Way unticked, Layer showing 0, Include Layers and Exclude Layers Nothing
The kitchen door’s Collider, open. For this picture the thief was placed in the kitchen, so that looking through the game camera shows the door.
Collider Size
The Collider’s box, in world units, about its node: 16 by 16 fills the doorway exactly. A new Collider’s is 64 by 64.
Collider Solid
Whether the box stops what walks into it. Unticked, the door is open: her body walks through it, his eyes see through it, and the map counts the doorway as floor. Ticked, it is a wall to all three. Its script ticks and unticks it; you leave it unticked, as the game starts with the door open.
Collider Layer
The collision layer the box is on: 1, the walls’ layer, so that the guards’ eyes, which look for walls on layer 1, and the region’s Mask, which holds 1, both take it for a wall. The row shows it as 0: an unnamed layer’s box is labelled by its place from 0, as the region’s Mask was in chapter 2.

The flap is a Nav Link across the door, from the hall’s side to the kitchen’s, which only the cat may use. The guards never take a link: their Use Links has been unticked since chapters 6 and 8. The cellar’s hatch stays shut, as chapter 4 left it: the flap is the link this chapter switches.

  1. With door_kitchen still selected, click Add component and choose NavLinkComponent.
  2. Set From to -16, 0 and To to 16, 0: a tile into the hall and a tile into the kitchen. Tick Both Ways. Untick Enabled: the flap is shut while the door is open. Set Cost to 2. Teleport stays unticked.
The same view, the Inspector narrowed with the search box, navlink, to the door’s Nav Link: From -16.0 and 0.0, To 16.0 and 0.0, Both Ways ticked, Enabled unticked, Cost 2.00, Teleport unticked; in the Viewport a handle sits a tile to each side of the door
The flap, switched off while the door stands open. For this picture the thief was placed in the kitchen.
The flap’s From and To
The Nav Link’s From and To, from the door’s middle: -16, 0 is the hall floor in front of it and 16, 0 the kitchen floor behind it. The link joins the cells under its two ends.
The flap’s Both Ways
The Nav Link’s Both Ways, ticked: the cat goes through from either side.
The flap’s Enabled
The Nav Link’s Enabled, unticked as the game starts: drawn dashed with an X, it is not on the map. The door’s script ticks it as the door shuts and unticks it as it opens, so the flap is a way only while the doorway is not.
The flap’s Cost
The Nav Link’s Cost, 2: the 32 units across cost what 64 of floor would. A way round by the corridor is far longer, so the flap is still the cat’s way.
The flap’s Teleport
The Nav Link’s Teleport, left unticked: the cat walks across, which takes time, rather than appearing on the far side.
  1. With door_kitchen still selected, under Groups, type works into Add group and press Enter: the group the lever will speak to.
  2. In Assets, press Create, choose New script, type door, and press Enter. In the Code tab, replace everything in door.rgs with the script below, and press Ctrl+S.
  3. Select door_kitchen, click Add component and choose Door. Shut is greyed: its script sets it.
  4. Press Ctrl+S.
rgs door.rgs
/// The kitchen door. The lever shuts it, and opens it again. Shut, it is a
/// wall to the thief, to a guard's eyes and to every walker's map; only the
/// cat gets through, by its flap.
component Door {
    /// Whether it is shut.
    #[readonly]
    shut: bool = false,

    fn on_signal() {
        if heard("pulled") {
            shut = !shut;
            set_flag("ColliderComponent", "solid", shut);
            set_flag("NavLinkComponent", "enabled", shut);
            if shut {
                set_text("SpriteComponent", "region_name", "door_closed");
            } else {
                set_text("SpriteComponent", "region_name", "door_open");
            }
        }
    }
}
heard("pulled")
True on the step the signal pulled reaches the door: the lever sends it to the group works, next section.
shut = !shut
! turns true into false and false into true. Each pull turns the door over: open to shut, shut to open. Shut shows which, greyed, in the Inspector.
set_flag("ColliderComponent", "solid", shut) and set_flag("NavLinkComponent", "enabled", shut)
Chapter 9’s set_flag, on two more components of the same node: the Collider’s Solid and the flap’s Enabled take the door’s new state. Each lands from the next step on.
set_text
Writes text onto another component on this node. set_text("SpriteComponent", "region_name", "door_closed") sets the Sprite’s Atlas Region, so the door shows the region you named above; door_open shows the open one again.

A lever you brush past

The lever stands on the kitchen floor beside the door. Its picture, lever.png, came with the starter, in art. Like the pots, it is an Area that notices her Area; brushing past it is enough to pull it.

  1. Select keep, press + Add node, choose Sprite, and rename it lever.
  2. Set its Position to 320, 128. Drag art/lever.png from Assets onto its Texture, set Size to 16, 16, and set Sort Layer to 1, with the furniture.
  3. Click Add component and choose AreaComponent. In its Shape, set Size to 12 and 12.
  4. In Assets, press Create, choose New script, type lever, and press Enter. In the Code tab, replace everything in lever.rgs with the script below, and press Ctrl+S.
  5. Select lever, click Add component and choose Lever.
  6. Press Ctrl+S.
rgs lever.rgs
/// The kitchen's lever. The thief brushing past it pulls it, and everything
/// it works hears it was pulled.
component Lever : Area {
    /// Whether it is pulled down.
    #[readonly]
    down: bool = false,

    fn on_enter(other: NodeId) {
        if in_group(other, "thief") {
            down = !down;
            set_flag("SpriteComponent", "flip.x", down);
            emit_to("works", "pulled");
        }
    }
}
The lever’s Area Shape Size
12 by 12, a little smaller than its picture: she pulls it by walking over it, not by passing near.
set_flag("SpriteComponent", "flip.x", down)
Mirrors the lever’s picture left to right while it is down: it leans left, pulled, and right again, up. flip.x reaches the X of the Sprite’s Flip.
emit_to("works", "pulled")
Tells every node in the group works, which is the door, that the lever moved. The door does not care which way: each pulled turns it over.
Down
The Lever’s one field, greyed: whether it is pulled down.
  1. Press F5. Walk the thief through the kitchen door and over the lever, on the floor just inside. The lever leans left and the door shuts behind her.
  2. Walk her back at the door: she stops against it. Walk her over the lever again, and the door opens. Press F5 again to stop.
The game: the kitchen door shown shut in its arch in the wall between the hall and the kitchen; the lever on the kitchen floor beside it, its red knob leaning left; the thief standing to the right of the lever; the bell at the top left in the hall and the bowl at the bottom right
After 30 steps, 0.5 s: the door shut and the lever pulled. For this picture the thief was placed on the lever from the start, and walked right from step 4 to step 24.

The door shuts on the third step after she touches the lever: on the first the lever hears her and sends pulled, on the second the door hears it and writes its Solid, and on the third that write has landed. Signals and the writes they cause each take a step.

A way that breaks mid-walk

A walker on its way through the kitchen door when it shuts must go round. Nothing in its script asks for that. The game’s map counts the shut door as a wall from the step Solid lands, bakes again the part of the map it touches, and every walker on that map plans its way again.

The game does not bake the whole keep again. It bakes only the squares of 8 by 8 cells that the change touches: for one door, a square or two of the keep’s 3520 cells, a few thousandths of a millisecond. So a door, a sitting cat or a fallen pan can change the map on any step without a pause you could feel.

The Nav Link’s Enabled, switched while the game runs
A link switched on is on the map from the step its Enabled lands, and every walker that may use links plans again; switched off, it is gone from the map, and a walker on its way across it plans another way. The flap is switched by door.rgs.
The Nav Obstacle’s Enabled, switched while the game runs
An obstacle switched on blocks its disc from the step it lands; switched off, the floor under it is open again. The cat’s is switched by cat.rgs, below.
The Nav Cost’s Enabled, switched while the game runs
A price switched on is on the map from the step it lands, and walkers plan their ways with it; switched off, the floor costs what it did before. The pans’ is switched by pots.rgs, below.

To watch a way break, send the hall guard through the door by hand, in Play, and shut it in front of him.

  1. Press F5. Walk the thief through the kitchen door and straight on east, above the lever, to the kitchen’s far side. The door stays open.
  2. Select guard_hall, type navagent into the Inspector’s search box and press Enter. Set his Target to 400, 104, in the kitchen. Leave Moving as it is: he is walking his round, so it is already ticked. Press Bake on the strip, and in its Gizmos menu leave Routes and Navigation ticked. His way runs through the kitchen door.
  3. Before he reaches the door, walk the thief back west along the lever’s row, a little below the door, and over the lever. Watch his way.
  4. Press F5 again to stop. The Target you typed in Play is gone, as everything set in Play is.
Play with the camera pulled back, cut to the hall, the kitchen and the corridor: the hall guard, selected, just west of the open kitchen door, a lime line from him east through the doorway to a cross on the kitchen floor; the thief beside the lever in the kitchen; the cat at the hearth and the corridor guard on the carpet with their own lines
After 94 steps, about 1.6 s, of Play: his way runs through the open door. For this picture the hall guard was placed in the hall and sent to 400, 104 from the start, as the step types it, and the thief walked west from the kitchen’s east side the whole run. The camera was pulled back for this picture.
The same, three steps later: the kitchen door drawn shut, the lever leaning left beside the thief, and the hall guard’s lime line now running back west from him past the tables, down the hall’s way to the corridor, east along the corridor and up the kitchen’s way to the same cross
After 97 steps, about 1.6 s, of Play: the door shut on step 96, and on step 97 his way goes round by the corridor. The same run and placements as above; the camera was pulled back for this picture.

The same run in the game: before, as the door shuts, and on his way round.

Three frames side by side of the great hall’s east half and the kitchen door. First: the hall guard just west of the open doorway, his cone towards it, the thief by the lever leaning right. Second: the door shut, the lever leaning left, the guard where he was. Third: the door still shut, the thief against the kitchen’s west wall, and the guard walking away south-west past the tables, his cone pointing down to the left
Steps 94, 97 and 240, 1.6 s, 1.6 s and 4 s, of the run above, in the game. The camera was pulled back for this picture.

The cat’s flap

The flap is on the map only while the door is shut, and only a walker whose Use Links is ticked may take it: the cat. Through a flap a cat goes slowly, so its script slows it while it crosses. And a cat that sits down in a doorway is in everyone’s way: the cat gets a Nav Obstacle, on while it sits.

  1. Select cat. Click Add component and choose NavObstacleComponent. Set Radius to 6. Enabled stays ticked: the cat starts sitting, and its script switches it after.
  2. Click the Code tab, then cat.rgs in its Scripts list. Replace everything in it with the script below, and press Ctrl+S. Its two new fields, Creep and Pace, show in the Cat at 10.00 u and 30.00 u, as the script declares them.
  3. Press Ctrl+S.
rgs cat.rgs
/// The cat wanders: it picks a spot near by that it can reach without going
/// the long way round, walks there, sits a while, and picks again. A noise
/// stops it where it stands. Sitting, it is in the way, and a walker steps
/// round it. Through the kitchen door's flap it creeps.
component Cat {
    /// How far from where it sits it looks for a spot, each way.
    #[suffix("u")]
    reach: f32 = 80.0,
    /// The longest way round it will walk to reach a spot.
    #[suffix("u")]
    longest: f32 = 160.0,
    /// Steps left to sit before it picks again.
    #[readonly]
    sitting: i32 = 60,
    /// Whether it is on its way somewhere.
    #[hidden]
    walking: bool = false,
    /// How fast it creeps through the flap.
    #[suffix("u")]
    creep: f32 = 10.0,
    /// How fast it walks.
    #[suffix("u")]
    pace: f32 = 30.0,

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

    fn on_signal() {
        if heard("noise") && walking {
            stop_walking();
        }
        if heard("nav:no_way") {
            sitting = 30;
        }
        if heard("nav:arrived") {
            sitting = 120;
        }
        if heard("nav:ended") {
            walking = false;
            if sitting == 0 {
                sitting = 180;
            }
        }
        if heard("crossing:door_kitchen") {
            set_number("NavAgentComponent", "speed", creep);
        }
        if heard("crossed:door_kitchen") {
            set_number("NavAgentComponent", "speed", pace);
        }
    }
}
crossing:door_kitchen
The signal a walker’s node hears when it starts across a link, named by the link’s node: here the flap on door_kitchen. It is heard on the step after the walker stepped onto the link. A link is crossed in a straight line, by position, so the shut door’s Collider does not hold the cat back.
crossed:door_kitchen
The signal it hears when it reaches the link’s far end and walks on, on the step after.
Creep and Pace
The Cat’s two new fields, in world units a second. On crossing:door_kitchen the script writes Creep, 10, onto its Nav Agent’s Speed with set_number, from chapter 8; on crossed:door_kitchen, Pace, 30, its old speed.
The cat’s Nav Obstacle Radius
The Nav Obstacle’s Radius, 6: a disc about the size of the cat, which the region’s Clearance of 3 grows to 9, as it grew the tables’.
The cat’s Nav Obstacle Enabled
The Nav Obstacle’s Enabled, switched on every step by set_flag("NavObstacleComponent", "enabled", !walking): on while the cat sits, off while it walks. A walking cat is not in the way, since it moves; under a sitting one, the cells of its disc are off the map.
  1. Press F5. Walk the thief into the kitchen and over the lever, so that the door shuts, and step off the lever.
  2. Select cat, and type navagent into the Inspector’s search box. Wait for its wander to pick a spot beyond the door: its way runs through the flap, and its Speed reads 10.00 while it creeps through. It may take a while: the cat picks its spots by chance. Press F5 again to stop.
Play, looking through the game camera at the kitchen door: the door drawn shut, the cat in the doorway over it, selected, and a short lime line from it across the door to a cross and ring on the kitchen floor beside the thief, who stands on the lever; the hall guard below in the hall with his own line; the Inspector narrowed with the search box, navagent, to the cat’s Nav Agent: Target 309.524 and 118.381, Moving ticked, Speed 10.00, Arrive Distance 6.00, Face Walk and Use Links ticked
After 510 steps, 8.5 s, of Play: the cat half through the flap, creeping at Speed 10.00. Your cat crosses at another time: random picks its spots. For this picture the thief stood on the lever from the start, so the door shut on the third step, and the cat started by the door in the hall.
The same Play, earlier: the cat sitting just west of the shut kitchen door, selected, the floor under it washed orange as the tables’ is; no line from it; the hall guard’s lime line along the top of the hall; the Inspector narrowed with the search box, navobstacle, to the cat’s Nav Obstacle: Radius 6.00, Enabled ticked
After 60 steps, 1 s, of the same Play: the cat sits, its Nav Obstacle on, and the cells under it are off the map.

Pans that fall

The pots hang on the kitchen’s top wall. Knocked, they swing askew and drop their pans on the floor under them: floor a walker can cross, but at a price, as the courtyard’s mud. A Nav Cost saved off, and switched on by the knock, puts the price on the map.

  1. Select pots. Click Add component and choose NavCostComponent. Set Size to 48, 32: three tiles by two, under the pots. Set Cost to 4. Untick Enabled: the pans hang until she knocks them.
  2. Click the Code tab, then pots.rgs in its Scripts list. Replace everything in it with the script below, and press Ctrl+S.
  3. Press Ctrl+S.
rgs pots.rgs
/// The kitchen's pots and pans. The thief knocking into them makes a noise
/// that everyone listening hears, and where it was is kept. The pans fall
/// and the pots hang askew: a walker steps round the floor the pans cover.
component Pots : Area {
    fn on_enter(other: NodeId) {
        if in_group(other, "thief") {
            keep_point("noise", position());
            emit_to("ears", "noise");
            set_flag("NavCostComponent", "enabled", true);
            anneal_rotation(0.4, 0.2, .OutQuad);
        }
    }
}
The pans’ Nav Cost Size and Cost
The Nav Cost’s Size, 48 by 32 about the pots, is the floor the pans cover; its Cost, 4, makes each unit of it cost four of open floor on the map, as the mud.
The pans’ Nav Cost Enabled
The Nav Cost’s Enabled, saved unticked: the price is not on the map until set_flag("NavCostComponent", "enabled", true) ticks it, from the step after she knocks the pots. Nothing unticks it again: the pans stay where they fell.
anneal_rotation(0.4, 0.2, .OutQuad)
Chapter 7’s anneal_rotation, on the pots: they turn to 0.4 of a radian, about 23 degrees, over 0.2 s, and hang there.
  1. Press F5. Walk the thief through the kitchen door and up into the pots. They swing askew.
  2. Select pots, type navcost into the Inspector’s search box, and press Bake on the strip: Enabled is ticked, and the floor under the pots is washed as the mud is. Press F5 again to stop.
Play, looking through the game camera at the kitchen: the pots, selected, hanging askew on the top wall over the thief, and a pink-washed rectangle with handles on the floor under them; the kitchen door open in the wall to the left, the lever below it, the bell in the hall; the Inspector narrowed with the search box, navcost, to the pots’ Nav Cost: Size 48.0 and 32.0, Cost 4.00, Enabled ticked; the status bar says Baked navigation for "nav": 3520 cells, 1346 blocked, 250 priced
After 30 steps, 0.5 s, of Play: the pans’ price on the map, 250 cells priced where there were 232. For this picture the thief was placed on the pots from the start.

What the chapter set

NodeComponentFieldValue
tilemap_packed.pngregiondoor_closed144, 48, 16 × 16
door_kitchennodeGroupsworks
ColliderSize16, 16
Solidunticked
Nav LinkFrom-16, 0
To16, 0
Both Waysticked
Enabledunticked
Cost2
Dooradded
NodeComponentFieldValue
lever, under keepnodePosition320, 128
SpriteTexturelever.png
Size16, 16
Sort Layer1
AreaShape Size12, 12
Leveradded
NodeComponentFieldValue
catNav ObstacleRadius6
CatCreep, Pace10, 30, as the script declares them
potsNav CostSize48, 32
Cost4
Enabledunticked

Every other field is as it comes: the door’s Collider Layer 1, shown as 0, and its flap’s Teleport unticked; the cat’s Nav Obstacle Enabled ticked.

Check it works