Skip to content

Tutorials · Interfaces · Chapter 7

The hotbar

55 minutes Uses: RadialProgress, Focus, Button, Signal, Panel, kept_number, keep_number, add_number, set_text, text, set_number, pressed, emit, heard, anneal_number, show, hide, in_group, min, max, destroy_self

By the end

  • The counters and the health bar showing what the game keeps
  • A hotbar of four slots that keys 1 to 4 use, a potion healing 3, the elixir hastening the hero
  • A cooldown ring on a slot after a use; B opens and shuts the bag
On this page

Counters that count

The counters read 0 and the bar is full, whatever happens. The room already keeps what the hero has: each pickup adds to a number the game keeps, keys, coins, gems, potions or elixirs, and the spikes take from health. A kept number lasts until the game closes, and any script can read it. What is missing is something to show it: a script on each counter, and one on the bar.

  1. In Assets, click Assets in the path bar over the tree, so the script is written at the project’s top, then press Create, choose New script, type counter, and press Enter.
  2. In the Code tab, replace everything in counter.rgs with the script below, and press Ctrl+S.
rgs counter.rgs
/// A count in the HUD: it shows how many the hero has of what it counts.
component Counter {
    /// The kept number it shows: keys, coins or gems.
    kind: String = "gems",

    fn loop() {
        // A pickup adds to its number on the step the hero walks into it, and
        // the count shows it from the next step on.
        set_text("TextComponent", "text", text(kept_number(kind, 0.0)));
    }
}
  1. Open scenes/delve.ron. Select key_count, click Add component and choose Counter. Set its Kind to keys.
  2. Do the same for coin_count, with Kind coins, and for gem_plate, with Kind gems, which is the script’s own start, so it already reads it.

kept_number(kind, 0.0) reads the number kept under that name, or 0 if nothing was ever kept. text(…) turns a number into words, a whole number with no decimal point. set_text("TextComponent", "text", …) writes the words onto this node’s own Text. A pickup adds to its number on the step the hero walks into it, and the count shows it from the next step on.

The Code panel with counter.rgs open: a comment on a count in the HUD, then component Counter with a kind field, a string, gems, and a loop that sets the Text’s text from the kept number named by kind; the Scripts list at the left names bag.rgs, cooldown_ring.rgs, counter.rgs and the project’s other scripts
  1. In Assets, press Create, choose New script, type health_bar, and press Enter.
  2. In the Code tab, replace everything in health_bar.rgs with the script below, and press Ctrl+S.
rgs health_bar.rgs
/// The health bar in the HUD: it shows the hero's health, which the spikes
/// take and a potion gives back.
component HealthBar {
    fn loop() {
        // The hero keeps its health as it enters the room. Until then the bar
        // shows what it was made with: full.
        let health = kept_number("health", -1.0);
        if health >= 0.0 {
            set_number("SliderComponent", "value", health);
        }
    }
}
  1. Select health_bar, click Add component and choose HealthBar.

The hero keeps its health as it enters the room, and the bar writes it into its Slider’s Value on every step. Until the hero has, kept_number("health", -1.0) answers the -1 it was given, which the script ignores, so the bar shows what it was made with: full.

kept_number
Reads a number kept under a name, as it was when the step began, or the second number if it was never kept. Pickups, the spikes and the hero all write to the same numbers, and this reads them.
set_text and text
set_text writes words onto another component of this node: here the Text’s Text. text turns a number into words.
The delve with the HUD’s plate at the top left and its coin counter reading 5, the hero at the right end of where the row of coins was, now taken, and at the foot of the screen a hotbar of four slots, the first holding a red flask, with the key line under it
Five coins picked up: the counter reads 5.

The hotbar

The hotbar is four slots at the foot of the screen. A key, 1 to 4, or a click uses what a slot holds: a potion heals, and the elixir makes the hero walk faster. What the hero picks up has to get into the slots first, and a script, the satchel, does that. It files each thing the hero takes into the first empty slot, and the slots and the bag show it.

