Skip to content

Tutorials · Ship Evader · Chapter 6

Meteors

45 minutes Uses: Area, Spawner, Sprite, random, anneal_turn, anneal_colour, vec4, spawn, destroy_self

By the end

  • A spark that fades and removes itself, saved as an artifact
  • Three rock artifacts sharing one script, each with numbers of its own
  • The game’s five physics layers, and Areas that notice what they should
  • A laser that stops on a rock and leaves a spark there
  • A sky that drops a rock every 1.6 s, anywhere along the top
On this page

A spark, where a laser stops

When a laser meets a rock, it stops there and leaves a small spark that fades. The spark is an artifact, like the laser, with a script that fades it out and then removes it. Make a script called spark:

  1. In Assets, press Create, choose New script, type spark, and press Enter. The editor writes spark.rgs into the project folder.
  2. Click the Code tab, then spark.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs spark.rgs
/// A spark, where a laser stopped: it fades out, then removes itself.
component Spark {
    /// How long it takes to fade out.
    #[suffix("s")]
    fade_time: f32 = 0.2,

    fn on_create() {
        // White, and all the way see-through: an alpha of 0.
        let faded = vec4(1.0, 1.0, 1.0, 0.0);
        // `await` waits here until the fade is done, then goes on.
        await anneal_colour("SpriteComponent", "color", faded, fade_time, .Linear);
        destroy_self();
    }
}
fn on_create()
A hook that runs once, when the node arrives: here, as the laser leaves the spark.
anneal_colour
Moves a colour field to a value over a time, along a curve, .Linear here. The field is named as text, its component and its name: the Sprite’s Color, as "SpriteComponent" and "color". It moves to white with an alpha of 0, so the spark fades out.
await
Waits on that line until the fade is done, and then the hook goes on to remove the spark. Waiting, in place has more.
  1. Click the Viewport tab, select ship, and drag art/laserBlue08.png into the Viewport.
  2. Double-click its row in the Hierarchy, type spark, and press Enter.
  3. Set its Position to 0 and 0.
  4. Leave its Sprite at the picture’s own 48 by 46, and set Sort Layer to 1, so a spark draws over the rock it is on.
  5. Press Add component and choose Spark. Its Fade Time is 0.2.
  6. Right-click spark, choose Save as artifact…, save it as artifacts/spark.artifact, and delete the copy left in the scene.
The spark artifact open in the editor: one node, spark, at position 0 and 0, a white star with a blue rim, magnified across the Viewport

Three sizes of rock

A rock falls at a speed of its own, drifts a little to one side, and spins. One script does it for all three sizes, and each size is an artifact of its own, with its own numbers in the script’s fields. Make a script called meteor:

  1. In Assets, press Create, choose New script, type meteor, and press Enter. The editor writes meteor.rgs into the project folder.
  2. Click the Code tab, then meteor.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs meteor.rgs
/// A meteor: it falls at a speed of its own, drifting a little to one side
/// and spinning, and removes itself once it is 100 units past the bottom.
component Meteor {
    /// The slowest it may fall.
    #[suffix("u/s")]
    #[range(1..)]
    slowest_fall: f32 = 90.0,
    /// The fastest it may fall.
    #[suffix("u/s")]
    #[range(1..)]
    fastest_fall: f32 = 150.0,
    /// The fastest it may drift, to either side.
    #[suffix("u/s")]
    drift: f32 = 30.0,
    /// The slowest it may spin, either way.
    #[suffix("rad/s")]
    slowest_spin: f32 = 0.4,
    /// The fastest it may spin, either way.
    #[suffix("rad/s")]
    fastest_spin: f32 = 2.0,
    /// How fast it moves, sideways and down, rolled when it is made.
    #[hidden]
    velocity: Vec2 = vec2(0.0, 0.0),

    fn on_create() {
        // Down is toward positive y: the world's y grows downward.
        velocity = vec2(random(-drift, drift), random(slowest_fall, fastest_fall));

        // How long it will take to fall to 100 units past the bottom edge
        // (the bottom edge is camera_view().w). `#[range(1..)]` on the two
        // fall speeds means this never divides by zero.
        let lifetime = (camera_view().w + 100.0 - position().y) / velocity.y;
        let spin = random(slowest_spin, fastest_spin);
        // Half of the meteors spin the other way.
        if random(0.0, 1.0) < 0.5 {
            spin = -spin;
        }
        // No builtin turns a node a little each step, so the whole spin is
        // one long turn that lasts as long as the fall.
        anneal_turn(spin * lifetime, lifetime, .Linear);
    }

    fn loop(dt: f32) {
        translate(velocity * dt);
        if position().y > camera_view().w + 100.0 {
            destroy_self();
        }
    }
}
random
A number between two others, different for each rock and on each Play. A rock rolls its fall, its drift and its spin as it arrives.
#[range(1..)]
Keeps the two fall speeds at 1 or more in the Inspector. The fall’s length is divided by the speed, so a speed of 0 would divide by zero.
anneal_turn
Turns the node by an angle over a time. Nothing turns a node a little on every step, so a rock’s whole spin is one long turn that lasts as long as its fall.
velocity: Vec2
A field holding a vector, hidden like the ship’s cooldown: the rock’s drift and fall, rolled once and then added a step at a time.

