Skip to content

Tutorials · Ship Evader · Chapter 15

Pause and settings

60 minutes Uses: Slider, Shortcut, Button, Focus, AudioSource, pause, resume, amount, pressed, set_music_volume, set_sfx_volume, save_number, saved_number, set_number, show, hide

By the end

  • Esc pausing everything that runs with the game, and a pause menu that runs only while it is paused
  • What a pause holds, and what plays on through it
  • A settings screen, with a slider for the music and one for the sound effects
  • Volumes saved as the sliders move, and applied by the title each time it opens
  • How to play’s third line, and the title’s fifth button
On this page

Esc pauses

The pause action has had Esc since chapter 4. A script of its own hears it, beside Game on the root, since Game’s loop spends the whole game waiting for the last life and hears nothing else. Make a script called pause_key:

  1. In Assets, press Create, choose New script, type pause_key, and press Enter. The editor writes pause_key.rgs into the project folder.
  2. Click the Code tab, then pause_key.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs pause_key.rgs
/// Esc, which the input map calls pause, pauses the game: everything that runs
/// with the game stops where it is, and the pause menu, which runs only while
/// the game is paused, takes over. It sits beside Game on the root, because
/// Game's loop spends the whole game waiting for the last life.
component PauseKey {
    fn loop() {
        if pressed("pause") {
            pause();
        }
    }
}
  1. Double-click scenes/game.ron in Assets to open the game, and click the Viewport tab.
  2. Select Game, the root, press Add component, and choose PauseKey: it sits beside Game.
  3. Press Ctrl+S.

pause pauses the scene as the step ends. Whether a node runs while the scene is paused is its Process Mode, behind the Inspector’s Advanced fold: Inherit, as its parent does, the default; Pausable; WhenPaused, only while paused; or Always. Every node in the game so far keeps the default, so all of it is pausable, and all of it stops.

The pause menu runs only while the game is paused. It shows itself on the first step of each pause, and hides itself on the step it resumes the game: once the game runs again its hooks stop, and a menu left showing would sit over the game. Make a script called pause_menu:

  1. In Assets, press Create, choose New script, type pause_menu, and press Enter. The editor writes pause_menu.rgs into the project folder.
  2. Click the Code tab, then pause_menu.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs pause_menu.rgs
/// The pause menu, over the game. It runs only while the game is paused, so
/// it shows itself on the first step of each pause, and it hides itself on the
/// step it resumes the game: once the game runs again its hooks stop, and a
/// menu left showing would sit over the game.
component PauseMenu {
    /// Whether it is showing, so it shows itself once a pause, not on every
    /// step.
    #[hidden]
    open: bool = false,

    fn loop() {
        if !open {
            show();
            open = true;
        }
        if pressed("ui_up") || pressed("ui_down") {
            play_sound("move");
        }
        if pressed("resume") {
            play_sound("press");
            hide();
            open = false;
            resume();
        }
        if pressed("title") {
            // A change of scene ends the pause too.
            play_sound("press");
            change_scene("title");
        }
    }
}
  1. Click the Viewport tab: game.ron is still open.
  2. Select hud, press + Add node, choose Node, and rename it pause_menu: it lands after banner.
  3. Untick its Visible.
  4. Open its Advanced fold and set Process Mode to WhenPaused.
  5. Give it a RectComponent, which fills the canvas; an AudioSourceComponent on Sfx with Positional unticked and the title menu’s two triggers, move at 0.4 and press at 0.6; and PauseMenu.
  6. Under pause_menu, a Label paused from the top centre, Offset Min -350 and 380, Offset Max 350 and 460, PAUSED at 1.3, centred.
  7. When you click the first colour of the buttons below, look at the top left of the picker that opens: if it reads U8, press it, so it reads F and the picker shows each value from 0 to 1, as this tutorial gives every colour, until you close the editor.
  8. Under pause_menu, a button resume, made as the title’s, at -222, 760 to 222, 838, RESUME, Action resume.
  9. Add a FocusComponent to it, then a ShortcutComponent with Action pause and Key empty: Esc resumes too, and RESUME draws ESC inside its right edge.
  10. Under pause_menu, a second button, title, also made as the title’s, at -222, 864 to 222, 942, QUIT TO TITLE, Action title, with a FocusComponent.
  11. Set resume’s Up to title and title’s Down to resume: the list a Focus link opens offers only nodes that have a FocusComponent of their own, so both get theirs first.
  12. Choose File → Save.
The pause menu selected: its Visible unticked, and under Advanced its Process Mode set to WhenPaused

Its buttons sit on game over’s rows, below where the STAGE banner shows, so a pause during a banner covers neither. Nothing dims the field behind the menu, and nothing should: a plain panel would be drawn over the buttons, since the interface draws its pictures grouped by texture rather than in the tree’s order. The menu reads well over the field as it is.

What a pause holds

  • Held where they are: everything that moves, every hook, wait and anneal, every particle and animated sprite, the starfield, and the game’s sounds, which carry on from where they stopped when the game resumes.
  • Playing on: the music, the pause menu’s own sounds, and any sound started while paused.
  • Working: kept and saved values, the keys, and the menus.
  • A change of scene ends a pause: QUIT TO TITLE goes to the title unpaused.