Keys for the slots

  1. Choose View → Input map…. Type bag in the New action box and press Add. Do the same for use_1, use_2, use_3 and use_4.
  2. Select bag and press Choose key…, and find B in its list. Do the same for use_1 to use_4, with the keys 1, 2, 3 and 4.
  3. Press Save, and close the window.

An action is a name, and a key is bound to it. The input map keeps the names, and a script asks about them, so a player who rebinds B changes nothing in the scripts.

  1. Select key_hints, the line at the foot of the screen, and set its Text to WASD or arrows walk / B bag / 1-4 use. It names only what works at each point in the tutorial, and the bag and the slots work from now on.

The satchel

The satchel keeps one number for each slot: hotbar_1 to hotbar_4 and bag_1 to bag_16, each holding which item is in that slot, 1 for a potion, 2 for the elixir, 3 for a key and 0 for nothing. Every step it files at most one item the hero owns that no slot holds yet, into the first empty slot: a potion or the elixir into the hotbar while it has room and then the bag, a key only into the bag. It also keeps how much room is left, which a pickup reads before it lets the hero take it, so a full bag leaves the thing on the floor. Gems and coins are counted, never filed.

  1. In Assets, press Create, choose New script, type satchel, and press Enter.
  2. In the Code tab, replace everything in satchel.rgs with the script below, and press Ctrl+S.
rgs satchel.rgs
/// The hero's satchel: it files what the hero carries into the slots, the
/// hotbar's four and the bag's sixteen. What the hero owns is kept in
/// potions, elixirs and keys. Each slot is kept too, hotbar_1 to hotbar_4 and
/// bag_1 to bag_16, as the item in it: 1 a potion, 2 the elixir, 3 a key, 0
/// nothing. Only the satchel files, one item a step, so two pickups taken on
/// the same step never go in the same slot.
component Satchel {
    fn loop() {
        // What the slots hold as this step began: how many of each item, how
        // many slots are empty, the first empty one of the hotbar and of the
        // bag, and the last one holding each item.
        let potions = 0.0;
        let elixirs = 0.0;
        let keys = 0.0;
        let empty_hotbar = 0.0;
        let empty_bag = 0.0;
        let first_hotbar = "";
        let first_bag = "";
        let last_potion = "";
        let last_elixir = "";
        let last_key = "";
        for i in 1..5 {
            let slot = "hotbar_" + text(i);
            let item = kept_number(slot, 0.0);
            if item == 0.0 {
                empty_hotbar += 1.0;
                if first_hotbar == "" {
                    first_hotbar = slot;
                }
            } else if item == 1.0 {
                potions += 1.0;
                last_potion = slot;
            } else if item == 2.0 {
                elixirs += 1.0;
                last_elixir = slot;
            } else if item == 3.0 {
                keys += 1.0;
                last_key = slot;
            }
        }
        for i in 1..17 {
            let slot = "bag_" + text(i);
            let item = kept_number(slot, 0.0);
            if item == 0.0 {
                empty_bag += 1.0;
                if first_bag == "" {
                    first_bag = slot;
                }
            } else if item == 1.0 {
                potions += 1.0;
                last_potion = slot;
            } else if item == 2.0 {
                elixirs += 1.0;
                last_elixir = slot;
            } else if item == 3.0 {
                keys += 1.0;
                last_key = slot;
            }
        }

        // What the hero owns that no slot holds yet waits to be filed; less
        // than none is an item a slot holds that the hero no longer owns.
        let waiting_potions = kept_number("potions", 0.0) - potions;
        let waiting_elixirs = kept_number("elixirs", 0.0) - elixirs;
        let waiting_keys = kept_number("keys", 0.0) - keys;

        // One item a step, into the first empty slot. A potion or the elixir,
        // the two things the hero drinks, goes in the hotbar while it has
        // room, then in the bag: drink_slot is where it goes. A key goes only
        // in the bag.
        let drink_slot = first_hotbar;
        if drink_slot == "" {
            drink_slot = first_bag;
        }
        if waiting_potions > 0.0 && drink_slot != "" {
            keep_number(drink_slot, 1.0);
        } else if waiting_elixirs > 0.0 && drink_slot != "" {
            keep_number(drink_slot, 2.0);
        } else if waiting_keys > 0.0 && first_bag != "" {
            keep_number(first_bag, 3.0);
        }

        // An item the hero no longer owns, as the key the door took, leaves
        // the last slot holding it.
        if waiting_potions < 0.0 {
            keep_number(last_potion, 0.0);
        }
        if waiting_elixirs < 0.0 {
            keep_number(last_elixir, 0.0);
        }
        if waiting_keys < 0.0 {
            keep_number(last_key, 0.0);
        }

        // The room left once what waits is filed, which a pickup reads before
        // it lets the hero take it. What the hotbar has no room for waits for
        // the bag.
        let room_hotbar = empty_hotbar - max(waiting_potions, 0.0) - max(waiting_elixirs, 0.0);
        let room_bag = empty_bag - max(waiting_keys, 0.0);
        if room_hotbar < 0.0 {
            room_bag += room_hotbar;
            room_hotbar = 0.0;
        }
        keep_number("room_hotbar", room_hotbar);
        keep_number("room_bag", room_bag);
    }
}
  1. Select hud, click Add component and choose Satchel.