Layers: what notices what

An Area is what notices another: a laser noticing a rock. Each Area is on a physics Layer, and its Mask says which layers it notices. The game uses five layers. Name them all once, now, on the big rock, though three are first used in the next chapter.

  1. Click the Viewport tab, select ship, and drag art/meteorBrown_big1.png into the Viewport.
  2. Double-click its row in the Hierarchy, type meteor_big, and press Enter.
  3. Set its Position to 0 and 0, and leave its Sprite at the picture’s 101 by 84 on Sort Layer 0.
  4. Press Add component and choose AreaComponent.
  5. In its Shape fold, set Kind to Circle and Size to 101 and 84, and leave the Layer inside the fold alone: the Area’s own Layer and Mask are the rows below the fold.
  6. Click the Area’s Layer, open Name layers… at the foot of the list, and name the first five layers ship, shot, enemy, enemy shot and meteor. The names belong to the project, and are written into project.rengy as you leave each field.
  7. Set the Area’s Layer to meteor alone, unticking ship, which every new Area starts on.
  8. Set its Mask to nothing, unticking ship there too: a rock notices nothing.
  9. Press Add component and choose Meteor. Its defaults are the big rock’s: it falls at 90 to 150, drifts up to 30, and spins at 0.4 to 2 radians a second.
  10. Right-click meteor_big, choose Save as artifact…, save it as artifacts/meteor_big.artifact, and delete the copy left in the scene.
  11. Select ship and drag art/meteorBrown_med1.png into the Viewport.
  12. Double-click its row in the Hierarchy, type meteor_medium, and press Enter.
  13. Make it the same way as meteor_big: an Area Circle 43 by 43 on Layer meteor with no Mask, and a Meteor whose Slowest Fall is 130 and Fastest Fall 200.
  14. Save it as artifacts/meteor_medium.artifact, and delete the copy.
  15. Select ship and drag art/meteorBrown_small1.png into the Viewport.
  16. Double-click its row in the Hierarchy, type meteor_small, and press Enter.
  17. Make it the same way: an Area Circle 28 by 28, Layer meteor, no Mask, Slowest Fall 170 and Fastest Fall 260.
  18. Save it as artifacts/meteor_small.artifact, and delete the copy.
The big rock’s artifact open on its one node, meteor_big, framed in the Viewport, its Inspector narrowed to the Area’s layers: Layer meteor, and a Mask with nothing ticked
The medium rock’s artifact, its Meteor narrowed to the fall: Slowest Fall 130 and Fastest Fall 200
The small rock’s artifact, its Meteor narrowed to the fall: Slowest Fall 170 and Fastest Fall 260

Lasers notice rocks

