Skip to content

Tutorials · Navigation and AI · Chapter 6

Orders

25 minutes Uses: NavAgent, CharacterBody, Area, PathFollow, arrived, can_reach, emit_to, heard, in_group, keep_point, kept_point, position, random, road_distance, stop_walking, vec2, wait, walk_to

By the end

  • The corridor guard on a Character Body and a Nav Agent, his Path Follow removed: one mover, and the Problems row that two raise
  • guard.rgs: his round walked from end to end by his script, along the carpet, and a noise near enough by road that sends him to it and back
  • pots.rgs on an Area round the pots: the thief, who now carries an Area of her own, knocks into them and everyone in the group ears hears it
  • cat.rgs: the cat wanders to spots near by that it can reach, sits a while, and stops where it stands at a noise
On this page

A guard who walks by himself

Until now the corridor guard has gone up and down the corridor on his Path Follow, and nothing could send him anywhere else. From here a script gives the walkers their orders. Each script is a short file of Rengy Script, which the editor runs as it stands; Rengy Script covers the language, and each page from here prints its scripts whole and says what each line is for.

A script cannot walk a Path Follow anywhere: it can send a Nav Agent. So the corridor guard gets one, and a body under it, so that walls hold him as they hold the thief. The cat in chapter 5 had no body, and its agent moved it by its position; an agent on a body moves it by its velocity instead, and the body does the moving.

  1. Select guard_corridor in the Hierarchy. Click Add component and choose CharacterBodyComponent.
  2. Set its Size: click its x box once, so that its number is selected, type 8 and press Enter; then its y box, 6, Enter. Set Motion Mode to Floating and Driven By to Script. Scroll to the foot of the section, click the button beside Layer, untick the box labelled 0 and tick the one labelled 1, then click outside the list: the value saved is 2. Every other field stays as it comes.
guard_corridor selected, its Inspector narrowed with the search box, body, to its Character Body and scrolled to the section’s foot: a greyed Drift of 0.0 and 0.0, Gravity 1800.00, Speed 220.00 and the jumping, slopes and pushing rows at their own values, Grounded, On Wall, Shoving, On Ceiling and Drop Through unticked, Layer reading 1, and Debug Color; the corridor guard’s Hierarchy row carries no warning, and the status bar counts no problem; the Viewport shows the whole keep with the guard at the corridor’s west end
The corridor guard’s Character Body, scrolled to its Layer. Driven By Script, it raises no problem beside his Path Follow; left at Keys, it would.
Character Body Size
The box that walls hold, in world units, hung from the node’s feet: its bottom edge at the node, the box above it. 8 by 6 is the thief’s box, the bottom of a figure 16 high. There is no offset to set: the box always stands on the feet. A new body’s is 32 by 48, a platformer’s hero.
Character Body Motion Mode
Grounded falls and stands on floors, as a platformer’s hero does; Floating has no gravity and moves the same way in every direction, as everything in a keep seen from above does: gravity does not pull it, and the jumping rows are a Grounded hero’s.
Character Body Driven By
Who sets its velocity. Keys reads WASD and the arrows, as the thief’s does; Script reads none of them, and leaves its velocity to a script or, here, to the Nav Agent on the same node. A new body’s is Keys: a guard left so would walk wherever you walked her.
Character Body Layer
The collision layers the body is on, a row of boxes behind a button, as the Nav Region’s Mask was in chapter 2. 2 ticks the second box, labelled 1: the layer the thief’s body is on. The walls are on 1, the first box, labelled 0, and so is a new body.
  1. With guard_corridor still selected, click Add component and choose NavAgentComponent.
  2. Set Speed to 40, his old pace, clicking its box once, typing and pressing Enter. Tick Face Walk. Untick Use Links. Target and Moving stay as they come: his script sets both.
guard_corridor selected, its Inspector narrowed with the search box, navagent, to its Nav Agent: Target 0.0 and 0.0, Moving unticked, Speed 40.00, Arrive Distance 4.00, Face Walk ticked, Use Links unticked; his Hierarchy row carries a warning triangle and the status bar says 1 problem
The corridor guard’s Nav Agent: no Target, standing until his script sends him.
Use Links
The Nav Agent’s Use Links, unticked here: his way may not cross a Nav Link. The hole and the ladder are not his, so he never drops into the cellar and never climbs down to the gallery; his map is floor only. The cat keeps it ticked.
Speed
The Nav Agent’s Speed, in world units a second: 40, the pace his Path Follow kept. On a body, the agent sets the body’s velocity to it, and the body walks.
Face Walk
The Nav Agent’s Face Walk, ticked: his Sprite is mirrored to face left while he walks left. The guard is drawn facing right.
Moving
The Nav Agent’s Moving, left unticked: he stands until his script sends him, which ticks it.