The first slot

The slots are buttons, in a Stack of their own. Their pictures come from the same pack as the title’s buttons, and items.png, which came with the starter, holds the five icons: it is one picture of five 16 by 16 squares, and its .meta names them potion, elixir, key, gem and coin.

  1. Copy the two files below into your project’s art folder, each from where the list says it is in the unzipped download. Then press Refresh in the Assets panel.
  1. Select hud, click + Add node and choose Stack. Rename it hotbar.
  2. In its Rect, set Anchor Min and Anchor Max both to 0.5, 1, Offset Min to -102, -97, Offset Max to 102, -48 and Pivot to 0.5, 1. That is a box 204 wide and 49 high, centred, with its foot 48 above the bottom of the screen.
  3. In its Stack, set Axis to Horizontal, Fit to Fixed and Item to 45. Spacing stays 8: four slots and three gaps are 204 wide.
  4. Select hotbar, click + Add node and choose Button. Rename it hotbar_1. Its Rect stays as it comes: the Stack places it.
  5. Remove its Text: press the bin at the right of its TextComponent header. A slot has no word.
  6. In its Panel, set Texture to buttonSquare_brown.png and Border to 8. In its Button, set Action to use_1 and Pressed Texture to buttonSquare_beige_pressed.png. The tints come from Apply theme, below.
  7. Select hotbar_1, click + Add node and choose Panel. Rename it hotbar_1_item, and untick its Visible: a slot shows nothing until it holds something.
  8. In its Rect, set Anchor Max to 0, 0, Offset Min to 6, 6, Offset Max to 38, 38 and Pivot to 0, 0. In its Panel, set Texture to items.png. That is a square 32 by 32, 6 in from the slot’s top left corner.

A slot shows what it holds

  1. In Assets, press Create, choose New script, type slot_picture, and press Enter.
  2. In the Code tab, replace everything in slot_picture.rgs with the script below, and press Ctrl+S.
rgs slot_picture.rgs
/// The picture in a slot of the hotbar or the bag: it shows the item the slot
/// holds, cut from the items picture by its region, and nothing while the slot
/// is empty.
component SlotPicture {
    /// The slot it shows, which is kept by that name: hotbar_1 to hotbar_4,
    /// or bag_1 to bag_16.
    slot: String = "hotbar_1",

    fn loop() {
        // The satchel and the slots keep what each slot holds: 1 a potion, 2
        // the elixir, 3 a key, 0 nothing.
        let item = kept_number(slot, 0.0);
        if item == 1.0 {
            set_text("PanelComponent", "region_name", "potion");
            show();
        } else if item == 2.0 {
            set_text("PanelComponent", "region_name", "elixir");
            show();
        } else if item == 3.0 {
            set_text("PanelComponent", "region_name", "key");
            show();
        } else {
            hide();
        }
    }
}
  1. Select hotbar_1_item, click Add component and choose SlotPicture. Set its Slot to hotbar_1.

