Skip to content

Tutorials · Ship Evader · Chapter 11

Effects

75 minutes Uses: AnimatedSprite, Sprite, Parallax, PostVolume, ParticleEmitter, AudioSource, Spawner, anneal_scale, anneal_colour, spawn

By the end

  • A flame under the ship, an animation packed from two of the pack’s pictures
  • A starfield of three planes that drift down and repeat as a whole
  • An opaque background, and why it costs less to draw
  • Bloom, and what makes something glow
  • Explosions and a spark: a picture that grows and fades, a burst of particles and a sound, each removing itself
On this page

A flame under the ship

The thruster is an animation: two of the pack’s fire pictures, played one after the other, 12 a second. Make Animation… packs pictures into one sheet and writes the animation that plays them.

  1. In Assets, open art, right-click fire04.png, and choose Make Animation…. The window opens with fire04.png ticked, Name fire, Frames a second 12 and Loop ticked.
  2. Tick fire05.png too, and press Create. It writes art/fire.png, a sheet of the two pictures, and art/fire.anim, a clip called fire that plays them, looping.
The Make Animation window over the editor, listing the pictures in art with “fire04.png” and “fire05.png” ticked, Name “fire”, Frames a second 12, and Loop ticked

Make Animation… makes and never updates: pressed again on the same name, it refuses. To change how fast the flame flickers, change the Animated Sprite’s Speed below. To change its pictures, delete both files and make them again, or make them under a new name and point the three slots below at it.

  1. Select ship, press + Add node, and choose Animated Sprite: it lands as a child of the ship.
  2. Rename it thruster, and set its Position to 0 and 51, under the ship’s tail.
  3. In its Sprite, drag art/fire.png onto Texture, set Size to 14 and 31, Sort Layer to -1, under the ship, and Blend to Add.
  4. In its Animated Sprite, drag art/fire.anim onto Animations, set Clip to fire in place of idle, and keep Speed 1 and Playing ticked.
The thruster selected: its Sprite’s Texture is “fire.png”, its Animated Sprite’s Animations “fire.anim” and its Clip “fire”, and the Viewport, through the game camera, showing its selection ring under the ship

The three slots must agree. The Sprite’s Texture is the picture the frames are cut from, so another picture there shows the wrong frames; the Clip names the clip to play, so another name draws the whole sheet at once. The Sprite’s Anchor places the flame. The flame is a node of its own, so it burns on, untinted, while the ship blinks.

Play, with the Viewport close on the ship at 200%: a pale blue flame burning under its tail

An opaque background

  1. Select background, and tick its Sprite’s Opaque.
The background’s Sprite narrowed to Opaque, ticked

Nothing changes on screen: darkPurple.png has no see-through pixel. But a Sprite drawn opaque does not blend with what is behind it, and the background is behind everything, so every frame costs a little less to draw. Tick it on any back layer that covers the screen.

A starfield

Three planes of stars drift down at three speeds, the far ones faintest and slowest, which reads as depth. A Parallax moves the sprites under it: here not with the camera, which never moves, but by an Autoscroll of its own, repeating the whole plane so it never runs out. Cameras has more on Parallax.

  1. Select Game, press + Add node, choose Node, and rename it far_stars. The Parallax preset would add a Sprite the plane does not need.
  2. Press Add component and choose ParallaxComponent.
  3. Set Factor to 0 and 0, Offset to 0 and 0, and Autoscroll to 0 and 40.
  4. Click its Color.
  5. If the button at the top left of the picker that opens reads U8, press it, so it reads F: the picker then shows each value from 0 to 1, as this tutorial gives every colour, until you close the editor.
  6. Make the colour white with an alpha of 0.35.
  7. Leave Repeat X ticked, tick Repeat Y, and set Repeat Period to 720 and 1280, the field’s size.
  8. With far_stars selected, press + Add node and choose Sprite, so the first star is its child.
  9. Rename it star, drag art/star1.png onto its Texture, set Size to 25 and 24, Sort Layer to -9, and Position to -300 and -560.
  10. Select that star and drag seven more pictures into the Viewport, each landing beside it under far_stars: star2.png, star3.png, star1.png, star2.png, star3.png, star1.png and star2.png, in that order.
  11. For each of the seven, double-click its row in the Hierarchy, type star, and press Enter.
  12. Set each one’s Sort Layer to -9, and place them: star2.png at -90, -420; star3.png at 180, -500; star1.png at 310, -200; star2.png at -230, -60; star3.png at 60, 90; star1.png at -330, 330; and star2.png at 240, 470.
  13. Right-click far_stars and choose Duplicate.
  14. Rename the copy middle_stars, and set its Offset to 180 and 640, its Autoscroll to 0 and 90, and its Color’s alpha to 0.6.
  15. Duplicate middle_stars as near_stars: Offset -180 and 320, Autoscroll 0 and 160, alpha 1.
