Skip to content

Tutorials · Navigation and AI · Chapter 8

A brain

30 minutes Uses: AnimatedSprite, StateMachine, CharacterBody, NavAgent, Sprite, PathFollow, angle_between, arrived, can_reach, can_see, emit_to, heard, kept_point, move_to, nearest, node, position, position_of, random, road_distance, set_number, set_param_flag, sqrt, tau, vec2, walk_to

By the end

  • art/guard.png and art/guard.anim: the guard’s three pictures on one sheet, cut into the clips idle, walk and alert
  • art/guard.asm: a graph that picks his clip from his speed and the switch alert, and nothing else
  • guard.rgs: his mind, five modes, on both guards, each with his own round’s corners
  • The hall guard walking by a Nav Agent, his Path Follow gone, with a cone of his own
  • thief.rgs: caught, she starts again where she came in
On this page

His clips

A guard is one picture cut from the keep’s sheet, the same whether he strolls or runs. From this chapter he has three poses: standing, a step, and alert, with a red mark over his head. They came with the starter, in art/: guard-idle.png, guard-step.png and guard-alert.png. Make Animation… packs them onto one sheet, and the Sprite sheet tab cuts the sheet into clips. Animation and Cutscenes’ Frames chapter teaches both tools at length; here they are used once.

  1. In Assets, open art, right-click guard-alert.png, and choose Make Animation…. The window opens with guard-alert.png ticked, Name guard-alert, Frames a second 12.0 and Loop ticked.
  2. Tick guard-idle.png and guard-step.png too. They play in name order: alert, idle, step.
  3. Set Frames a second to 6: click its box once, type and press Enter. Then click Name, delete guard-alert and type guard; Enter in that box is the same as Create. Leave Loop ticked, and press Create. It writes art/guard.png, a sheet 64 by 32 holding the three pictures side by side, and art/guard.anim, with one clip called guard that plays them, 6 a second, looping.
The Make Animation window over the editor, listing the pictures in art from carpet.png to hatch.png with guard-alert.png, guard-idle.png and guard-step.png ticked, Name still guard-alert, Frames a second 12.0 and Loop ticked, and the Create and Cancel buttons; behind it the Viewport looks through the game camera at the corridor
The three pictures ticked. Name and Frames a second are still as the window opened; the steps above set them to guard and 6.
Make Animation’s Name
What the two files it writes are called, and the one clip in them: guard gives art/guard.png, art/guard.anim and a clip guard.
Make Animation’s Frames a second
How many of its pictures that clip shows a second: at 6, each one stays a sixth of a second.
Make Animation’s Loop
Ticked, the clip starts again from its first picture when it ends; unticked, it stops on its last.

The guard clip is the three pictures as three whole frames. Nothing plays it; leave it where it is. Its frames say where each picture lies on the sheet, in the sheet’s own pixels, and the cuts below start from those corners.

PictureFrame of guardRegion xRegion yWidthHeight
guard-alert0001624
guard-idle11801616
guard-step23601616

The corridor guard draws the sheet

  1. Select guard_corridor. In his Sprite, drag art/guard.png from Assets onto Texture, and clear Atlas Region, which says guard: click its box, delete the word and press Enter.
  2. Click Add component and choose AnimatedSpriteComponent. Drag art/guard.anim from Assets onto its Animations. Clip stays idle, as it comes. Tick Frame Size.
Atlas Region
The Sprite’s Atlas Region: a named part of its Texture to draw; empty, it draws the whole picture. guard named the guard’s part of the keep’s sheet, and guard.png has no part of that name. Emptied, the Sprite holds the whole sheet, and the Animated Sprite draws one frame of it at a time.
Animations
The Animated Sprite’s Animations: the .anim file it plays, which holds the clips and names the sheet they are cut from: guard.anim.
Clip
The Animated Sprite’s Clip: the clip it plays, by name. A new one says idle, which is the guard’s standing clip once it is cut below. From the next section his graph chooses it.
Frame Size
The Animated Sprite’s Frame Size: ticked, each frame is drawn at its own size and stood on its pivot. The alert picture is 24 tall and the others 16, so ticked, the alert one stands on his feet with the mark above his head, where unticked every frame would be squeezed into the Sprite’s Size.