The script reads the number the satchel keeps for its slot. For a potion, the elixir or a key it sets the Panel’s Atlas Region, chapter 3’s way of cutting a picture out of a sheet by name, to potion, elixir or key, and shows the node. For 0 it hides it. set_text writes the region’s name, since a region is text, and show and hide make a node visible or invisible.

A slot is used

  1. In Assets, press Create, choose New script, type hotbar_slot, and press Enter.
  2. In the Code tab, replace everything in hotbar_slot.rgs with the script below, and press Ctrl+S.
rgs hotbar_slot.rgs
/// A slot of the hotbar: its key or a click on it uses the item it holds. A
/// potion gives back health, but never past the most the hero can have, and
/// at full health it stays in the slot. The elixir hastens the hero. A use
/// empties the slot, which then cools down before it can be used again.
component HotbarSlot {
    /// Which slot it is, 1 to 4: what it holds is kept in hotbar_1 to
    /// hotbar_4, and use_1 to use_4 use it.
    index: f32 = 1.0,
    /// The health a potion gives back.
    heal: f32 = 3.0,
    /// The most health the hero can have.
    max_health: f32 = 10.0,
    /// How long the elixir hastens the hero for.
    #[suffix("s")]
    haste: f32 = 5.0,
    /// How long after a use the slot cannot be used again.
    #[suffix("s")]
    cooldown: f32 = 1.5,
    /// Seconds until the slot can be used again.
    #[hidden]
    cooling: f32 = 0.0,

    fn loop(dt: f32) {
        // Counted here rather than waited out: a hook that waits hears
        // nothing, and a use asked for meanwhile must be refused, not kept.
        if cooling > 0.0 {
            cooling -= dt;
        }
        // Its key, 1 to 4 through the input map, presses its use_ action on
        // the step the key goes down, and a click on the slot on the game's
        // next step.
        if pressed("use_" + text(index)) && cooling <= 0.0 {
            let slot = "hotbar_" + text(index);
            let item = kept_number(slot, 0.0);
            let health = kept_number("health", 0.0);
            let used = false;
            if item == 1.0 && health < max_health {
                add_number("health", min(heal, max_health - health));
                add_number("potions", -1.0);
                used = true;
            } else if item == 2.0 {
                keep_number("haste", haste);
                add_number("elixirs", -1.0);
                used = true;
            }
            if used {
                // Emptied on the step the count goes down, so the satchel
                // finds the two agreeing.
                keep_number(slot, 0.0);
                cooling = cooldown;
                // Heard by the ring the slot's Signal component wires it to,
                // on the next step.
                emit("used");
            }
        }
    }
}
  1. Select hotbar_1, click Add component and choose HotbarSlot. Set its Index to 1. Its other fields start at the script’s own numbers, 3, 10, 5 and 1.5: leave them.

pressed("use_1") is true on the step 1 goes down, or on the step after a click on the slot is let go. The script is one for all four slots: index says which, and it asks about use_ and that number.

The Code panel with hotbar_slot.rgs open: a comment on a slot of the hotbar, then component HotbarSlot with the fields index 1.0, heal 3.0, max_health 10.0, haste 5.0 with a suffix of seconds, cooldown 1.5 with a suffix of seconds, and a hidden cooling 0.0, and the start of a loop that counts cooling down; the Scripts list at the left names the project’s scripts
Index
Which slot this is, 1 to 4. The slot’s number is kept under hotbar_ and the index, and its key is the action use_ and the index.
Heal, Max Health
How much health a potion gives back, 3, and the most the hero can have, 10. A potion never takes the hero past 10, and at full health it is refused and stays in the slot.
Haste
How long the elixir makes the hero walk faster, in seconds: 5.
Cooldown
How long, in seconds, a slot cannot be used again after a use: 1.5. The script counts a hidden field, Cooling, down to 0 on every step, and refuses a use while it is above 0, so a refused press is ignored, not queued.