The far_stars plane selected: its Parallax with Factor 0, Autoscroll 0 and 40, Color a see-through white, Repeat X and Repeat Y ticked and Repeat Period 720 by 1280, and the Hierarchy listing its eight stars
The near_stars plane selected: Offset -180 and 320, Autoscroll 0 and 160, and Color at full alpha
Autoscroll
How far the plane drifts on its own, in units a second, across and down: 40, 90 and 160 down for the three planes.
Repeat Period
With Repeat X or Y ticked, how far apart the whole plane’s copies sit. One field apart, each plane’s eight stars show in any field-sized view, and the stars repeat as one sheet rather than one by one.
Offset
Shifts a plane, so the three copies of the same eight stars do not line up.

Bloom

Bloom makes the brightest things glow. A Post Volume grades how the whole scene looks, its bloom included, in Play and in an exported game.

  1. Select Game, press + Add node, choose Node, and rename it bloom.
  2. Press Add component and choose PostVolumeComponent.
  3. Keep Enabled ticked, and set Area to Global, so it covers the whole scene.
  4. In its Profile, leave everything as it comes but the bloom’s rows, which come after Vignette: keep their Enabled ticked, set Threshold to 0.8 and Intensity to 1.0, and press Enter.
  5. Press Ctrl+S to save the scene.
  6. Double-click artifacts/laser.artifact, select laser, set its Sprite’s Blend to Add, and save it.
The bloom node selected, its Post Volume narrowed to its bloom: Enabled ticked, Threshold 0.80 and Intensity 1.00

Brightness decides what glows, not the blend: whatever is brighter than the threshold keeps a halo. So the lasers glow, the enemies’ red shots glow most, and so do the near stars, a young burst of sparks and a hit’s white flash; the fireball, the flame and the far stars stay under it. Add blends a laser by adding its light to what is behind it, so where lasers cross they brighten. The rows above the bloom’s are its colour grade, and the corners come out a little darker: that is its vignette.

Explosions

An enemy shot down or rammed leaves an explosion: a fireball that grows and fades, a burst of sparks, and a crunch. The sparks and the crunch are components that start on their own as the explosion arrives; a script grows and fades the fireball, then removes the whole. Make a script called explosion:

  1. In Assets, press Create, choose New script, type explosion, and press Enter. The editor writes explosion.rgs into the project folder.
  2. Click the Code tab, then explosion.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs explosion.rgs
