Skip to content

Tutorials · Crypt Knight · Chapter 12

The sword

60 minutes Uses: Area, AnimatedSprite, Sprite, play_clip, heard, emit_to, set_point, keep_number, kept_number, pressed

By the end

  • An attack action, on J and X
  • The knight’s sheet packed again, with six clips: idle, run, jump, fall, hurt and slash
  • An event track on the slash that is set on its swing’s two frames, and a reach, an Area, that is live only while the track is
  • A knight who slashes once he has the sword, plays his hurt clip when hurt, and is pushed back by a slash that lands
  • Creatures that notice they are struck
On this page

A slash that hurts only when it swings

The knight swings his sword sideways: J or X, once he has taken the sword at the end of the Crypt. A slash is six frames of art, a third of a second. What it reaches is not the whole of it: the blade only cuts on the two frames where it is out in front of him, the third and the fourth. Before and after, it is wound up or wound down, and touching a creature then should not count as a hit.

So the swing has two parts that the clip keeps together: its frames, which are the picture, and an event track, a row of cells, one a frame, that is set on frames 2 and 3. The reach is an Area on a node of its own, under the knight, that names the track: it is live only on the frames where the track is set. A creature’s Area looks for the reach’s layer, and a creature the reach meets is struck.

The slash needs two new strips of the knight, for it and for the hurt that has so far been only a blink, which means the sheet of chapter 7 is made again.

An attack action

  1. Choose View → Input map…. Type attack into New action and press Add.
  2. With attack selected, press Record a control and press J. Press Record a control again and press X.
  3. Press Save, and close the window.
The Input window with four actions, move, jump, down and attack, attack selected: two controls, J and X, each a Press

Pack the sheet again

An Animated Sprite plays clips of one picture, and the knight’s sheet holds idle, run and jump. The attack and the hurt are two more strips of the same collection, so the sheet is made again from all five: the old one is deleted and Make Animation… writes it again under the same name. Its id comes from its name and place, so the knight in every scene, which names it, finds the new one where the old one was.

  1. Copy the two pictures above into art, and press Refresh in Assets.
  2. In Assets, open art, right-click hero.png and choose Delete. The window lists the scenes that still use it: confirm. Do the same for hero.anim.
  3. Right-click gothic-hero-attack.png and choose Make Animation…. It opens with that picture ticked. Tick gothic-hero-hurt.png, gothic-hero-idle.png, gothic-hero-jump.png and gothic-hero-run.png too: five in all, played in name order.
  4. Set Name to hero, leave Frames a second and Loop as they are, and press Create. It writes art/hero.png, a sheet of the five strips side by side, again 2048 by 128, and art/hero.anim, one clip called hero that plays them.

The five strips sit at these places, which are the Margin to set in the Sprite sheet: the jump strip at x 0, the run strip at 307, the attack strip at 1101, the idle strip at 1679 and the hurt strip at 1833. The idle strip is not where it was in chapter 7, since it is packed between the others.

  1. Select knight, so that the Animation panel shows hero.anim, and double-click art/hero.png in Assets: the Sprite sheet tab opens on it.
  2. Idle. Set Cell to 38 and 48, Margin to 1679 and 0. Drag across the first four cells of the top row: Selected 4 x 1. Press From selection; name it idle, 8 fps, Loop; press Generate 1 and Clear.
  3. Run. Cell 66 and 48, Margin 307 and 0. Drag from cell 0, 0 to 11, 0: Selected 12 x 1. From selection; run, 14 fps, Loop; Generate 1 and Clear.
  4. Jump. Cell 61 and 77, Margin 0 and 0. Drag from cell 0, 0 to 2, 0: Selected 3 x 1. From selection; jump, 12 fps, Once; Generate 1 and Clear.
  5. Fall. Keep that grid. Drag from 3, 0 to 4, 0: Selected 2 x 1. From selection; fall, 8 fps, Once; Generate 1 and Clear.
  6. Hurt. Cell 48 and 48, Margin 1833 and 0. Drag from 0, 0 to 2, 0: Selected 3 x 1. From selection; hurt, 10 fps, Once; Generate 1 and Clear.
  7. Slash. Cell 96 and 48, Margin 1101 and 0. Drag from 0, 0 to 5, 0: Selected 6 x 1. From selection; slash, 18 fps, Once; Generate 1 and Clear.
  8. In the Animation panel, press Save. The panel lists hero (5), idle (4), run (12), jump (3), fall (2), hurt (3) and slash (6).