A use spends the item: a potion adds to health and takes one from potions, the elixir keeps haste for five seconds and takes one from elixirs, and the slot is emptied and starts cooling. add_number, min and keep_number do the sums, and emit tells the ring, which the ring section below wires up.

The pickups and the hero change

Two scripts the starter holds have to learn about slots. A pickup that needs a slot, a potion, the elixir or a key, stays on the floor while the satchel has no room for it, and the elixir has to make the hero walk faster. Both files are replaced.

  1. In the Code tab’s Scripts list, click pickup.rgs, replace everything in it with the script below, and press Ctrl+S.
rgs pickup.rgs
/// Something lying in the room that the hero takes by walking into it. It adds
/// `amount` to the number kept under `kind`, and is gone. A potion, the elixir
/// and the key each need a slot to go in, so one stays on the floor while the
/// satchel has no room for it.
component Pickup : Area {
    /// The kept number it counts into: potions, elixirs, keys, gems or coins.
    kind: String = "gems",
    /// How much it adds to that number.
    amount: f32 = 1.0,
    /// The item a slot shows for it: 1 a potion, 2 the elixir, 3 a key, or 0
    /// for what is only counted, as gems and coins are.
    item: f32 = 0.0,

    fn on_enter(other: NodeId) {
        // The hero's node is in the group "hero".
        if in_group(other, "hero") {
            // The room the satchel keeps: a key goes only in the bag, a potion
            // or the elixir in the hotbar or the bag.
            let room = kept_number("room_bag", 0.0);
            if item == 1.0 || item == 2.0 {
                room += kept_number("room_hotbar", 0.0);
            }
            if item == 0.0 || room >= amount {
                add_number(kind, amount);
                destroy_self();
            }
        }
    }
}
  1. Click hero.rgs, replace everything in it with the script below, and press Ctrl+S.
rgs hero.rgs
/// The hero, whom the keys walk through its Character Body. A new game starts
/// it with `health`, kept so that everything else can read it. While the
/// elixir hastens it, it walks faster.
component Hero {
    /// The health a new game starts with.
    health: f32 = 10.0,
    /// How fast the keys walk it.
    #[suffix("u/s")]
    walk_speed: f32 = 64.0,
    /// How many times as fast it walks while hastened.
    haste_factor: f32 = 1.5,

    fn on_create() {
        // Only when nothing is kept yet: a hero met again keeps what it has.
        if kept_number("health", -1.0) < 0.0 {
            keep_number("health", health);
        }
    }

    fn loop(dt: f32) {
        // The elixir keeps haste: the seconds left that the hero walks faster
        // for, counted down here.
        let haste = kept_number("haste", 0.0);
        if haste > 0.0 {
            keep_number("haste", max(haste - dt, 0.0));
            set_number("CharacterBodyComponent", "speed", walk_speed * haste_factor);
        } else {
            set_number("CharacterBodyComponent", "speed", walk_speed);
        }
    }
}

Each pickup now has an Item field, the number the satchel files it as. Set it on the pickups that go in a slot; gems and coins keep the 0 they start with, since they are only counted. The hero’s two new fields start at the script’s own 64 units a second and 1.5 times as fast: leave them.

  1. Select potion_1 and set its Item to 1. Do the same for potion_2. Select elixir and set its Item to 2, and select key and set its Item to 3.
  2. Select hero. Its Walk Speed is 64 and its Haste Factor 1.5, as the script starts.
The delve with the hero at the lower left having taken the second red flask: the first slot of the hotbar at the foot of the screen holds a red flask, one red flask is still on the floor, and the key line along the foot reads WASD OR ARROWS WALK / B BAG / 1-4 USE
The second potion picked up: the satchel files it into the first slot.

The bag opens

The bag is open all the time, as chapter 3 left it. It should be shut until the player asks, and B opens it and shuts it.

  1. In Assets, press Create, choose New script, type bag, and press Enter.
  2. In the Code tab, replace everything in bag.rgs with the script below, and press Ctrl+S.