One mover a node, again

The corridor guard still has the Path Follow chapter 1 gave him, and now a Nav Agent too. His row carries the warning triangle, and the status bar says 1 problem. Click the 1 problem: the Problems tab opens on its row.

The same, with the Problems tab open in the bottom dock: All 1, Errors 0, and under Warnings 1 one row for guard_corridor reading This node has a Nav Agent and a Path Follow, and the Path Follow moves it after the agent every step, so the agent’s walk is undone — keep one of the two, or move the Path Follow to a parent or a child
Two movers on the corridor guard, and the row the Problems tab gives them: the cat’s row in chapter 5, for him.
  1. With guard_corridor selected, remove his PathFollowComponent with the bin icon at the right of its header. The row leaves the Problems tab, and the triangle leaves his row.
  2. Press Ctrl+S.

He stays corridor_round’s child, and the path stays where it is, drawn in the Viewport: nothing follows it now, but it is the drawing of his round. Press F5 and he stands at his post: a Nav Agent walks only where it is sent, and nothing sends him yet.

His round, from a script

His script sends him to the far end of his round, and when that walk ends, to the other end, and so on for ever. It also listens for a noise, which the next section makes.

  1. In Assets, press Create, choose New script, type guard, and press Enter. In the Code tab, replace everything in guard.rgs with the script below, and press Ctrl+S.
rgs guard.rgs
/// The corridor guard walks his round from end to end. A noise near enough
/// by road sends him to it the shortest way round; he looks about a moment,
/// and goes back to his round.
enum Errand { Round, Noise }

component Guard {
    /// One end of his round.
    post: Vec2,
    /// The other end.
    far: Vec2,
    /// How far by road a noise can be and still be heard.
    #[suffix("u")]
    hearing: f32 = 320.0,
    /// What he is about.
    #[readonly]
    errand: Errand = .Round,
    /// Whether he is walking to the far end.
    #[hidden]
    outward: bool = true,

    fn on_create() {
        walk_to(far);
    }

    fn on_signal() {
        if heard("noise") && errand == .Round {
            let noise = kept_point("noise", position());
            let way = road_distance(noise);
            if way >= 0.0 && way < hearing {
                errand = .Noise;
                walk_to(noise);
            }
        }
        if heard("nav:ended") {
            if errand == .Noise {
                if arrived() {
                    await wait(2.0);
                }
                errand = .Round;
            } else {
                outward = !outward;
            }
            if outward {
                walk_to(far);
            } else {
                walk_to(post);
            }
        }
    }
}
  1. Select guard_corridor, click Add component and choose Guard.
  2. Set Post to 40, 232, where he stands, and Far to 600, 232, the corridor’s east end, each box clicked once, typed into and Enter. Hearing stays 320. Errand is greyed: his script sets it.
  3. Press Ctrl+S.
enum Errand { Round, Noise }
A list of names, one of which a field holds. errand is .Round while he walks his round and .Noise while he goes to a noise. The Inspector draws it as a list.
component Guard
The script’s component, added to a node like any other. Each field in it is a row in the Inspector, named from the field: post is Post. A field with = … starts at that value; post and far have none, so they start at 0, 0 until you type them.
#[suffix("u")], #[readonly], #[hidden]
How the Inspector draws the field under them. #[suffix("u")] prints a u, world units, after the number. #[readonly] draws it greyed, so you can watch it but not type into it. #[hidden] keeps it out of the Inspector: outward, which way he walks, is the script’s alone.
fn on_create()
Runs once, on the first step the node exists: as the game starts, he is sent to far.
walk_to
Sends this node’s Nav Agent to a point in the world by the cheapest way round walls and across the links it may use: it sets the agent’s Target and ticks Moving, and the agent plans the way. It returns at once; the walk goes on by itself.
fn on_signal() and heard
on_signal runs on a step when a signal has reached this node. heard("noise") is true on that step if the signal was noise. Several can arrive on one step, so the script asks about each.
nav:ended
The signal a walker’s node hears when its walk ends, however it ends: it arrived, it found no way there, or it was stopped. It is heard on the step after the walk ended. Here it turns him round at an end of his round, and ends a walk to a noise.
kept_point
Reads a point kept under a name, as it stood when the step began: kept_point("noise", position()) is where the last noise was, or his own position if no noise was ever kept. The pots keep it, in the next section.
position
Where this node is in the world, as the step began.
road_distance
How far this node would walk to a point by the cheapest way its agent may take, in world units, round walls and along the map: not as the crow flies. -1 when there is no way there. So a noise beyond a wall is as far as the walk round it, and one he cannot reach is never heard.
arrived
Whether this node’s Nav Agent reached where it was last sent, as the step began. After nav:ended, it tells a walk that got there from one that found no way, or was cut short.
await wait
wait(2.0) waits two seconds; await in front holds the rest of the hook there until then. He stands at the noise, looking about, for two seconds, and only then goes back to his round.