Three clips

  1. With guard_corridor still selected, double-click art/guard.png in Assets. The Sprite sheet tab opens on it; scroll over the sheet to zoom in.
  2. Idle. Under Grid, set Cell to 16 and 16, Margin to 18 and 0, and Spacing to 0 and 0. Click cell 0, 0. Under Clips, press From selection, name the clip idle, set its rate to 6 fps and its loop to Loop, and press Generate 1. Press Clear.
  3. Walk. Keep Cell and Margin, and set Spacing to 2 and 0: the gap the packing left between the idle and the step pictures. Drag from cell 0, 0 to cell 1, 0. Press From selection, name it walk, 6 fps, Loop, Generate 1, Clear.
  4. Alert. Set Cell to 16 and 24, Margin to 0 and 0, and Spacing to 0 and 0. Click cell 0, 0. Press From selection, name it alert, 6 fps, Loop, Generate 1, Clear.
  5. In the Animation tab, press Save. It writes the three clips into art/guard.anim, after the guard clip.
ClipCellMarginSpacingCellsRateLoop
idle16, 1618, 00, 00, 06 fpsLoop
walk16, 1618, 02, 00, 0 to 1, 06 fpsLoop
alert16, 240, 00, 00, 06 fpsLoop
The Animation tab on guard_corridor, atlas 64x32: the clips guard (3), idle (1), walk (2) and alert (1) as tabs, walk lit, Loop lit and 6.0 fps; Frame 1 of 2; under Frames its first frame, dur 0.16667, pivot x 0.50 and y 1.00, Region x 18, y 0, w 16, h 16; at the foot the sheet’s three pictures, the alert one with its red mark, the start of a strip cut off by the tab’s edge
walk, two frames: the idle picture, then the step. Its first frame’s Region is the idle picture’s, 18, 0, 16 by 16.
Cell, Margin and Spacing
The Sprite sheet tab’s grid. Cell is one frame’s size in the sheet’s pixels; Margin is where the grid starts from the sheet’s top left; Spacing is the gap between one cell and the next. The packing left 2 pixels between the pictures, so walk’s second cell starts at 36, not 34.
A frame’s pivot
The point of the frame that stands on the node, from 0 to 1 each way. A cut frame gets 0.5, 1: the middle of its bottom edge, his feet. The three pictures are all drawn standing on their bottom row, so every frame keeps it.

A graph picks his clip

Which clip he plays is the business of a State Machine and its graph. The graph has three states, each playing one clip, and it chooses between them from two things: how fast his body moves, and a switch called alert that his script turns on while he chases or searches. That is all it does. His mind, what he does next and why, is his script, in the next section; the graph only dresses him for it.

  1. With guard_corridor selected, click Add component and choose StateMachineComponent. Its Graph is empty. Click the Graph tab, and press New state machine. It writes art/guard.asm beside art/guard.anim, with one state, idle, playing the clip idle, and puts it in the State Machine’s Graph.
  2. In the Parameters list, type alert into the name box, choose switch in the list of kinds beside it, and press Add. Its row reads alert, switch, unticked: it starts off.
  3. In the row over the canvas, type walk into State and walk into Clip, and press Add state. Do the same with alert and alert. Drag the new boxes apart: walk to the right of idle, and alert below the two, between them.
  4. Click the Any State box, press Link and click alert. In its row in the Transitions list, numbered 0, click Is and type alert into the box. Leave the box beside it ticked, as Is gives it, so it reads true.
  5. The new arrow runs from Any State, behind idle, down to alert. Drag the Any State box down to the right of alert, level with it, so the arrow runs clear of idle. Its place is the tab’s, not the file’s: the next time the tab opens, the box is back above idle.
  6. Click alert, press Link and click idle. In row 1, click Is, type alert, and untick the box, so it reads false.
  7. Click idle, press Link and click walk. In row 2, click >, type speed, and set the number beside it to 5: click it once, type, Enter.
  8. Click walk, press Link and click idle. In row 3, click <, type speed, and set the number to 5 the same way. Set its Blend, the box with an s after its number, to 0.1.
  9. Press Save in the Graph tab’s bar, and Ctrl+S.