rgs bag.rgs
/// The bag's window: shut as the delve opens, and opened and shut by B.
component Bag {
    /// Whether the bag is open.
    #[hidden]
    open: bool = false,

    fn on_create() {
        hide();
    }

    fn loop() {
        // B, through the input map's bag, presses bag on the step it goes
        // down.
        if pressed("bag") {
            open = !open;
            if open {
                show();
            } else {
                hide();
            }
        }
    }
}
  1. Select bag, click Add component and choose Bag.

pressed("bag") is true on the step B goes down. The script flips a hidden flag and shows or hides the bag. It also hides the bag on create, so the delve opens with it shut. The sixteen slots inside it have to show what the hero carries, as the hotbar’s do, and that comes with the next section. The picture shows the bag as it will look once they do.

The delve with the bag open at the right: a golden key in its first slot and the other fifteen empty, the key counter at 1 and the gem counter at 1, and the four slots of the hotbar empty
B: the bag, the key in its first slot.

Out of the ring’s way

The bag’s sixteen slots become buttons with a picture inside, as the hotbar’s are, and every control on the HUD is kept out of the focus. You build the first slot, and the other fifteen are copies of it.

  1. Select item_potion, then Ctrl-click item_elixir, item_key and item_gem, and press Delete. Chapter 3’s four pictures are drawn by a script from now on.
  2. Select bag_2, Shift-click bag_16, and press Delete. Only bag_1 is left, and the copies replace the others.
  3. Select bag_1, click Add component and choose ButtonComponent. Set its Action to bag_1_menu. Nothing hears it until chapter 9. The tints come from Apply theme.
  4. Click Add component and choose FocusComponent. Set its Mode to None.
  5. Select hotbar_1 and add a FocusComponent the same way, with Mode None. Do the same for drop_button.
bag_1 selected, its Inspector narrowed with the search box, focus, to the Button’s Hovered row and a Focus: Mode None, Subtree Focus Inherit, Subtree Pointer Inherit, and Up, Down, Left, Right, Next and Previous all empty, with a Clear Links button under them; the Viewport shows the room, the HUD, the open bag with its first slot outlined and the four slots of the hotbar
Mode
A Focus’s Mode: how a control may take the focus. All takes it by the keys and by a click, as the title’s buttons do. Click takes it only by a click, which chapter 16’s steppers use. None never takes it: here, for every slot and for Drop.

The bag’s slots show what they hold

  1. Select bag_1, click + Add node and choose Panel. Rename it bag_1_item, and untick its Visible.
  2. In its Rect, set Anchor Min and Anchor Max both to 0.5, 0.5, Offset Min to -16, -16 and Offset Max to 16, 16. In its Panel, set Texture to items.png.
  3. Click Add component and choose SlotPicture. Set its Slot to bag_1.
  4. Select bag_1 and press Ctrl+D. Rename the copy bag_2 and its child bag_2_item. Set the copy’s Button Action to bag_2_menu and its child’s Slot to bag_2.
  5. Do it fourteen times more, each time on the last copy, up to bag_16: its child bag_16_item, Action bag_16_menu and Slot bag_16. Each is a copy of bag_1 with its number in three words.
  6. Select hud and press Apply theme. It writes the tints of every new button and the white of every new picture.

A cooldown ring

After a slot is used it cannot be used for 1.5 seconds, and the player should see why. A ring over the slot, a Radial Progress, does that: it draws a circle of any share from 0 to 1, and a script lets it run down from full to empty.

  1. Select hotbar_1, click + Add node and choose Node. Rename it hotbar_1_ring. It sits after hotbar_1_item, so it is drawn over it.
  2. Click Add component and choose RectComponent. Set Anchor Max to 0, 0, Offset Max to 45, 45 and Pivot to 0, 0. That is a square 45 by 45, the size of the slot.
  3. Click Add component and choose RadialProgressComponent. Set Value to 0, Inner Radius to 0.62 and Color to 63, 38, 49, alpha 217. Start, Direction and Segments stay as they come.