Read from the top: he is sent to far as the game starts. When that walk ends, outward flips and he is sent to post; when that one ends, back to far. On a noise he hears while on his round, he is sent to it if the walk there is shorter than hearing; when that walk ends he waits two seconds if he got there, and goes back to the end he was walking to.

  1. Press F5. Walk the thief out of the courtyard to the corridor, and watch him go east from his post.
  2. Select guard_corridor, press Bake on the strip, and in the strip’s Gizmos menu leave Routes and Navigation ticked and untick the rest. Press F5 again to stop.
Play with the camera pulled back, cut to the corridor: the corridor guard at his post at its west end, selected, and a lime line from him down onto the carpet’s top row, along the carpet the corridor’s whole length past the ladder, and up at the east end to a cross; under the corridor the gallery, the cellar’s grate and the courtyard’s mud
His way after 10 steps, a sixth of a second, of Play: down onto the carpet, along it, and up to far at the end. The camera was pulled back for this picture.

His post and his far end are both on the corridor’s upper row, and the straight way between them runs along that row. His way does not: it steps down onto the carpet, runs its whole length, and steps up at the end. The carpet is the road chapter 3 priced at 0.5, so a unit of carpet costs half a unit of floor, and the way along it is the cheaper one. This is the road chapter 3 promised, walked.

A noise

The pots hang on the kitchen’s top wall. Knocked into, they keep where the noise was and tell everyone listening. They need an Area, a box that notices other Areas, and a script that hears what it notices.

  1. Select pots. Click Add component and choose AreaComponent. In its Shape, set Size to 24 and 24. Every other field stays as it comes: Solid unticked, Offset 0, 0.
  2. In Assets, press Create, choose New script, type pots, and press Enter. In the Code tab, replace everything in pots.rgs with the script below, and 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.
component Pots : Area {
    fn on_enter(other: NodeId) {
        if in_group(other, "thief") {
            keep_point("noise", position());
            emit_to("ears", "noise");
        }
    }
}
  1. Select pots, click Add component and choose Pots. It has no fields.
pots selected, a small blue box with handles round the pots in the kitchen’s top row; the Inspector narrowed with the search box, areacomponent, to its Area: Shape Kind Rectangle, Size 24.0 and 24.0, Offset 0.0 and 0.0, Solid unticked, the Shape’s Layer 1, and under it the Area’s Layer and Mask rows each showing 0
The pots’ Area, 24 by 24, round them.
Area Shape Size
The box the Area notices with, in world units, about its node. 24 by 24 reaches past the pots’ own picture, so walking into the wall under them knocks them. A new Area’s is 64 by 64.
Area Layer and Mask
Left as they come: the Area is on layer 1 and notices Areas on layer 1. The rows show it as the box labelled 0, an unnamed layer’s box labelled by its place from 0, as the Nav Region’s Mask did in chapter 2.
component Pots : Area
A script with an Area base: it goes on a node that has an Area, and it hears what that Area meets. It has no fields.
on_enter
Runs on the step another Area starts to overlap this one, with that Area’s node as other. Once: standing on in it does not run it again; stepping out and in again does.
in_group
Whether that node was in the group as the step began. in_group(other, "thief") is how the pots tell the thief from anything else that brushes them.
keep_point
Keeps a point under a name until the game closes, for any script to read with kept_point. It lands at the end of the step, so a script reads it from the next step on: the step the noise is heard.
emit_to
Sends a signal to every node in a group. They hear it on the next step, in on_signal, as heard("noise"). A node not in the group hears nothing.

The pots tell the group ears, so the guard and the cat go into it. They notice only what is in the group thief, so she goes into that. And she needs an Area of her own.

  1. Select thief. Under Groups, type thief and press Enter. Click Add component and choose AreaComponent. In its Shape, set Size to 8 and 6, and Offset to 0 and -3: the box of her body, about its middle, three units up from her feet.
  2. Select guard_corridor. Under Groups, type ears and press Enter. Select cat and do the same.
  3. Press Ctrl+S.
The Area’s Shape Offset
Where the Area’s box sits from its node, in world units. The thief’s node is at her feet, and 0, -3 lifts the box’s middle three units, so that its bottom edge is her feet, as her body’s is. A new Area’s is 0, 0, its middle on the node, as the pots’ is.
Groups
Names a node carries, for scripts to ask about. A node can carry several. The thief carries thief; the corridor guard and the cat carry ears.
  1. Press F5. Walk the thief straight to the kitchen: out of the courtyard, west along the corridor to the opening in its north wall under the kitchen, up through it and into the pots. If the corridor guard is near enough by road, he leaves his round for them. If he is not, step out of the pots and back in when he is nearer.
  2. Select guard_corridor and watch Errand read Noise. Press F5 again to stop.