The Graph tab on guard_corridor, Entry idle: idle with its green entry dot, walk to its right with an arrow each way labelled speed > 5 and speed < 5 - 0.10s, alert below them with an arrow up to idle labelled alert is false, and the dashed Any State box to the right of alert, level with it, its arrow into alert labelled alert is true; the Parameters list at the right names alert, a switch, unticked, and grounded, speed, velocity_x and velocity_y marked the body; under it the Rehearse section, In: idle, Nothing fires from idle
The finished graph, with Any State dragged beside alert as the step above does.
The Transitions list, headed First match wins: row 0, Any State to alert, 0.00 s, Is lit, alert, true ticked; row 1, alert to idle, Is lit, alert, false; row 2, idle to walk, greater than lit, speed, 5.00; row 3, walk to idle, 0.10 s, less than lit, speed, 5.00
The four arrows, in order. The number at each row’s left is its place.
RowFromToConditionBlend
0Any Statealertalert Is true0
1alertidlealert Is false0
2idlewalkspeed > 50
3walkidlespeed < 50.1
Graph
The State Machine’s Graph: the .asm file it runs, guard.asm. Each state plays a clip of the node’s Animated Sprite, and the arrows say when to go from one to another. The entry, idle, is where it starts.
speed
A parameter the guard’s Character Body writes on every step: how fast he moves, in world units a second. He walks at 40 and stands at 0, so 5 tells walking from standing with room to spare.
A transition’s Blend
How long, in seconds, the old clip fades into the new one when the arrow is taken. 0 is a cut, and every new arrow starts there; 0.1 softens his stop from a walk.
alert, a switch
A parameter you declared, on or off, and off as it starts. His script turns it on while he chases or searches, and off otherwise. Any State to alert takes him there from either other state the step it goes on, and alert to idle brings him back the step it goes off.

His mind, as a script

In chapter 7 the corridor guard had an errand: his round, a noise, or a look. From here he has a mode, one of five. He walks his round corner to corner (Patrol). A glimpse of her, or a noise near enough by road, makes him creep to where it was (Suspicious). Seen near enough to be sure, she is chased at a run (Chase), and caught she is sent back to where she came in. Lost, she is searched for about where he saw her last, for six seconds (Search), and then he goes back to his round (Return), and patrols again once he is on it.

One script does this for both guards. His round is no longer two ends of the corridor: it is up to four corners, so the hall guard’s rectangle fits it too.

  1. Click the Code tab, then guard.rgs in its Scripts list. Replace everything in it with the script below, and press Ctrl+S.
rgs guard.rgs
/// A guard's mind. He walks his round, corner to corner. A glimpse of the
/// thief, or a noise near enough by road, makes him suspicious: he creeps to
/// where he glimpsed her or heard it. Seen near enough to be sure, she is
/// chased at a run, and caught she is sent back to where she came in. Lost,
/// she is searched for about where he saw her last, for a while, and then he
/// goes back to his round.
enum Mode { Patrol, Suspicious, Chase, Search, Return }