hotbar_1_ring selected, its Inspector narrowed with the search box, radial, to its Radial Progress: Value 0.000, Start -90.00 °, Direction Clockwise, Inner Radius 0.62, Segments 64 and a translucent dark plum Color; the Viewport shows the room through the camera with the HUD’s plate, the open bag, and the four slots of the hotbar at the foot, the first outlined as the selection
Value
A Radial Progress’s Value: how much of the circle is drawn, 0 to 1. 0 draws none, so the ring is invisible while the slot is ready; 1 draws it whole; a cooldown animates it from 1 back to 0. A new one’s is 1.
Inner Radius
The hole in the middle, as a share of the outer radius, 0 to 0.95. 0 is a pie, filled to the centre; 0.62 leaves a ring, thick enough to see on a slot, with the item showing through. A new one’s is 0.
Color
The colour of the ring or pie, with its transparency: the dark plum of the outlines, 63, 38, 49, at 217 of 255, so the item shows through it a little.

The ring’s Start, Direction and Segments stay as they come: a circle that starts at the top and fills clockwise, from 64 segments. Chapter 16 defines them.

The slot has to tell the ring that it was used. A Signal component wires a slot’s signals to other nodes, so neither has to know the other: the slot says emit("used"), and whoever the Signal is wired to hears it on the next step.

  1. Select hotbar_1, click Add component and choose SignalComponent. Under Connections, press the + beside it once. Set Signal to used and To to hotbar_1_ring.
  1. In Assets, press Create, choose New script, type cooldown_ring, and press Enter.
  2. In the Code tab, replace everything in cooldown_ring.rgs with the script below, and press Ctrl+S.
rgs cooldown_ring.rgs
/// The ring over a hotbar slot: each time the slot is used, it fills and then
/// empties over the slot's cooldown, so the player sees how long it is until
/// the slot can be used again.
component CooldownRing {
    /// How long the ring takes to empty: the slot's cooldown.
    #[suffix("s")]
    seconds: f32 = 1.5,

    fn loop() {
        // The slot emits used when it uses its item, and its Signal component
        // wires that to this ring, which hears it on the next step.
        if heard("used") {
            // Full first, so the anneal starts from there.
            set_number("RadialProgressComponent", "value", 1.0);
            anneal_number("RadialProgressComponent", "value", 0.0, seconds, .Linear);
        }
    }
}
  1. Select hotbar_1_ring, click Add component and choose CooldownRing. Its Seconds starts at the script’s own 1.5, the slot’s cooldown: leave it.
The Code panel with cooldown_ring.rgs open: a comment on the ring over a hotbar slot, then component CooldownRing with a seconds field of 1.5, and a loop that, when it has heard used, sets the Radial Progress’s value to 1.0 and anneals it to 0.0 over seconds, linearly; the Scripts list at the left names the project’s scripts

When the ring hears used, it sets its Value to 1 and anneal_number runs it to 0 in 1.5 seconds, .Linear, at an even pace. heard is true on the one step the signal arrives.

The other three slots

  1. Select hotbar_1 and press Ctrl+D. The copy comes with its two children. Rename it hotbar_2, and its children hotbar_2_item and hotbar_2_ring.
  2. Set the copy’s values from the table below: its Button Action, its HotbarSlot Index, its Signal’s To, and its item picture’s SlotPicture Slot.
  3. Press Ctrl+D on hotbar_2 and do the same for hotbar_3, and again for hotbar_4.
SlotButton ActionHotbarSlot IndexSignal ToSlotPicture Slot
hotbar_1use_11hotbar_1_ringhotbar_1
hotbar_2use_22hotbar_2_ringhotbar_2
hotbar_3use_33hotbar_3_ringhotbar_3
hotbar_4use_44hotbar_4_ringhotbar_4

To see the ring, the hero has to use something, and a potion is refused at full health. So the hero has to be hurt first: the spikes take health while the hero stands on them.