The attack and hurt strips are 96 and 48 wide, and the knight is drawn in them off to one side, so each frame’s pivot x, the point of it that sits on the knight’s node, is not the middle. The pivots are measured from where the soles are in each strip, so that the body stays where idle’s is. And the jump and fall frames need their pivots again, as in chapter 7, since the clips were made again.

ClipFramesPivot to set
jump0, 1 and 2y 0.987, 0.870 and 0.623
fall0 and 1y 0.545 and 0.779
hurt0, 1 and 2x 0.61 on each, and y stays at 1
slash0 to 5x 0.47 on each, and y stays at 1
  1. In the Animation panel, click jump. Under Frames, double-click the y box of the pivot, type 0.987, and press Enter. Press Next frame, and set y to 0.870; again, 0.623. Click fall: 0.545, Next frame, 0.779.
  2. Click hurt. Double-click the pivot x box, type 0.61, press Enter, press Next frame, and do it again, for all three frames.
  3. Click slash. Set pivot x to 0.47, Next frame, and so on, for all six.
  4. Press Save, and Ctrl+S.

The slash now has to say which of its frames are the cut. The Animation panel needs room for that: drag the border between the Viewport and the panels at the foot of the window up, to give the panel more height.

  1. With slash chosen, under the frame strip type slash into Track name, choose Active, and press Add track. A row called slash appears in the grid above, with a cell for each of the clip’s six frames.
  2. Drag across the third and fourth cells, frames 2 and 3, counting from 0, in the slash row: they fill with orange. Frames 2 and 3 are the swoosh and the full reach of the swing.
  3. Press Save, and Ctrl+S.
The Animation panel on the knight with the slash clip chosen, Once at 18 fps: its one track, slash, in the grid with its third and fourth cells orange, and Frame 0 of the strip with pivot x 0.47 and y 1.00
Active track
A track is a row of the clip’s frames, set on some. An Active one is a state held across its frames: while the clip is on a set frame, the track is on. A Trigger one fires once as its frame begins. Chapter 14 will use a Trigger for a sound.

The sword’s reach

The reach is an Area on a node of its own, under the knight, so that it moves with him. It is on a new layer, 2, which only a creature looks for, and it names the track slash. A node’s Area that names a track is shown by the clip only on the frames where the track is set, and a hidden node’s Area overlaps nothing: so the node is hidden, and the reach is live for those two frames and no others. A script writes only its own node, and the reach has to turn with the knight, so the reach’s own small script sets its offset, to the side the knight faces, which the knight keeps each step.

  1. In Assets, press Create, choose New script, type blade, and press Enter.
  2. In the Code tab’s Scripts list, click blade.rgs, replace everything in it with the script below, and press Ctrl+S.
rgs blade.rgs
/// The knight's sword's reach: an Area on a node of its own under the knight,
/// which the slash clip's `slash` track shows on the swing's two frames. It
/// reaches out on the side the knight faces, which the knight keeps as
/// `facing`.
component Blade {
    /// How far from the knight the middle of the reach is.
    #[suffix("u")]
    reach: f32 = 22.0,

    fn loop() {
        set_point("AreaComponent", "shape.offset", vec2(kept_number("facing", 1.0) * reach, -16.0));
    }
}
  1. Click the Viewport tab. Select knight, press + Add node, and choose Area: it lands under knight. Rename it slash and untick its Visible: the clip shows it, below. Under Groups, type slash.
  2. In its Area, set the Shape’s Size to 32 and 24, and its Offset to 22 and -16: a box in front of the knight, at the height of his sword arm.
  3. Set the Area’s own Layer to 2, untick 0 in its Mask so it reads Nothing, and set its Track to slash.
  4. Press Add component and choose Blade. Leave its Reach at 22.
  5. With slash selected, press Ctrl+C, then Ctrl+S. Open scenes/crypt.ron, right-click knight, choose Paste as child, and press Ctrl+S: the Crypt’s knight carries the same reach.