/// An explosion, where something was destroyed: a fireball that grows and
/// fades, a burst of sparks and a bang. The sparks and the bang are its
/// Particle Emitter's and its Audio Source's own, and each starts as the
/// explosion arrives, with nothing here to call. The fireball is one picture:
/// it grows from the scale the explosion is made at to `end_scale`, fading out
/// as it grows, and then the explosion removes itself.
component Explosion {
    /// How big the fireball grows, as a scale of its Sprite's size.
    #[range(0.0..)]
    end_scale: f32 = 1.0,
    /// How long the fireball takes to grow and fade.
    #[suffix("s")]
    #[range(0.05..)]
    lifetime: f32 = 0.5,

    fn on_create() {
        // From the scale the explosion is made at: the big explosion is made
        // at twice the scale, and grows twice as big.
        anneal_scale(vec2(end_scale, end_scale), lifetime, .OutQuad);
        // White, and all the way see-through: it fades as it grows. `await`
        // waits here until the fade is done. A burst goes with its node, so
        // each explosion's sparks are made to die before its fade ends.
        let faded = vec4(1.0, 1.0, 1.0, 0.0);
        await anneal_colour("SpriteComponent", "color", faded, lifetime, .InQuad);
        destroy_self();
    }
}

anneal_scale grows the node from the scale it was made at, along .OutQuad, quick then slow, while the colour fades along .InQuad, slow then quick.

  1. Double-click scenes/game.ron in Assets to go back to the game, and click the Viewport tab.
  2. Select ship, and drag art/explosion00.png into the Viewport.
  3. Double-click its row in the Hierarchy, type explosion, and press Enter.
  4. Set its Position to 0 and 0 and its Scale to 0.5 and 0.5: the explosion is made at half size and grows to its full size.
  5. In its Sprite, set Size to 128 and 118, in place of the picture’s 583 by 536, Sort Layer to 3 and Blend to Add.
  6. Add a ParticleEmitterComponent: tick One Shot; Burst 24, Capacity 24; Lifetime 0.45; Speed 260; Direction 0 and -1; Spread 6.3, a whole circle; Gravity 0; Start Color 1, 0.85, 0.45, alpha 1; End Color 1, 0.35, 0.1, alpha 0; Start Size 10, End Size 2; Sort Layer 4; Blend Add.
  7. Add an AudioSourceComponent: Clip audio/explosionCrunch_000.ogg, Bus Sfx, Volume 0.7, Autoplay ticked, Looping unticked, and, in its Spatial fold, Positional unticked. The next chapter says why.
  8. Add Explosion, keeping End Scale 1 and Lifetime 0.5.
  9. Save it as artifacts/explosion.artifact, and delete the copy left in the scene.
  10. Make big_explosion the same way, for the ship’s last life: Scale 1 and 1; a burst of Burst 48, Capacity 48, Lifetime 0.7, Speed 360, Start Size 14, End Size 3; Clip audio/lowFrequency_explosion_000.ogg at Volume 0.9; and an Explosion of End Scale 2 and Lifetime 0.75.
  11. Save it as artifacts/big_explosion.artifact, and delete the copy.
The explosion’s artifact open, framed on its fireball: its node at Scale 0.5, its Sprite 128 by 118 with Blend Add
The explosion’s artifact narrowed to Positional: the Audio Source’s Positional unticked
The big explosion’s artifact narrowed to its scales: its node at Scale 1, and its Explosion’s End Scale 2

A one-shot burst goes with its node, so each burst’s Lifetime is a little shorter than its explosion’s fade: 0.45 s inside 0.5 s, and 0.7 s inside 0.75 s. Problems lists a one-shot emitter whose Burst is more than its Capacity.

The spark’s burst

  1. Press Ctrl+S to save the scene.
  2. Double-click artifacts/spark.artifact, select spark, and set its Sprite’s Blend to Add.
  3. Add a ParticleEmitterComponent: One Shot ticked; Burst 8, Capacity 8; Lifetime 0.18; Speed 180; Direction 0 and 1, down the field; Spread 1.6; Gravity 0; Start Color 0.75, 0.9, 1, alpha 1; End Color 0.3, 0.6, 1, alpha 0; Start Size 6, End Size 1; Sort Layer 2; Blend Add.
  4. Add an AudioSourceComponent: Clip audio/impactMetal_000.ogg, Volume 0.4, Autoplay ticked, Positional unticked.
  5. Save the artifact.
  6. In the Code tab’s Scripts list, click spark.rgs, make the comment at the top of the file the one below, and press Ctrl+S. Only the comment changes, so the game plays the same without it.