Play with the camera pulled back to the whole keep: the corridor guard in the opening between the corridor and the kitchen, selected, a lime line from him straight up into the kitchen and then across to a cross on the pots, where the thief stands; the Inspector narrowed with the search box, guard, to his Guard: Post 40.0 and 232.0, Far 600.0 and 232.0, Hearing 320.00 u, and Errand Noise, greyed
After 60 steps, 1 s, of Play: his way to the noise, his errand Noise. For this picture the thief was placed on the pots from the start, and the corridor guard under the kitchen’s opening, where his round takes him about 9 s in. The camera was pulled back for this picture.

From his post the pots are further by road than his Hearing of 320: the corridor alone, from his post to the opening, is longer than that. From the corridor’s east end they are too far as well. Under the kitchen’s opening the same noise is near enough, and he goes the shortest way to it, stands two seconds, and goes back to his round.

The cat wanders

The cat has walked to its bowl, and stayed. Its script makes it wander instead: it picks a spot near by, walks there if it can reach it without going the long way round, sits a while, and picks again. Its agent stays as it is, and its script sends it, so Moving goes off.

  1. Select cat. In its Nav Agent, untick Moving. Target keeps 424, 164; nothing walks there now, since the script sends the cat.
  2. In Assets, press Create, choose New script, type cat, and press Enter. In the Code tab, replace everything in cat.rgs with the script below, and 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.
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,

    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;
            }
        }
    }

    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;
            }
        }
    }
}
  1. Select cat, click Add component and choose Cat. Reach 80 and Longest 160 stay as the script declares them; Sitting is greyed.
  2. Press Ctrl+S.
fn loop()
Runs on every step while the node exists. While the cat sits, it counts sitting down a step at a time; at 0 it tries a spot.
random
A number from the first up to, not including, the second, new at every call and different every Play. Two calls give a spot up to reach, 80, from the cat each way.
vec2
Builds a point, or a step, from its x and its y: position() + vec2(…) is a spot that far from the cat.
can_reach
Whether this node could walk to a point from where it stood as the step began, round walls and across the links its agent may use. A spot on an island, such as the cellar seen from the hall, is not reachable.
stop_walking
Stops this node’s Nav Agent where it stands, unticking Moving. The walk ends, and nav:ended is heard on the next step.
nav:arrived
The signal a walker’s node hears when it reached its Target, on the step after, with nav:ended on the same step. The cat then sits 120 steps, two seconds.
nav:no_way
The signal a walker’s node hears when its agent found no way to its Target and stopped, with nav:ended on the same step. The cat then sits 30 steps and tries again.
Reach, Longest, Sitting
The Cat’s fields. Reach is how far from where it sits it looks for a spot, each way, in world units: 80. Longest is the longest way round it will walk to one: 160, so a spot just beyond a wall, which is near but a long walk, is let go. Sitting is the steps left to sit, greyed: the script counts it.

Each walk ends with nav:ended, and how long it sits after depends on how it ended: 120 steps after it arrived, 30 when there was no way, and 180 when a noise stopped it. While it walks, loop holds sitting at 0, which is how the script tells the last case: neither nav:arrived nor nav:no_way came with the end.

  1. Press F5. Walk the thief up into the hall, near the tables. Select cat, and watch its ways: within a few seconds it walks to a spot near by, sits, and picks again. Press F5 again to stop.
Play, looking through the game camera at the hall: the cat, selected, above the west table, and a lime line from it down to the left round the west table’s disc to a cross and ring on the floor; the thief beside the east table and the hall guard below; the Inspector shows the Cat, Reach 80.00 u, Longest 160.00 u, Sitting 0, and its Nav Agent, Target 99.359 and 123.095, Moving ticked
After 240 steps, 4 s, of Play: the cat’s second walk, round the west table to a spot it chose. Your cat picks other spots: random gives other numbers every Play. For this picture the thief was placed in the hall from the start.

What the chapter set

NodeComponentFieldValue
guard_corridorCharacter BodySize8, 6
Motion ModeFloating
Driven ByScript
Layer2: the box labelled 1
Nav AgentSpeed40
Face Walkticked
Use Linksunticked
GuardPost40, 232
Far600, 232
nodeGroupsears
Path Followremoved
NodeComponentFieldValue
potsAreaShape Size24, 24
Potsadded
thiefnodeGroupsthief
AreaShape Size8, 6
Shape Offset0, -3
catnodeGroupsears
Nav AgentMovingunticked
Catadded

Every other field is as it comes, or as its script declares it: the guard’s Hearing 320, the cat’s Reach 80 and Longest 160.

Check it works