The game paused: PAUSED, a lit RESUME, with ESC at its right edge, and QUIT TO TITLE over the field, its lasers, three divers, one next to the ship, a big rock with a small one beside it, the edge of a third rock and the stars held where they were, and the field not dimmed
  1. Open scenes/how_to_play.ron, select controls, and add a third line to its Text, PAUSE: ESC: Esc pauses now.
  2. Choose File → Save: in a Text, Enter starts a new line rather than leaving the field, so Ctrl+S would save nothing.
How to play with three controls: FLY: W A S D OR ARROWS, FIRE: SPACE and PAUSE: ESC

Settings

A Slider reports an action too: when the player moves it, its action is pressed, and amount answers where it now is. Each slider’s script sets its bus and saves the value, and opens on the saved one. Make a script called music_volume:

  1. In Assets, press Create, choose New script, type music_volume, and press Enter. The editor writes music_volume.rgs into the project folder.
  2. Click the Code tab, then music_volume.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs music_volume.rgs
/// The music slider, on the settings screen: it opens where the saved music
/// volume is, and each move the player makes sets the music's volume and
/// saves it.
component MusicVolume {
    fn on_create() {
        // Writing a slider moves it and presses nothing, so the screen opens
        // on the saved volume and saves nothing.
        set_number("SliderComponent", "value", saved_number("music_volume", 0.8));
    }

    fn loop() {
        // Only on a step the player moved it, which is when amount() answers
        // the slider's new place.
        if pressed("music") {
            let level = amount("music");
            set_music_volume(level);
            save_number("music_volume", level);
        }
    }
}

Then a script called effects_volume:

  1. In Assets, press Create, choose New script, type effects_volume, and press Enter. The editor writes effects_volume.rgs into the project folder.
  2. Click the Code tab, then effects_volume.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs effects_volume.rgs
/// The effects slider, on the settings screen: it opens where the saved
/// effects volume is, and each move the player makes sets the effects' volume,
/// saves it, and plays a laser at it, so the player hears how loud the game
/// will be. While it is dragged, the laser plays no more than once every
/// sample_gap.
component EffectsVolume {
    /// The shortest time between two of the laser's sounds while the slider
    /// moves.
    #[suffix("s")]
    sample_gap: f32 = 0.15,
    /// Seconds until the laser may sound again.
    #[hidden]
    quiet: f32 = 0.0,

    fn on_create() {
        // Writing a slider moves it and presses nothing, so the screen opens
        // on the saved volume without a sound.
        set_number("SliderComponent", "value", saved_number("effects_volume", 0.8));
    }

    fn loop(dt: f32) {
        // Counted here rather than waited out: a hook that waits hears
        // nothing, and the moves made meanwhile would go unset and unsaved.
        if quiet > 0.0 {
            quiet -= dt;
        }
        if pressed("effects") {
            let level = amount("effects");
            set_sfx_volume(level);
            save_number("effects_volume", level);
            if quiet <= 0.0 {
                play_sound("sample");
                quiet = sample_gap;
            }
        }
    }
}
if pressed("music")
Only on a step the player moved the slider is amount read. A screen’s first step reads no slider, so a script that read it every step would change the volume as the screen opened.
set_number on the Slider
Writing a slider moves it and presses nothing, so the screen opens on the saved volume without saving or sounding.
set_music_volume and save_number
Set the Music bus at once, and save the value under music_volume; the effects slider sets the Sfx bus and saves effects_volume. A saved value is written to the file a moment after it changes, not on every step a slider is dragged.
quiet
The effects slider plays a laser at the new level, no more than once every 0.15 s, counted down in loop(dt). A wait would not do: a hook that is waiting hears nothing, and the moves made meanwhile would go unset and unsaved.

The screen’s own script plays a tick as the focus moves, and goes back on BACK or Esc. Make a script called settings_menu:

  1. In Assets, press Create, choose New script, type settings_menu, and press Enter. The editor writes settings_menu.rgs into the project folder.
  2. Click the Code tab, then settings_menu.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs settings_menu.rgs