The slash node, the knight’s child, selected, its Inspector narrowed to its Area: Shape Rectangle, Size 32 by 24, Offset 22 and -16, Layer 2, Mask Nothing and Track slash, and the Hierarchy tagging it slash; in the Viewport its Area shows a square grab on each corner and a small dot at its centre for the Offset

The layers are now three: 0 for what hears the knight, 1 for what hurts him, and 2 for what his sword reaches. A creature looks for layer 2, and the reach looks for none.

The knight swings

Open knight.rgs. The slash is a clip the script starts, as it starts the others, with two differences: a slash plays to its end, however the knight moves meanwhile, and a hurt plays over it. First the comment at the top, which now says so: replace its first ten lines with these.

rgs part of knight.rgs
/// The knight: runs where `move` points, jumps on `jump`, jumps lower when
/// `jump` is let go early, drops through a ledge when `down` is held as it
/// jumps, jumps once more in the air once the relic is taken, faces the way it
/// last moved, and shows the clip for what it is doing. Once the sword is
/// taken, `attack` slashes, and a slash that lands pushes the knight back a
/// little. It keeps where it last stood on safe ground. A spike or a pit costs
/// it a heart and puts it back there; an enemy costs it a heart and hops it
/// back. A hurt shakes the camera, blinks the knight and plays its hurt clip,
/// and nothing can hurt it while it blinks. A heart it picks up heals one.
/// With no hearts left, the level is lost.

Then the fields, replacing the one hopping, which now also counts a slash’s push, and adding eight: the slash’s cooldown and length, the hurt’s length, how hard and how long a landed slash pushes the knight back, and three counters.

rgs part of knight.rgs
    /// Seconds of the hop, or of a slash's push back, still to run.
    #[hidden]
    hopping: f32 = 0.0,
    /// Seconds from one slash to the next.
    #[suffix("s")]
    cooldown: f32 = 0.4,
    /// How long the slash clip plays: six frames at 18 a second.
    #[suffix("s")]
    slash_time: f32 = 0.33,
    /// How long the hurt clip plays: three frames at 10 a second.
    #[suffix("s")]
    hurt_time: f32 = 0.3,
    /// How fast a slash that lands pushes the knight back.
    #[suffix("u/s")]
    recoil_speed: f32 = 80.0,
    /// How long that push lasts.
    #[suffix("s")]
    recoil_time: f32 = 0.1,
    /// Seconds until the knight may slash again.
    #[hidden]
    readying: f32 = 0.0,
    /// Seconds of the slash clip still to play.
    #[hidden]
    slashing: f32 = 0.0,
    /// Seconds of the hurt clip still to play.
    #[hidden]
    hurting: f32 = 0.0,

In loop, a landed slash first. What a slash strikes tells the knight, and the knight is pushed back, away from the way he faces, with his steering held:

rgs part of knight.rgs
        // A slash that landed: what it struck says "recoil" to the group
        // "knight", heard a step later. The push goes the opposite way to the
        // way the knight faces, and steering waits for it to end.
        if heard("recoil") {
            set_number("CharacterBodyComponent", "velocity.x", -facing * recoil_speed);
            hopping = recoil_time;
        }

The two comment lines of the hopping branch change, to say what else it is for:

rgs part of knight.rgs
            // Hopping back from a hurt, or pushed back by a slash: the push
            // keeps its own speed.

Then, after the line that flips the Sprite, the slash itself. The knight keeps the way he faces for the reach; counts down the cooldown; and a press of attack slashes if he has the sword, the cooldown is over and no hurt is playing:

rgs part of knight.rgs
        // The sword's reach reads which way the knight faces.
        keep_number("facing", facing);
        // The slash: once the sword is taken, attack starts one, when the last
        // has cooled and no hurt is playing.
        if readying > 0.0 {
            readying -= dt;
        }
        if pressed("attack") && kept_number("sword", 0.0) > 0.0 && readying <= 0.0 && hurting <= 0.0 {
            play_clip("slash");
            shown = "slash";
            slashing = slash_time;
            readying = cooldown;
        }

And the clip he shows. After the if chain that chooses want, two blocks: a slash, and over anything a hurt, each plays to its end, and holds want there until it has.

rgs part of knight.rgs
        // A slash, and over anything a hurt, plays to its end.
        if slashing > 0.0 {
            slashing -= dt;
            want = "slash";
        }
        if hurting > 0.0 {
            hurting -= dt;
            want = "hurt";
        }

Last, in on_enter, two lines under } else {, where a hurt costs a heart: the hurt clip plays over whatever he was doing.

rgs part of knight.rgs
                // The hurt clip plays over whatever the knight was doing.
                hurting = hurt_time;

Press Ctrl+S.

The Code panel open on knight.rgs at the slash: the check on pressed("attack") and the sword, the play_clip of slash, the counters, and the cooldown, with the caret on the line that sets slashing and Up to date in the status line
play_clip "slash" and shown
Starts the slash from its first frame. Setting shown to "slash" tells the code below that the clip on show is already the slash, so it does not start it again. slashing is how long it plays, 0.33 seconds, and the swing is the same length wherever he is: running, standing, in the air.
pressed("attack") and kept_number("sword", 0.0)
A press of J or X, and the sword: the relic of chapter 11 keeps a 1 under sword, and until it does, a press does nothing. readying is the cooldown, 0.4 seconds, and hurting is whether the hurt clip is playing: a hurt knight cannot swing.
keep_number("facing", facing)
The reach, a different node, cannot read the knight’s facing, a field of his script. So the knight keeps it, as a kept value, every step, and Blade reads it back.
heard("recoil")
What a slash strikes says "recoil" to the group knight, as a heart says "heal", and the knight hears it a step later, and is pushed back at recoil_speed for recoil_time: 80 units a second for a tenth of a second, which is 8 units.

Creatures that are struck

The hounds, the skull and the wolf do not die. The knight evades them in the Crypt and does not fight them: only the beast of chapter 13 has health. A creature that is struck tells the knight, so that the knight is pushed back, and chapter 14 makes it flash. Its script is one short hook.

  1. In Assets, press Create, choose New script, type struck, and press Enter.
  2. In the Code tab’s Scripts list, click struck.rgs, replace everything in it with the script below, and press Ctrl+S.
rgs struck.rgs
/// A creature the knight's sword can strike: a slash that lands on it pushes
/// the knight back.
component Struck : Area {
    fn on_enter(other: NodeId) {
        if in_group(other, "slash") {
            // The knight is in the group "knight", and is pushed back a step
            // later.
            emit_to("knight", "recoil");
        }
    }
}
  1. Click the Viewport tab, and in scenes/crypt.ron select hound_1.
  2. In its Area, click the Mask row, and tick 2, so that it reads 2: it now looks for the reach.
  3. Press Add component and choose Struck.
  4. Do the same for hound_2, skull and wolf, and press Ctrl+S.
The first hound framed and selected, its Area the yellow box inside the green box of its body; its Inspector narrowed to its Area: Shape Rectangle, Size 28 by 18, Offset 0 and -9, Layer 1 and Mask 2

A creature’s Area is on layer 1, where the knight’s looks for it, and looks for layer 2, where the reach is. Both are Areas that notice Areas, and the reach is live for two frames of each swing.

Check it works

The knight in his hurt clip, rocking back with his cape thrown forward as he is thrown over a hound that has touched him, with four hearts at the top left

The sword is at the end of the Crypt. To skip the walk, set the knight’s Position in crypt.ron to 1460 and 160, past the second pillar, press Ctrl+S and F5, run right to the altar and take the sword, and set it back to 72 and 160 when you have seen it.