Tutorials · Navigation and AI · Chapter 9
Timers
By the end
- A Timer on each guard, six seconds, one shot, that his script starts when he loses the thief and that sends him back to his round when it runs out
- A Timer on the bell, four seconds, held until a guard chases her and held again when he stops
bell.rgs: the bell shows what is left of its countdown, and when it runs out it swings and calls every guard to where she was last seen
On this page
A search that ends
In chapter 8 a guard counted his own search down, one step at a time, in Patience. A Timer counts for him: a countdown on his node, in seconds, that his script starts, stops, and hears when it runs out. Each guard gets one, and his script hands it the search.
- Select
guard_corridor. Click Add component and choose TimerComponent. - Set Wait Time to
6, clicking its box once, typing and pressing Enter. Tick One Shot. Untick Autostart. Paused stays unticked. - Select
guard_halland do the same: Wait Time6, One Shot ticked, Autostart unticked.
- Wait Time
- The Timer’s Wait Time: how long its countdown lasts, in seconds.
6is the search chapter 8 counted in steps,360of them. A new Timer’s is1; at0it never fires. - One Shot
- The Timer’s One Shot: ticked, it fires once at the end of its countdown and stops. Unticked, it starts the next countdown at once and fires every Wait Time, which suits a vent or a drip, not a search. A new Timer’s is unticked.
- Autostart
- The Timer’s Autostart: ticked, it starts counting on the node’s first step. A guard’s is unticked: his search starts when he loses her, and a countdown from the first step would fire six seconds into the game, for no search at all. A new Timer’s is ticked.
- Paused
- The Timer’s Paused: ticked, its countdown holds where it is, neither running down nor firing, until it is unticked. The guards leave it unticked; the bell, below, starts with it ticked.
- Time Left
- The Timer’s Time Left, read-only: the seconds left of its countdown, live in Play.
0means stopped: not started yet, run out as a one shot, or stopped by a script.
His script hands it the search
- Click the Code tab, then
guard.rgsin its Scripts list. Replace everything in it with the script below, and press Ctrl+S.
/// 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 until his Timer runs out,
/// and then he goes back to his round. He tells the bell when he starts and
/// stops chasing, and when it rings he comes to search where she was last
/// seen.
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,
/// 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,
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);
emit_to("bell", "spotted");
}
} else if mode == .Chase {
if !sees {
mode = .Search;
start_timer();
emit_to("bell", "lost");
walk_to(last_seen);
} else if toward.length() < 10.0 {
emit_to("thief", "caught");
emit_to("bell", "lost");
mode = .Return;
to_round = true;
} else {
keep_point("seen", at);
walk_to(at);
}
} else if mode == .Search {
if sees {
stop_timer();
mode = .Chase;
set_number("NavAgentComponent", "speed", run);
emit_to("bell", "spotted");
}
}
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("alarm") && (mode == .Patrol || mode == .Return) {
mode = .Search;
last_seen = kept_point("seen", position());
start_timer();
set_number("NavAgentComponent", "speed", run);
walk_to(last_seen);
}
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);
}
}
}
// An arrival on the step the search runs out is the search's, so it is heard first.
if heard("timer:fired") && mode == .Search {
mode = .Return;
to_round = true;
}
}
}
The script no longer has Patience, or the hidden count it kept, and each guard’s Guard still shows Patience: the editor keeps a field a script stops declaring, as chapter 8 said. Clear it the same way.
- Select
guard_corridor, remove his Guard with the bin icon at the right of its header, click Add component and choose Guard. Set A to40,232, B to600,232and Corners to2, as in chapter 8. - Select
guard_halland do the same, with A56,72, B264,72, C264,168and D56,168. Corners stays4. - Press Ctrl+S.
Four parts changed. A chase that loses her starts his Timer, and tells the bell, below:
} else if mode == .Chase {
if !sees {
mode = .Search;
start_timer();
emit_to("bell", "lost");
walk_to(last_seen);
} else if toward.length() < 10.0 {
emit_to("thief", "caught");
emit_to("bell", "lost");
mode = .Return;
to_round = true;
} else {
keep_point("seen", at);
walk_to(at);
}
Seen again while he searches, the Timer is stopped, and the chase starts again:
} else if mode == .Search {
if sees {
stop_timer();
mode = .Chase;
set_number("NavAgentComponent", "speed", run);
emit_to("bell", "spotted");
}
}
And when the Timer runs out, he returns. This is the last thing his on_signal asks:
// An arrival on the step the search runs out is the search's, so it is heard first.
if heard("timer:fired") && mode == .Search {
mode = .Return;
to_round = true;
}
- start_timer
- Starts this node’s Timer on a fresh countdown of its Wait Time: six seconds from the step he loses her. Started again while it runs, it starts from six again.
- stop_timer
- Stops this node’s Timer and throws away what was left, as the step ends: seen again, his search is over, and a later one starts from six.
timer:fired- The signal a node’s Timer sends to the script on that node when its countdown runs out, heard once for each firing. Only his own Timer’s: the bell’s firing is the bell’s.
The alarm
The bell hangs in the great hall. From this chapter it counts too: four seconds, but only while a guard is chasing the thief. A guard who starts a chase tells it spotted, and one who stops tells it lost. When its four seconds of chase have run out, it swings, and calls every guard. A guard on his round comes running to search where she was last seen.
That is the last two changes to his script. In a chase he keeps where she is, under the name seen, on every step; and he hears the bell’s alarm:
if heard("alarm") && (mode == .Patrol || mode == .Return) {
mode = .Search;
last_seen = kept_point("seen", position());
start_timer();
set_number("NavAgentComponent", "speed", run);
walk_to(last_seen);
}
Only a guard on his round or on his way back answers: one who is already chasing or searching has her in hand. He searches at a run, from the point the chaser kept with keep_point, for his own Timer’s six seconds.
- Select
bell. Under Groups, typebellinto Add group and press Enter: it is the group the guards’emit_to("bell", …)reaches. - Click Add component and choose TimerComponent. Set Wait Time to
4. Tick One Shot, and tick Paused. Autostart stays ticked, as it comes. - In Assets, press Create, choose New script, type
bell, and press Enter. In the Code tab, replace everything inbell.rgswith the script below, and press Ctrl+S. - Select
bell, click Add component and choose Bell. - Press Ctrl+S.
/// The alarm bell. Its Timer starts held, and counts down only while a
/// guard is chasing the thief; when it runs out the bell rings, and every
/// guard comes to search where she was last seen.
component Bell {
/// Seconds left before it rings, as this step began.
#[readonly]
left: f32 = 0.0,
fn loop() {
left = time_left();
}
fn on_signal() {
if heard("spotted") {
set_flag("TimerComponent", "paused", false);
}
if heard("lost") {
set_flag("TimerComponent", "paused", true);
}
if heard("timer:fired") {
pulse_turn(0.3, 0.15, .InOutSine);
emit_to("guards", "alarm");
}
}
}
- Autostart and Paused, together
- The bell’s Timer starts on its first step, and starts held: its countdown is running, at four seconds, and does not move until something unticks Paused. Its script does, on
spotted, and ticks it again onlost, so it counts only the seconds of chase, all of them added up, however many chases it takes. - set_flag
- Writes true or false onto another component on this node:
set_flag("TimerComponent", "paused", false)unticks the Timer’s Paused, from the next step on. - time_left
- The seconds left on this node’s Timer, as the step began;
0when it is stopped. The bell writes it into Left on every step, so you can read the countdown in the Inspector, where the Timer’s Time Left shows it too. - Left
- The bell’s script’s one field, read-only: what
time_leftsaid this step. - pulse_turn
- Turns this node by an angle, in radians, and back, for ever, taking the seconds given each way, eased by the curve:
0.3of a radian, about 17 degrees, each way in0.15s,.InOutSine. The bell turns about its top, where its bracket holds it, so it swings. It swings from the moment it rings to the end of the game. spotted,lostandalarm- The three signals between the guards and the bell. A guard sends
spottedto the groupbellas a chase starts, andlostas it stops, whether she is lost or caught. The bell sendsalarmto the groupguardswhen it rings. Each is heard on the step after it is sent.
The bell, in Play
- Press F5, walk the thief up into the hall under the bell, and select
bell. In the Inspector, typetimerinto the search box and press Enter: Paused is ticked and Time Left holds at4.00 s. - Type
bellinto the search box instead and press Enter, so its Left shows, and walk her towards the hall guard on his round. When he chases her, Left falls; when he catches her or loses her, it holds where it got to. - Press F5 again to stop.
3.7833 seconds left. For this picture the thief was placed in the hall near his round from the start.What the chapter set
| Node | Component | Field | Value |
|---|---|---|---|
bell | node | Groups | bell |
| Timer | Wait Time | 4 | |
| One Shot | ticked | ||
| Paused | ticked | ||
| Bell | added | ||
guard_hall | Timer | Wait Time | 6 |
| One Shot | ticked | ||
| Autostart | unticked | ||
| Guard | removed and added again, its corners typed again | ||
guard_corridor | Timer | Wait Time | 6 |
| One Shot | ticked | ||
| Autostart | unticked | ||
| Guard | removed and added again, its corners typed again |
Every other field is as it comes: the bell’s Autostart ticked, the guards’ Paused unticked. The corners are chapter 8’s.