A laser needs an Area too, noticing the rocks’ layer, and it leaves a spark where it stops, so it gets a Spawner of its own. An artifact can carry a Spawner, as the ship does.

  1. Press Ctrl+S to save the scene: opening an artifact closes the open scene, asking Save the open scene? when it has edits not on disk.
  2. Double-click artifacts/laser.artifact in Assets to open it.
  3. Select laser, press Add component, and choose AreaComponent.
  4. In its Shape fold, leave Kind at Rectangle and set Size to 9 and 54, the laser’s own.
  5. Set the Area’s Layer to shot alone and its Mask to meteor alone.
  6. Press Add component, choose SpawnerComponent, and add an entry: Name spark, Artifact artifacts/spark.artifact.
  7. Click the Code tab, then laser.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
  8. Click the Viewport tab and press Ctrl+S to save the artifact.
rgs laser.rgs
/// A laser: it flies up the field and removes itself 100 units past the top,
/// or where it meets something, leaving a spark there.
component Laser : Area {
    /// How fast the laser flies.
    #[suffix("u/s")]
    speed: f32 = 900.0,

    fn loop(dt: f32) {
        // Up is toward negative y: the world's y grows downward.
        translate(vec2(0.0, -speed * dt));
        // camera_view().y is the top edge of the field.
        if position().y < camera_view().y - 100.0 {
            destroy_self();
        }
    }

    /// When the laser's Area meets another that it notices: for now, only a
    /// meteor's.
    fn on_enter() {
        spawn("spark", position());
        destroy_self();
    }
}

component Laser : Area says the component needs an Area on its node, and gives it the Area’s hook, on_enter, which runs when the laser’s Area meets one it notices: for now, only a rock’s. A script with : Area on a node that has none is listed in Problems, which is why the Area went on first. The hook spawns a spark where the laser is, and removes the laser. It takes no argument yet, since it does not need to know what it met; chapter 7 gives it one.

The laser artifact open, its Inspector narrowed to its Area’s layers: Layer shot, and a Mask with meteor ticked

The sky drops the rocks

Something has to drop the rocks: a node of its own, sky, with a Spawner naming the three and a script counting down to the next. Make a script called sky:

  1. In Assets, press Create, choose New script, type sky, and press Enter. The editor writes sky.rgs into the project folder.
  2. Click the Code tab, then sky.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs sky.rgs
/// The sky: it drops a meteor at once and then one every 1.6 s, each of a
/// random size, somewhere along the top of the field.
component Sky {
    /// Seconds between two meteors.
    #[suffix("s")]
    #[range(0.1..)]
    meteor_interval: f32 = 1.6,
    /// Seconds until the next meteor.
    #[hidden]
    meteor_countdown: f32 = 0.0,

    fn loop(dt: f32) {
        meteor_countdown -= dt;
        if meteor_countdown <= 0.0 {
            // Anywhere from the left edge (view.x) to the right one (view.z),
            // 50 units above the top (view.y): up is toward negative y.
            let view = camera_view();
            let at = vec2(random(view.x, view.z), view.y - 50.0);
            // Each of the three sizes as likely as the others.
            let size = random(0.0, 3.0);
            if size < 1.0 {
                spawn("meteor_big", at);
            } else if size < 2.0 {
                spawn("meteor_medium", at);
            } else {
                spawn("meteor_small", at);
            }
            // `#[range(0.1..)]` on the interval means the sky never drops
            // something on every step.
            meteor_countdown = meteor_interval;
        }
    }
}

Each time its countdown runs out, the sky picks a point along the top, 50 units above the edge and out of sight, and rolls a number from 0 to 3 to choose a size, each as likely as the others. #[range(0.1..)] keeps the gap at a tenth of a second or more.

  1. Double-click scenes/game.ron in Assets to go back to the game, and click the Viewport tab.
  2. Select Game, press + Add node and choose Node, and rename it sky.
  3. Press Add component, choose SpawnerComponent, and add three entries: meteor_big, meteor_medium and meteor_small, each with its artifact.
  4. Press Add component and choose Sky. Its Meteor Interval is 1.6.
  5. Press Ctrl+S.
The sky selected: its Spawner holds three entries, and its Sky component a Meteor Interval of 1.60 s
The game: rocks of three sizes tumbling down the purple field above the ship

Check it works