Skip to content

Tutorials · Navigation and AI · Chapter 7

Eyes

By the end

  • guard.rgs with sight: as far as 112, 0.6 radians to either side of the way he faces, and no wall on layer 1 between them; seen, he goes to where she stood
  • Sees, a field you can watch in Play, ticked while she is in his cone
  • cone, a picture of his cone under him, turned by cone.rgs to point the way he walks
On this page

What he sees

The corridor guard hears a noise, and is blind. His script now asks three questions on every step: where is the thief, is she ahead of him and near enough, and is there a wall between them. When all three answers are yes, he has seen her, and he goes to where she stood, the way he goes to a noise.

  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
/// 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 sees ahead of him, in
/// a cone, as far as his sight and no further than a wall: when the thief is
/// in it, he goes to where he saw her. Either way, he looks about a moment
/// and goes back to his round.
enum Errand { Round, Noise, Look }

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,
    /// 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,
    /// What he is about.
    #[readonly]
    errand: Errand = .Round,
    /// 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,
    /// Whether he is walking to the far end.
    #[hidden]
    outward: bool = true,

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

    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();
        sees = thief != node(0)
            && toward.length() < sight
            && angle_between(facing, toward) < cone
            && can_see(at, walls);
        if sees && errand != .Look {
            errand = .Look;
            walk_to(at);
        }
    }

    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 == .Round {
                outward = !outward;
            } else {
                if arrived() {
                    await wait(2.0);
                }
                errand = .Round;
            }
            if outward {
                walk_to(far);
            } else {
                walk_to(post);
            }
        }
    }
}

Select guard_corridor. His Guard section has gained four rows, each at the value the script gives it: Sight 112.00 u, Cone 0.60 and Walls 1, and Sees, greyed. Leave them so: there is nothing to type.

guard_corridor selected, its Inspector narrowed with the search box, guard, to its Guard: Post 40.0 and 232.0, Far 600.0 and 232.0, Hearing 320.00 u, Sight 112.00 u, Cone 0.60, Walls 1, and greyed, Errand Round and Sees unticked; the Viewport shows the whole keep, the guard at the corridor’s west end with a pale-yellow see-through wedge reaching east from him, and in the Hierarchy a cone row under his
The corridor guard’s Guard, after the script gained its sight. The wedge and the cone row are the next section’s.
Sight
The Guard’s Sight: how far he sees, in world units, straight from his feet to hers. 112 is seven tiles, about a third of the view’s width: he sees her before she reaches him, and not across a room.
Cone
The Guard’s Cone: how far to either side of straight ahead he sees, in radians, not degrees. 0.6 is about 34° each way, so the whole cone is about 69° wide. Over 1.57, a quarter turn, he would see beside him.
Walls
The Guard’s Walls: the collision layers that hide what is behind them, as a mask. 1 is the walls’ layer, the one the keep’s walls are on.
Sees
The Guard’s Sees: whether the thief was in his cone as this step began. #[readonly], so it is greyed and you cannot tick it; in Play you watch it tick and untick.
.Look
A third errand: going to where he saw her. While it lasts, seeing her again does not send him again, so he walks to where she stood, not after her; a chase is chapter 8’s.
facing, was
Two hidden fields. was is where he stood the step before; when he has moved more than 0.1 since, facing becomes the way he moved. So he faces the way he last walked, and a guard standing still keeps facing where he was going. He starts facing down, vec2(0.0, 1.0).
nearest
The nearest shown node in a group, other than this one, as the step began, by straight distance: nearest("thief") is her. When there is none, it is nobody, node(0), which is what thief != node(0) asks about.
position_of
Where that node stood in the world as the step began: her feet.
.length()
How long a Vec2 is. toward runs from his feet to hers, so its length is how far apart they are; moved.length() is how far he walked in a step.
angle_between
The angle between two directions, in radians: 0 when they point the same way, half a turn, about 3.14, when they are opposite. Under cone, she is ahead of him.
can_see
Whether no wall on the layers in its mask stood between this node and a point as the step began. A wall is a solid collider on a shown node that is not a walker, or a solid tile layer, on those layers. Areas never hide anything, and neither does another walker.
&&
And: sees is true only when every part is: she exists, she is nearer than sight, she is inside cone, and no wall on walls is between them.
fn loop()
Runs on every step. He looks on every step, not only when a signal comes.

A cone you can see