component Guard {
    /// His round's first corner.
    a: Vec2,
    /// Its second.
    b: Vec2,
    /// Its third; a round of two corners never comes here.
    c: Vec2,
    /// Its fourth.
    d: Vec2,
    /// How many corners his round has: 2 or 4.
    corners: i32 = 4,
    /// How far by road a noise can be and still be heard.
    #[suffix("u")]
    hearing: f32 = 320.0,
    /// How far he sees.
    #[suffix("u")]
    sight: f32 = 112.0,
    /// How far to either side of straight ahead he sees, in radians.
    cone: f32 = 0.6,
    /// The collision layers that hide what is behind them.
    walls: i32 = 1,
    /// How near he must see her to be sure it is her.
    #[suffix("u")]
    sure: f32 = 64.0,
    /// How fast he walks his round.
    walk: f32 = 40.0,
    /// How fast he creeps when suspicious.
    creep: f32 = 24.0,
    /// How fast he runs after her.
    run: f32 = 72.0,
    /// How long he searches, in steps.
    patience: i32 = 360,
    /// What is on his mind.
    #[readonly]
    mode: Mode = .Patrol,
    /// Whether the thief is in his cone, as this step began.
    #[readonly]
    sees: bool = false,
    /// Which way he faces: the way he last walked.
    #[hidden]
    facing: Vec2,
    /// Where he stood the step before.
    #[hidden]
    was: Vec2,
    /// Where he saw her last.
    #[hidden]
    last_seen: Vec2,
    /// The corner he is walking to, from 0.
    #[hidden]
    next: i32 = 0,
    /// Whether to set off for that corner this step.
    #[hidden]
    to_round: bool = true,
    /// Steps of searching left.
    #[hidden]
    searching: i32 = 0,

    fn on_create() {
        was = position();
        facing = vec2(0.0, 1.0);
    }

    fn loop() {
        let moved = position() - was;
        if moved.length() > 0.1 {
            facing = moved;
        }
        was = position();
        let thief = nearest("thief");
        let at = position_of(thief);
        let toward = at - position();
        // Returning, he has given up; he looks again on his round.
        sees = mode != .Return
            && thief != node(0)
            && toward.length() < sight
            && angle_between(facing, toward) < cone
            && can_see(at, walls);
        if sees {
            last_seen = at;
        }

        if mode == .Patrol || mode == .Return {
            if sees {
                mode = .Suspicious;
                set_number("NavAgentComponent", "speed", creep);
                walk_to(at);
            }
        } else if mode == .Suspicious {
            if sees && toward.length() < sure {
                mode = .Chase;
                set_number("NavAgentComponent", "speed", run);
            }
        } else if mode == .Chase {
            if !sees {
                mode = .Search;
                searching = patience;
                walk_to(last_seen);
            } else if toward.length() < 10.0 {
                emit_to("thief", "caught");
                mode = .Return;
                to_round = true;
            } else {
                walk_to(at);
            }
        } else if mode == .Search {
            searching -= 1;
            if sees {
                mode = .Chase;
                set_number("NavAgentComponent", "speed", run);
            } else if searching <= 0 {
                mode = .Return;
                to_round = true;
            }
        }

        if to_round {
            to_round = false;
            set_number("NavAgentComponent", "speed", walk);
            if next == 0 {
                walk_to(a);
            } else if next == 1 {
                walk_to(b);
            } else if next == 2 {
                walk_to(c);
            } else {
                walk_to(d);
            }
        }
        set_param_flag("alert", mode == .Chase || mode == .Search);
    }

    fn on_signal() {
        if heard("noise") && (mode == .Patrol || mode == .Return) {
            let noise = kept_point("noise", position());
            let way = road_distance(noise);
            if way >= 0.0 && way < hearing {
                mode = .Suspicious;
                set_number("NavAgentComponent", "speed", creep);
                walk_to(noise);
            }
        }
        if heard("nav:ended") {
            if mode == .Patrol || mode == .Return {
                if arrived() {
                    next += 1;
                    if next >= corners {
                        next = 0;
                    }
                }
                mode = .Patrol;
                to_round = true;
            } else if mode == .Suspicious {
                mode = .Return;
                to_round = true;
            } else if mode == .Search {
                let far = 40.0 * sqrt(random(0.0, 1.0));
                let turn = random(0.0, tau());
                let spot = last_seen + vec2(far * turn.cos(), far * turn.sin());
                if can_reach(spot) {
                    walk_to(spot);
                } else {
                    walk_to(last_seen);
                }
            }
        }
    }
}