The hero standing on the middle spike trap, the health bar’s orange fill shorter than before, the coin counter still reading 5, and the four slots of the hotbar at the foot, the first holding a red flask
On the spikes: the health bar down.
The hero below the spikes, the health bar full again, the first slot of the hotbar empty and a dark ring drawn part way round it, the coin counter still reading 5
1 drinks the potion: the bar full again and the ring running.

The counters, the hotbar, the rings and the bag’s slots

The pickups and the hero

NodeComponentFieldValue
potion_1, potion_2PickupItem1
elixirPickupItem2
keyPickupItem3
gem_green, gem_red, gem_blue, coin_1 to coin_5PickupItem0
heroHeroWalk Speed64
Haste Factor1.5

The counters

NodeComponentFieldValue
key_countCounterKindkeys
coin_countCounterKindcoins
gem_plateCounterKindgems

The hotbar

ComponentFieldValue
RectAnchor Min0.5, 1
Anchor Max0.5, 1
Offset Min-102, -97
Offset Max102, -48
Pivot0.5, 1
StackAxisHorizontal
FitFixed
Item45

The hotbar’s slots

NodeComponentFieldValue
hotbar_1 to hotbar_4PanelColor255, 255, 255, alpha 255, written by Apply theme
TexturebuttonSquare_brown.png
Border8
ButtonNormal255, 255, 255, alpha 255, written by Apply theme
Hovered255, 255, 255, alpha 255, written by Apply theme
Pressed211, 211, 211, alpha 255, written by Apply theme
Pressed TexturebuttonSquare_beige_pressed.png
FocusModeNone
HotbarSlotHeal3
Max Health10
Haste5
Cooldown1.5
Cooling0
hotbar_1ButtonActionuse_1
SignalConnectionsused to hotbar_1_ring
HotbarSlotIndex1
hotbar_2ButtonActionuse_2
SignalConnectionsused to hotbar_2_ring
HotbarSlotIndex2
hotbar_3ButtonActionuse_3
SignalConnectionsused to hotbar_3_ring
HotbarSlotIndex3
hotbar_4ButtonActionuse_4
SignalConnectionsused to hotbar_4_ring
HotbarSlotIndex4

The slots’ pictures and rings

NodeComponentFieldValue
hotbar_1_item to hotbar_4_itemNodeVisibleunticked
RectAnchor Max0, 0
Offset Min6, 6
Offset Max38, 38
Pivot0, 0
PanelColor255, 255, 255, alpha 255, written by Apply theme
Textureitems.png
SlotPictureSlothotbar_1 to hotbar_4
hotbar_1_ring to hotbar_4_ringRectAnchor Max0, 0
Offset Max45, 45
Pivot0, 0
Radial ProgressValue0
Inner Radius0.62
Color63, 38, 49, alpha 217
CooldownRingSeconds1.5

The bag’s slots

NodeComponentFieldValue
bag_1 to bag_16ButtonActionbag_1_menu to bag_16_menu
Normal255, 255, 255, alpha 255, written by Apply theme
Hovered255, 255, 255, alpha 255, written by Apply theme
Pressed211, 211, 211, alpha 255, written by Apply theme
FocusModeNone
bag_1_item to bag_16_itemNodeVisibleunticked
RectAnchor Min0.5, 0.5
Anchor Max0.5, 0.5
Offset Min-16, -16
Offset Max16, 16
PanelColor255, 255, 255, alpha 255, written by Apply theme
Textureitems.png
SlotPictureSlotbag_1 to bag_16
drop_buttonFocusModeNone

The key line

ComponentFieldValue
TextTextWASD or arrows walk / B bag / 1-4 use

The input map’s new actions

ActionKey
bagB
use_11
use_22
use_33
use_44

The bag’s Bag script and the slots’ hidden fields, Cooling and Open, are not typed. A Signal’s To is picked by the ring’s name, as the steps say; the project keeps the node’s number behind it. Every other field of every node is as it comes.

Check it works