Nothing in the game draws what a guard sees: his sight is three numbers and a line. A cone you can see is a picture under him that points his way. cone.png came with the starter, in art/: a wedge 112 long, his sight, and 154 across at its end, so its sides are his cone’s.

  1. Select guard_corridor, press + Add node, choose Sprite, and rename it cone. It lands under him.
  2. Set its Position to 0 and -8, clicking each box once, typing and pressing Enter.
  3. Drag art/cone.png from Assets onto the Sprite’s Texture, set Size to 112 and 154, the picture’s own size, Anchor to 0 and 0.5, and Sort Layer to 1.
  4. In Assets, press Create, choose New script, type cone, and press Enter. In the Code tab, replace everything in cone.rgs with the script below, and press Ctrl+S.
rgs cone.rgs
/// The guard's cone of sight, drawn: it turns to point the way he walks.
component Cone {
    /// Where it stood the step before.
    #[hidden]
    was: Vec2,

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

    fn loop() {
        let moved = position() - was;
        was = position();
        if moved.length() > 0.1 {
            anneal_rotation(angle_of(moved), 0.15, .OutQuad);
        }
    }
}
  1. Select cone, click Add component and choose Cone. Its one field, was, is hidden.
  2. Press Ctrl+S.
cone selected under guard_corridor in the Hierarchy, its box drawn round a pale-yellow see-through wedge reaching east from the corridor guard over the corridor’s walls; the Inspector narrowed with the search box, spritecomponent, to its Sprite: Texture cone.png, Size 112.0 and 154.0, Anchor 0.0 and 0.5, Color white, Sort Layer Custom 1, Blend Alpha
The cone, under the corridor guard. In the editor it points east, unturned; in Play its script turns it.
Sprite Texture
The picture the Sprite draws: cone.png.
Sprite Size
How large it is drawn, in world units: 112 by 154, the picture’s own pixels, one unit each.
Sprite Anchor
Which point of the picture sits on the node, as a fraction of its width and its height. 0, 0.5 is the middle of its left edge: the wedge’s tip. So the tip sits on the node, and the picture turns about it. A new Sprite’s is 0.5, 0.5, its middle.
Sprite Sort Layer
Which pictures draw over which. 1 is the layer the tables and the pots are on: the wedge lies on the floor, under the guards and the thief.
angle_of
The angle a direction points at, in radians, as a rotation: 0 to the right, a quarter turn, about 1.57, straight down. The way the cone moved is the way he walked.
anneal_rotation
Turns this node to an angle, the short way round, over a time: 0.15 seconds here, with the curve .OutQuad, fast at first and slowing as it arrives. So the cone swings round with him rather than snapping.

What hides her

  1. Press F5. Walk the thief out of the courtyard and west along the corridor’s carpet, towards the corridor guard as he walks east. When he is near enough, he sees her, and walks to where she stood.
The game: the corridor, the corridor guard at the left on the carpet and the thief a little ahead of him to the east, a pale-yellow see-through wedge opening east from his head across her and over the walls above and below; the ladder at the right, the cellar’s grate below
After 60 steps, 1 s: his cone points east, on her. For this picture the thief was placed on the carpet ahead of him from the start.

Behind him, or further off than 112, she is not seen, though she stands on the same carpet. Now put a wall between them.

  1. Walk the thief up into the hall and stand her against its bottom wall, just above the corridor, west of the ladder. Select guard_corridor and watch Sees as he walks east below her. Press F5 again to stop.
Play, looking through the game camera: the corridor guard on the carpet, selected, the thief in the hall just above the corridor’s north wall, and the pale wedge of his cone reaching up across the wall to her; the hall guard further up and the tables at the top; the Inspector shows his Guard, Sight 112.00 u, Cone 0.60, Walls 1, Errand Round and Sees unticked
After 110 steps, about 1.8 s, of Play: she is near enough and ahead of him, and his cone’s picture is on her, but the wall is between them. Sees is unticked, and he walks on. For this picture the thief was placed in the hall from the start.

What the chapter set

NodeComponentFieldValue
guard_corridorGuardSight112, as the script gives it
Cone0.6, as the script gives it
Walls1, as the script gives it
cone, under guard_corridornodePosition0, -8
SpriteTexturecone.png
Size112, 154
Anchor0, 0.5
Sort Layer1
Coneadded

Every other field is as it comes. The guard’s Post, Far and Hearing are chapter 6’s.

Check it works