Select guard_corridor and look at his Guard in the Inspector. The new fields are there, A to D, Corners, Sure and the rest, at the script’s values. But Post, Far and Errand are still there too, though the script no longer has them.

  1. With guard_corridor selected, remove his Guard with the bin icon at the right of its header. Click Add component and choose Guard.
  2. Set A to 40, 232 and B to 600, 232, the two ends of his round, each box clicked once, typed into and Enter. Set Corners to 2. C and D stay 0, 0: a round of two corners never comes to them. Every other field stays as the script gives it.
  3. Press Ctrl+S.
enum Mode { Patrol, Suspicious, Chase, Search, Return }
The five things on his mind, by name. Mode holds one of them, and the script compares and sets it as .Patrol, .Chase and so on. An enum is a closed list: a mode not on it cannot be written, or misspelled.
A, B, C, D and Corners
His round, as corners in world units, in order, and how many of them he walks: 2 or 4. He walks from one to the next, and from the last back to A. The corridor guard’s two are the corridor’s ends; the hall guard’s four are hall_round’s corners. Corners default to 0, 0, and Corners to 4.
Sure
How near he must see her to be sure it is her and chase: 64 world units, four tiles. Further away, still in his Sight of 112, she is a glimpse, and he only creeps.
Walk, Creep and Run
His three paces, in world units a second, which his script writes onto his Nav Agent’s Speed: 40 on his round, as before; 24, slow and wary, when suspicious; 72 in a chase. She runs at 70, so he gains on her by two units a second.
Patience
How long he searches, in steps: 360, six seconds at sixty steps a second. Chapter 9 hands this to a Timer.
Mode
What is on his mind: read-only, greyed in the Inspector, and live while the game plays. Select a guard in Play and watch it change.
set_number
Writes a number onto another component on this node: set_number("NavAgentComponent", "speed", run) sets his Nav Agent’s Speed to his Run, from the next step on. The script changes his pace this way whenever his mode changes.
set_param_flag
Sets a switch his State Machine reads, from the next step. The last line of his loop sets alert on every step: on while he chases or searches, off otherwise. It is the one thing his script tells his graph.

What he sees, and when he looks

rgs part of guard.rgs
        // Returning, he has given up; he looks again on his round.
        sees = mode != .Return
            && thief != node(0)
            && toward.length() < sight
            && angle_between(facing, toward) < cone
            && can_see(at, walls);
        if sees {
            last_seen = at;
        }

His eyes are chapter 7’s, with one more condition first: while he is returning, he does not look. He has given up, and he looks again once he is back on his round and patrolling. Whenever he sees her, he keeps where: last_seen, which a search starts from.

Patrol and Return: a glimpse