rgs part of spark.rgs
/// A spark, where a laser stopped: a little burst and a small sound as it
/// arrives, which its Particle Emitter and its Audio Source start on their own,
/// then it fades out and removes itself.
component Spark {
The spark’s artifact narrowed to its burst: One Shot ticked and Burst 8

Who explodes

  1. Click the Viewport tab, then open artifacts/diver.artifact, select diver, add a SpawnerComponent with one entry, explosion with artifacts/explosion.artifact, and save it.
  2. Open artifacts/gunner.artifact, select gunner, add the same entry to its Spawner, after enemy_shot, and save it.
  3. Double-click scenes/game.ron to go back, select ship, and add an entry to its Spawner, after laser: big_explosion with artifacts/big_explosion.artifact.
  4. In the Code tab’s Scripts list, click enemy.rgs, replace everything in it with the script below, and press Ctrl+S: an enemy explodes where it was, shot down or rammed.
rgs enemy.rgs
/// An enemy's hits: each laser that meets it costs it a hit point, the last one
/// blows it up and adds its points to the score, a hit it lives through
/// flashes it white, and meeting the ship blows it up too, for no points.
component Enemy : Area {
    /// How many lasers it takes to bring it down.
    hit_points: i32 = 1,
    /// How long the white flash of a hit takes to fade.
    #[suffix("s")]
    flash_time: f32 = 0.12,
    /// What shooting it down adds to the score.
    points: f32 = 100.0,

    /// When its Area meets another that it notices, on the node `other`: a
    /// laser's or the ship's.
    fn on_enter(other: NodeId) {
        // The groups `other` is in say which it is.
        if in_group(other, "shot") {
            hit_points -= 1;
            if hit_points <= 0 {
                // Shot down: its points go on the score, and it leaves an
                // explosion where it was. The game keeps the score as it keeps
                // the lives, so the HUD can show it.
                add_number("score", points);
                spawn("explosion", position());
                destroy_self();
            } else {
                // A hit it lives through washes it white at once, and the
                // white fades back to its own colours over flash_time.
                set_number("SpriteComponent", "flash", 1.0);
                anneal_number("SpriteComponent", "flash", 0.0, flash_time, .Linear);
            }
        } else if in_group(other, "ship") {
            // Rammed: it explodes and goes, and scores nothing. The ship
            // loses a life.
            spawn("explosion", position());
            destroy_self();
        }
    }
}

The last life leaves the big explosion where the ship was. Click ship.rgs in the Scripts list, and change the comment at the top and the else branch at the end of on_enter:

rgs part of ship.rgs
/// The player's ship: it flies where `move` points, never leaves the field,
/// fires lasers while `fire` is held, and keeps its position for whatever aims
/// at it. Whatever its Area meets costs it a life: it flashes white, then
/// blinks, and nothing can hit it while it does. On its last life it blows up.
/// Each whole second it stays alive adds 10 to the score.
component Ship : Area {
rgs part of ship.rgs
        } else {
            // The last life: a bigger shake, a big explosion where the ship
            // was, and the ship is gone. A node that is gone runs no hooks, so
            // it cannot wait to start the game again: the game's own node,
            // which stays, does that when it hears "game over".
            emit_to("camera", "big shake");
            emit_to("game", "game over");
            spawn("big_explosion", position());
            destroy_self();
        }
  1. Press Ctrl+S to save ship.rgs.
  2. Save the scene with File → Save all, and click the Viewport tab.

The big explosion’s rumble plays on into the new game: a sound that plays once outlives the scene it started in.

The game with Space held: a diver shot down above the ship, in its lane, bursting into an orange fireball ringed with sparks beside a meteor
The game just after the last hit, the STAGE 2 banner across it: a big fireball where the ship was, ringed with sparks

Check it works