/// The settings screen's own sounds and its way back: a sound as the focus
/// moves between the two sliders and Back, and Back, or Esc through its
/// Shortcut, goes to the title. Each slider looks after its own volume.
component SettingsMenu {
    fn loop() {
        if pressed("ui_up") || pressed("ui_down") {
            play_sound("move");
        }
        if pressed("back") {
            // A sound asked for on the step that changes scene plays on after
            // it.
            play_sound("back");
            change_scene("title");
        }
    }
}
  1. Choose File → New scene and click the Viewport tab.
  2. Add a Node root called Settings with the same AudioSourceComponent as Title’s.
  3. Choose File → Save as… and save the scene as scenes/settings.ron: the editor saves no scene without a node in it, which is why this comes after Settings.
  4. Build the rest as How to play: under Settings, the game’s camera (no Shake) and background, and a screen canvas.
  5. Under screen, a Label heading, -350, 200 to 350, 280, SETTINGS at 1.3, centred.
  6. Under screen, a menu Node with a RectComponent, an AudioSourceComponent on Sfx with Positional unticked and two triggers, move (audio/select_001.ogg, 0.4) and back (audio/back_001.ogg, 0.6), and SettingsMenu.
  7. Under menu, press + Add node, choose Slider, and rename it music.
  8. Set its Rect from the top centre: Offset Min -240 and 440, Offset Max 240 and 480.
  9. Set its Slider: Action music, Value 0.8, Min 0, Max 1, Step 0.1, Interactive ticked, Fill 0.55, 0.75, 1, alpha 1, Track 0.15, 0.15, 0.25, alpha 1.
  10. Add a FocusComponent and MusicVolume.
  11. Under music, a Label music_label that names the slider, above it: Anchor Min 0 and 0, Anchor Max 1 and 0, Offset Min 0 and -56, Offset Max 0 and -8; MUSIC at 0.8, centred.
  12. Under menu, a second Slider effects, the same but at -240, 640 to 240, 680, with Action effects, no Focus, an AudioSourceComponent on Sfx with Positional unticked and one trigger, sample, Clip audio/laserSmall_000.ogg at 0.5, and EffectsVolume.
  13. Under effects, a Label effects_label saying EFFECTS, as the music’s.
  14. Under menu, a Menu Button back, made as the title’s, at -222, 900 to 222, 978, BACK, Action back; its Shortcut’s Action ui_cancel, which draws ESC inside BACK’s right edge as it did in chapter 13; then a FocusComponent.
  15. Set music’s Up to back and back’s Down to music, now that both have a FocusComponent.
  16. Choose File → Save.
The settings scene in the editor, the music slider selected: its Slider with Action music, Value 0.8, Min 0, Max 1 and Step 0.1, and the Hierarchy listing the sliders, their labels and the back button
The effects slider’s EffectsVolume narrowed to Sample Gap 0.15 s

In an exported game a slider that has the focus is lifted toward white, its fill and its track both: Left and Right, or A and D, move the lifted one, a tenth at a time, so the slider the keys move shows where the focus is. The mouse drags either.

The settings screen: SETTINGS, a MUSIC slider and an EFFECTS slider, each filled to 0.8 and the MUSIC one lifted toward white, and BACK with ESC at its right edge
The settings screen after one press of D: the MUSIC slider, still the lifted one, filled to 0.9, the EFFECTS slider still at 0.8

The title applies the volumes

The title sets both buses from the saved volumes as it opens, so they hold from the game’s first sound, at every launch. Make a script called title:

  1. In Assets, press Create, choose New script, type title, and press Enter. The editor writes title.rgs into the project folder.
  2. Click the Code tab, then title.rgs in its Scripts list, replace everything in it with the script below, and press Ctrl+S.
rgs title.rgs
/// The title, on its root: the volumes the player saved in Settings, applied
/// as the title opens, so they hold from the game's first sound. 0.8 until
/// the player has chosen any.
component Title {
    fn on_create() {
        set_music_volume(saved_number("music_volume", 0.8));
        set_sfx_volume(saved_number("effects_volume", 0.8));
    }
}

Until a player moves a slider, both are 0.8, so from here every sound plays at 0.8 of its own level: the game is a little quieter than it was. A scene opened straight from the editor, without the title before it, plays at full volume.

  1. Double-click scenes/title.ron in Assets to open the title, and click the Viewport tab.
  2. Select Title, the root, press Add component, and choose Title.
  3. Right-click how_to_play and choose Duplicate: the copy lands after it.
  4. Rename the copy settings, and set its Offset Min to -222 and 808, Offset Max to 222 and 886, Text to SETTINGS and Action to settings.
  5. Move credits down to -222, 912 to 222, 990, and quit to -222, 1016 to 222, 1094.
  6. Press Enter, then Ctrl+S.
  7. Click the Code tab, then title_menu.rgs in its Scripts list, replace everything in it with the script below, now that scenes/settings.ron exists, and press Ctrl+S.
rgs title_menu.rgs
/// The title's menu: a sound as the focus moves, and where each of its
/// buttons goes. Play is the NewGame beside this, which starts a new game.
component TitleMenu {
    fn loop() {
        // W and S, and the arrows: the input map's ui_up and ui_down, which
        // move the focus. The menu goes round from its last button to its
        // first and back, so each of them moves it.
        if pressed("ui_up") || pressed("ui_down") {
            play_sound("move");
        }
        if pressed("how_to_play") {
            // A sound asked for on the step that changes scene plays on after
            // it.
            play_sound("press");
            change_scene("how_to_play");
        }
        if pressed("settings") {
            play_sound("press");
            change_scene("settings");
        }
        if pressed("credits") {
            play_sound("press");
            change_scene("credits");
        }
        if pressed("quit") {
            // No sound: the game closes at the end of this step.
            quit();
        }
    }
}
  1. Click the Viewport tab, so the game runs in front of you when you press F5.
The title with five buttons, PLAY lit, then HOW TO PLAY, SETTINGS, CREDITS and QUIT

Check it works