rgs part of guard.rgs
        if mode == .Patrol || mode == .Return {
            if sees {
                mode = .Suspicious;
                set_number("NavAgentComponent", "speed", creep);
                walk_to(at);
            }

On his round, a glimpse makes him suspicious: he slows to Creep and walks to where she stood when he saw her, not after her. If she steps out of sight, he goes there all the same.

Suspicious: sure of her

rgs part of guard.rgs
        } else if mode == .Suspicious {
            if sees && toward.length() < sure {
                mode = .Chase;
                set_number("NavAgentComponent", "speed", run);
            }

Creeping, he chases the moment he sees her nearer than Sure, at Run. If he gets where he was going and has not, his walk ends, and the nav:ended part below sends him back to his round.

Chase: lost, caught, or after her

rgs part of guard.rgs
        } else if mode == .Chase {
            if !sees {
                mode = .Search;
                searching = patience;
                walk_to(last_seen);
            } else if toward.length() < 10.0 {
                emit_to("thief", "caught");
                mode = .Return;
                to_round = true;
            } else {
                walk_to(at);
            }

In a chase he walks to where she is on every step. The step he cannot see her, he searches, starting where he saw her last. Within 10 of her, she is caught: he tells her so with emit_to the group thief, and goes back to his round.

Search: found again, or given up

rgs part of guard.rgs
        } else if mode == .Search {
            searching -= 1;
            if sees {
                mode = .Chase;
                set_number("NavAgentComponent", "speed", run);
            } else if searching <= 0 {
                mode = .Return;
                to_round = true;
            }

Searching, he counts down Patience one step at a time. Seen again, she is chased again. When the count runs out, he returns.

Back to his round, and his clip

rgs part of guard.rgs
        if to_round {
            to_round = false;
            set_number("NavAgentComponent", "speed", walk);
            if next == 0 {
                walk_to(a);
            } else if next == 1 {
                walk_to(b);
            } else if next == 2 {
                walk_to(c);
            } else {
                walk_to(d);
            }
        }
        set_param_flag("alert", mode == .Chase || mode == .Search);

Every mode that ends in his round sets to_round, and this part sends him: at Walk, to the corner he was walking to before. Then the switch for his graph.

What he hears

rgs part of guard.rgs
    fn on_signal() {
        if heard("noise") && (mode == .Patrol || mode == .Return) {
            let noise = kept_point("noise", position());
            let way = road_distance(noise);
            if way >= 0.0 && way < hearing {
                mode = .Suspicious;
                set_number("NavAgentComponent", "speed", creep);
                walk_to(noise);
            }
        }
        if heard("nav:ended") {
            if mode == .Patrol || mode == .Return {
                if arrived() {
                    next += 1;
                    if next >= corners {
                        next = 0;
                    }
                }
                mode = .Patrol;
                to_round = true;
            } else if mode == .Suspicious {
                mode = .Return;
                to_round = true;
            } else if mode == .Search {
                let far = 40.0 * sqrt(random(0.0, 1.0));
                let turn = random(0.0, tau());
                let spot = last_seen + vec2(far * turn.cos(), far * turn.sin());
                if can_reach(spot) {
                    walk_to(spot);
                } else {
                    walk_to(last_seen);
                }
            }
        }
    }

A noise is chapter 6’s, heard only while he patrols or returns, and it makes him suspicious too. nav:ended is heard the step after any walk of his ends, arrived or not. On his round, an arrival moves him on to the next corner; returning, it makes him patrol again. Suspicious, the end of his walk is the end of the matter: he returns. Searching, it sends him to another spot: somewhere in a disc of 40 about where he saw her last, if he can reach it, or to that point itself.

sqrt
The square root of a number; 0 for a negative one. 40.0 * sqrt(random(0.0, 1.0)) is a distance from 0 to 40 that falls as often near the rim of the disc as near its middle, so his spots spread over the whole disc instead of bunching at its centre.
tau
One full turn, in radians: about 6.283. random(0.0, tau()) is a direction, any way round.
.cos() and .sin()
The cosine and sine of an angle in radians: vec2(far * turn.cos(), far * turn.sin()) is the point far away in the direction turn. With 0 along +x and a quarter turn straight down, as chapter 7’s angles are.

His mind in Play

  1. Press F5. Select guard_corridor, and in the Inspector type guard into the search box at its top and press Enter, so only his Guard shows. In Play, press Enter after typing in a box: while it has the cursor, W, A, S and D type into it as well as walking her. Wait: he walks east along the corridor into the view about eight seconds in, Mode Patrol.
  2. Walk the thief onto the carpet in front of him. Sees ticks, Mode reads Suspicious and he slows; nearer, Chase, the red mark over his head, and he runs.
  3. Press F5 again to stop.
Play, looking through the game camera at the corridor and the courtyard below it: the map’s wash over the floor; the corridor guard on the carpet, selected, walking east, a lime line from him along the carpet to a cross at its east end; the thief at the courtyard gate; the Inspector narrowed with the search box, guard, to his Guard: A 40.0 and 232.0, B 600.0 and 232.0, C and D 0.0 and 0.0, Corners 2, Hearing 320.00 u, Sight 112.00 u, Cone 0.60, Walls 1, Sure 64.00 u, Walk 40.00, Creep 24.00, Run 72.00, Patience 360, Mode Patrol and Sees unticked, both greyed; the status bar says Baked navigation for "nav": 3520 cells, 1340 blocked, 232 priced
After 600 steps, 10 s, of Play: on his round, Mode Patrol, walking to his second corner. The thief is where the game starts her, and Bake drew the map under his route.
Play, looking through the game camera at the corridor’s west end: the corridor guard on the carpet with a red mark over his head, a lime line from him to the thief a few steps east of him, his pale cone on her; the map’s wash over the floor; the Inspector shows his Guard, Mode Chase and Sees ticked, and the Animation tab below has alert lit; the status bar says Baked navigation for "nav": 3520 cells, 1340 blocked, 232 priced
After 45 steps, 0.75 s, of Play: Mode Chase, his alert clip, his way to her. For this picture the thief was placed on the carpet ahead of him from the start, where the step walks her, and Bake drew the map under his route.

The hall guard too

The hall guard still goes round on the Path Follow chapter 1 gave him. He gets what the corridor guard got in chapters 6 to 8 at once: a body and a Nav Agent, his clips and his graph, the same script with his own corners, and a cone.

  1. Select guard_hall. Click Add component and choose CharacterBodyComponent. Set its Size to 8, 6, Motion Mode to Floating, Driven By to Script, and Layer to 2 (untick the box labelled 0, tick the one labelled 1), as chapter 6 did for the corridor guard.
  2. Click Add component and choose NavAgentComponent. Set Speed to 40, tick Face Walk and untick Use Links. Target, Moving and Arrive Distance stay as they come.

His row carries the warning triangle, and the status bar says 1 problem: two movers on one node, as the cat and the corridor guard had. Click the 1 problem.

Edit mode, looking through the game camera at the great hall: the hall guard at the round’s top left corner, selected, his pale cone reaching across the hall, the round’s rectangle round the two tables, the thief by the tables, the cat at the hearth and the bell on the north wall; the Inspector narrowed with the search box, navagent, to his 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 the chips guards and ears and a warning triangle, with cone_hall under it; the Problems tab in the bottom dock shows one row for guard_hall 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 hall guard, and the row the Problems tab gives them. The picture is taken from the finished chapter with his Path Follow put back, so his groups, his clips and his cone, which the next steps add, are already there; and the thief was placed in the hall, so that looking through the game camera shows him.
  1. With guard_hall selected, remove his PathFollowComponent with the bin icon at the right of its header. The row leaves the Problems tab. hall_round stays, the drawing of his round.
  2. Under Groups, type guards into Add group and press Enter; then ears, Enter.
  3. In his Sprite, drag art/guard.png onto Texture and clear Atlas Region.
  4. Click Add component and choose AnimatedSpriteComponent. Drag art/guard.anim onto Animations, and tick Frame Size.
  5. Click Add component and choose StateMachineComponent. Drag art/guard.asm from Assets onto its Graph: the one graph serves both guards.
  6. Click Add component and choose Guard. Set A to 56, 72, B to 264, 72, C to 264, 168 and D to 56, 168: hall_round’s four corners, in its order. Corners stays 4.
  7. With guard_hall selected, press + Add node, choose Sprite, and rename it cone_hall. Set its Position to 0, -8; drag art/cone.png onto Texture; set Size to 112, 154, Anchor to 0, 0.5 and Sort Layer to 1. Click Add component and choose Cone: the corridor guard’s cone, for him.
  8. Select guard_corridor, and under Groups add guards. He is in ears already, since chapter 6.
  9. Press Ctrl+S.
Edit mode, looking through the game camera at the great hall: the hall guard at the round’s top left corner, selected, his pale cone reaching east, the round’s rectangle round the tables, the cat at the hearth, the bell on the north wall and the thief between the tables; in the Hierarchy guard_hall carries the chips guards and ears and has cone_hall under it; the Inspector narrowed with the search box, guard, to his Guard: A 56.0 and 72.0, B 264.0 and 72.0, C 264.0 and 168.0, D 56.0 and 168.0, Corners 4, the rest at the script’s values, Mode Patrol
The hall guard’s Guard, his four corners. For this picture the thief was placed in the hall, so that looking through the game camera shows him.
Speed, Face Walk and Use Links
The hall guard’s Nav Agent, as the corridor guard’s: Speed 40 until his script changes it, Face Walk ticked so he faces his walk, Use Links unticked so the hole is not his way to the cellar.
Groups guards and ears
Both guards are in both. ears is the group the pots tell of a noise, from chapter 6; guards is the one chapter 9’s bell calls.

Caught

A guard who catches the thief says caught to the group thief, which is her. Her script hears it and puts her back where she came in, at the courtyard gate.

  1. In Assets, press Create, choose New script, type thief, and press Enter. In the Code tab, replace everything in thief.rgs with the script below, and press Ctrl+S.
  2. Select thief, click Add component and choose Thief. Its one field, start, is hidden.
  3. Press Ctrl+S.
rgs thief.rgs
/// The thief. Caught by a guard, she is put back where she came in.
component Thief {
    /// Where she starts again when she is caught.
    #[hidden]
    start: Vec2,

    fn on_create() {
        start = position();
    }

    fn on_signal() {
        if heard("caught") {
            move_to(start);
        }
    }
}
start
Where she stood as the game began, kept by on_create, which runs once, on her first step. Hidden: nothing to type.
move_to
Puts this node at a point, in world units, at once: no walk, no wall in the way. move_to(start) takes her from the guard’s hands to the gate in one step.
caught
The signal a guard sends with emit_to("thief", "caught") when he is within 10 of her. She hears it on the next step, so she is moved one step after he comes within 10 of her.

The guard who caught her goes back to his round without looking. If she runs straight back past him he does not see her until he has reached his round again.

What the chapter set

The clips are in the tables of the first section, and the graph’s four arrows in the second. Both guards, and the hall guard’s cone:

NodeComponentFieldValue
guard_hallnodeGroupsguards, ears
SpriteTextureguard.png
Atlas Regionempty
Character BodySize8, 6
Motion ModeFloating
Driven ByScript
Layer2: the box labelled 1
Nav AgentSpeed40
Face Walkticked
Use Linksunticked
Animated SpriteAnimationsguard.anim
Frame Sizeticked
State MachineGraphguard.asm
GuardA56, 72
B264, 72
C264, 168
D56, 168
Path Followremoved
cone_hall, under guard_hallnodePosition0, -8
SpriteTexturecone.png
Size112, 154
Anchor0, 0.5
Sort Layer1
Coneadded
guard_corridornodeGroupsears, guards
SpriteTextureguard.png
Atlas Regionempty
Animated SpriteAnimationsguard.anim
Frame Sizeticked
State MachineGraphguard.asm
GuardA40, 232
B600, 232
Corners2
thiefThiefadded

Every other field is as it comes, or as the script gives it: Hearing 320, Sight 112, Cone 0.6, Walls 1, Sure 64, Walk 40, Creep 24, Run 72, Patience 360, and the hall guard’s Corners 4.

Check it works