Add-ons / Meter Macro Set

A set of macros and JavaScript APIs for creating and working with dynamic, animated meters. Useful for creating things like health bars, progress meters, and visual timers.

Author ChapelR
Website https://twinelab.net/custom-macros-for-sugarcube-2/#/meter-macros
Story format SugarCube 2
Last checked Tue Nov 24 2020
License Unlicense
Download meter.zip

Index

Overview

A set of macros and JavaScript APIs for creating and working with dynamic, animated meters. Useful for creating things like health bars, progress meters, and visual timers.

Macros

<<newmeter>>

<<newmeter>>

Syntax:

<<newmeter name [value]>>
    ... optional child tags (see below) ...
<</newmeter>>

The <<newmeter>> macro can be used to create a new meter instance. You must provide a name for the meter, in quotes, and can optionally pass a starting value (between 0 and 1, inclusive) for the meter. You can configure a number of other meter options using the optional child tags <<colors>>, <<sizing>>, <<animation>>, and <<label>>. You do not have to include any of the child tags.

Each <<newmeter>> macro call defines a set of attributes that the meter will use, and allows the meter system to reference and alter the meter's value and animate it. Each meter profile should be unique on the page; if you want to create a health bar meter for both enemy health and player health and the configurations are largely the same, for example, you will still need to create two different meters via the <<newmeter>> macro to show them on the same passage / page.

⚠️ Attention: You should define your meters before using them, and you should always do so in your StoryInit special passage. Meters are not stateful, thus you must to define them here (or in your Story JavaScript via the API) or they won't work across browser refreshes and saved games. In fact, failing to use the <<newmeter>> macro before the story begins will raise an error.

⚠️ Warning: Any other code or text in the <<newmeter>> macro, other than its child tags, will have no effect, and will simply be discarded.

Arguments:

READ: Documentation

<<colors>>

<<colors>>

Syntax:

<<newmeter ...>>
    <<colors full [empty] [backing]>>
<</newmeter>>

The <<colors>> child tag can be used to configure the color scheme of the meter, and should be valid CSS color values. If only the full color is provided, or if full and empty are the same, the meter will appear as a single color, regardless of its value. On the other hand, if both a full and empty value are provided, the meter will blend the colors as its value changes. The backing argument controls the color for the empty portions outside the meter.

Arguments:

READ: Documentation

<<sizing>>

<<sizing>>

Syntax:

<<newmeter ...>>
    <<sizing width [height]>>
<</newmeter>>

The <<sizing>> child tag can be used to determine the width and height of the meter. These values can be in any valid CSS size units, like px, em,%, vw, etc.

Arguments:

<<animation>>

<<animation>>

Syntax:

<<newmeter ...>>
    <<animation timeOrFalse [easing]>>
<</newmeter>>

The <<animation>> child tag can be used to configure the timing and easing of meter animations for whenever meters are updated. By default, the animation lasts 400ms and is eased with jQuery's "swing" easing.

Arguments:

READ: Documentation

<<label>>

<<label>>

Syntax:

<<newmeter ...>>
    <<label labelText [textColor] [alignment]>>
<</newmeter>>

The <<label>> child tag can be used to configure label for your meter which can include TwineScript values, like $health. The label is always centered vertically, but you may align it horizontally. The label's font size is determined by the size of the meter. If the meter is too small, the label's font will be scaled down to fit, otherwise it will default to the font size of its most immediate parent. If you want to change the font size of the meter, wrap it in another element with the desired font-size style. Tip: Any time a meter is animated, the string content of the labelText is reprocessed, meaning variables and the like will be dynamically updated.

Arguments:

READ: Documentation

<<showmeter>>

<<showmeter>>

Syntax: <<showmeter name [value]>>

Renders the indicated meter into the passage, optionally setting the value. If you set the value, the meter will animate to that value after rendering.

Arguments:

Usage:

/* render the player's health bar */
<<showmeter '$healthBar' `$health / $maxHealth`>>
/* render the experience meter */
<<set _xp to $exp / $neededForNextLevel>>
<<showmeter '$xpBar' _xp>>
/* create a timer meter */
Given the following in StoryInit:
<<newmeter '_tenSeconds' 0>>
    <<animation 10s linear>>
<</newmeter>>
In passage:
You have ten seconds until the bomb explodes...
<<showmeter '_tenSeconds' 1>>
<<timed 10s>>BOOM!<</timed>>

READ: Documentation

<<updatemeter>>

<<updatemeter>>

Syntax: <<updatemeter name value>>

Changes the value of a meter; if the meter is on the page, it will be automatically changed, with an animation, depending on how the meter was set up.

Arguments:

Usage:

Given the follwing in StoryInit:
<<newmeter '$healthBar'>><<label '$health'>><</newmeter>>
<<newmeter '$xpBar'>><</newmeter>>
/* change the player's health */
<<set $health to 23, $maxHealth to 130>>\
<<showmeter '$healthBar' `$health / $maxHealth`>>
<<link 'take a potion'>>
    <<set $health to Math.clamp($health + random(20, 35), 0, $maxHealth)>>
    <<updatemeter '$healthBar' `$health / $maxHealth`>>
<</link>>
<<link 'take damage'>>
    <<set $health to Math.clamp($health - random(10, 25), 0, $maxHealth)>>
    <<updatemeter '$healthBar' `$health / $maxHealth`>>
<</link>>
/* gain some experience */
<<show '$xpBar'>>
You earned 100xp!
<<set $xp += 100>>
<<set _xp to $xp / $neededForNextLevel>>
<<updatemeter '$xpBar' _xp>>

READ: Documentation

Examples for <<newmeter>> and its child tags

/* setting up a meter for player health */
<<newmeter 'healthBar'>>
    <<animation 300ms>>
    <<colors 'yellow' 'red' 'black'>>
    <<label '$health' 'black' center>>
<</newmeter>>

/* setting up an experience bar */
<<newmeter 'xpBar' 0>>
    <<animation false>>
    <<sizing '100%'>>
<</newmeter>>

/* setting up a timer meter */
<<newmeter 'timer' 0>>
    <<animation 10s linear>>
<</newmeter>>

JavaScript API

Meter.add()

Meter.add()

Syntax: Meter.add(name [, options] [, value])

Returns: A new Meter instance.

Functionally the same as the <<newmeter>> macro and its child tags, this method creates a new meter instance with a given name. Unlike the macro version and its tags, you just need to pass a flat object of options (see below) to configure the meter's settings.

Arguments:

The default settings you can overwrite with you options object looks like this:

{
    full    : "#2ECC40", // color when the meter is full
    empty   : "#FF4136", // color when the meter is empty
    back    : "#DDDDDD", // color of the backing
    height  : "10px",    // overall height of the meter
    width   : "180px",   // overall width of the meter
    animate : 400,       // animation time in ms
    easing  : "swing",   // the animation easing
    label   : "",        // the text (including TwineScript expressions) to display in the label
    text    : "#111111", // the text color for the label
    align   : "center"   // the text alignment for the label
}

Usage:

Meter.add('healthBar', {}, 1);
Meter.add('timer', { animate : 10000 }, 0);
Meter.add('xpBar');

Meter.is()

Meter.is()

Syntax: Meter.is(thing)

Returns: Boolean.

Tests if the passed thing is an instance of the Meter constructor.

Arguments:

Usage:

var d = new Date();
var a = [];
var s = 'hi';
var m = Meter.add('blah');

Meter.is(d); // false
Meter.is(a); // false
Meter.is(s); // false
Meter.is(m); // true

Meter.has()

Meter.has()

Syntax: Meter.has(name)

Returns: Boolean.

Tests if a meter with the given name exists.

Arguments:

Usage:

if (!Meter.has('blah')) {
    Meter.add('blah');
}

Meter.get()

Meter.get()

Syntax: Meter.get(name)

Returns: A meter instance.

Returns the meter instance with the given name, or null, if it doesn't exist.

Arguments:

Usage:

var blah = Meter.get('blah');

Meter.del()

Meter.del()

Syntax: Meter.del(name)

Returns: Nothing.

If a meter with the given name exists, it is deleted.

Arguments:

Usage:

Meter.add('blah');
Meter.has('blah'); // true
Meter.del('blah');
Meter.has('blah'); // false

<meter>.animate()

<meter>.animate()

Syntax: <meter>.animate()

Returns: This instance (chainable).

Causes the meter to animate according to its settings after a new value has been set.

📝 Note: As part of the animation process, the meter's label text is reprocessed, meaning dynamic content, like variables, can be used in labels and will update with the meter.

Usage:

Meter.get('blah').value = 0.8;
Meter.get('blah').animate();

<meter>.val()

<meter>.val()

Syntax: <meter>.val([value])

Returns: The meter's value.

Sets or returns the meter's value. If you set the meter's value with this method, it is instantly updated and automatically animated if possible.

Arguments:

Usage:

Meter.add('blah', {}, 0.4);
Meter.get('blah').val(); // 0.4
Meter.get('blah').val(0.1); // sets the meter to 0.1, and returns 0.1

<meter>.options()

<meter>.options()

Syntax: <meter>.options([settings])

Returns: The meter's settings object.

Sets or returns the meter's settings

📝 Tip: This method can be used to change a meter's colors, animations, or other properties after it's been created.

Arguments:

Usage:

Meter.get('blah').options(); // returns the settings of the meter
Meter.get('blah').options({ animate : 1000 }); // changes the meter's `animate` setting, and returns the settings object

if (Meter.get('blah').options().animate < 500) {
    // do something if the meter's animation time is less than half a second
}

<meter>.place()

<meter>.place()

Syntax: <meter>.place(target)

Returns: This instance (chainable).

Places the meter on the page in the target element.

Arguments:

Usage:

Meter.get('blah').place('#some-element');

<meter>.click()

<meter>.click()

Syntax: <meter>.click([options,] cb)

Returns: This instance (chainable).

Sets up an accessible, WAI-ARIA-compatible click event using SugarCube's jquery#ariaClick() method. Accepts the same options as that method. Users may use <meter>.$element.ariaClick() instead--this method is just provided as a bit of a shortcut.

Arguments:

Usage:

Meter.get('blah').ariaClick({ label : 'Blah!' }, function (event) {
    var meter = Meter.get('blah');
    meter.val(meter.value + 0.1);
});

<meter>.on()

<meter>.on()

Syntax: <meter>.on(event, cb)

Returns: This instance (chainable).

Sets up a recurring event handler on the indicated event. Similar to jQuery's .on() method, but doesn't accept namespaces (namespaces passed in will be stripped from their event types). If you need more access and know what you're doing, use meter.$element.on() instead.

Arguments:

Usage:

Meter.get('blah').on('dblclick', function (event) {
    var meter = Meter.get('blah');
    meter.val(meter.value + 0.1);
});

<meter>.one()

<meter>.one()

Syntax: <meter>.one(event, cb)

Returns: This instance (chainable).

Sets up a one-time event handler on the indicated event. Similar to jQuery's .one() method, but doesn't accept namespaces (namespaces passed in will be stripped from their event types). If you need more access and know what you're doing, use meter.$element.one() instead.

Arguments:

Usage:

Meter.get('blah').one('click', function (event) {
    alert('This is the blah meter.');
});

<meter>.off()

<meter>.off()

Syntax: <meter>.off(event)

Returns: This instance (chainable).

Removes user event handlers that are bound to the meter. Only event handlers set up via meter#on() and meter#one() are affected. If you need more access and know what you're doing, use meter.$element.off() instead.

Arguments:

Usage:

Meter.get('blah').off('click'); // removes click events from the meter

<meter>.clone()

<meter>.clone()

Syntax: <meter>.clone() or clone(<meter>)

Returns: A deep copy of this meter instance.

This method creates deep copies/clones of the meter as a new meter instance. This may not work how you expect, since the new copy is not accessible via the Meter.get() and related methods; those will still point to the old meter. This is only included so that meters may be used in story variables for authors with unusual needs, and is only documented to make you aware of that fact.

<meter>.toJSON()

<meter>.toJSON()

Syntax: <meter>.toJSON() or JSON.stringify(<meter>)

Returns: A serilaized version of the meter (for SugarCube saves).

This method creates a JSON-serializable string of a meter instance, for use this SugarCube's serialization systems. This is only included so that meters may be used in story variables for authors with unusual needs, and is only documented to make you aware of that fact.

Instance properties

Instances of Meter have the following properties:

Events

The meter's animations trigger two events you can plug into, should you need to. These are triggered on the meter's element (<meter>.$element) but should bubble up to the document for you to catch. The event object is given a meter property that represents the Meter instance that sent the event. The events are:

Usage

var meter = Meter.add('healthBar');

meter.on(':meter-animation-start', function () {
    alert('Your health has changed.');
});

The JavaScript above could also be adapted to work with TwineScript:

<<newmeter 'healthBar'>><</newmeter>>
<<script>>
    Meter.get('healthBar').on(':meter-animation-start', function () {
        alert('Your health has changed.');
    });
<</script>>

Other usage notes

Configuration options

You can alter whether the system attempts to make the Meter constuctor global at the top of the un-minified script. You can also change the default settings meters are created with just below the options--use this so you can spend less time configuring your meters.

HTML structure

The HTML structure of the generated meter looks like this:

<div class='chapel-meter' data-val='value*' data-label='label*'>
    <div class='meter-label'>label*</div>
    <div class='meter-bottom'>
        <div class='meter-top'></div>
    </div>
</div>

\* Filled in by the appropriate property of the meter instance.

Styling options

You can adjust some meter styles via CSS using the above HTML structure. The styles set via the meter's configuration are applied via jQuery on the element itself, and cannot be easily overridden without liberal use of the !important rule.

Live demo

Demo Twee code:

:: Start
<<set $health to 63, $maxHealth to 100>>\
<<showmeter '$healthBar' `$health / $maxHealth`>>
<<link 'Drink a potion.'>>
    <<set $health to Math.clamp($health + 25, 0, $maxHealth)>>
  <<updatemeter '$healthBar' `$health / $maxHealth`>>
<</link>>
<<link 'Take damage.'>>
  <<set $health to Math.clamp($health - random(10, 25), 0, $maxHealth)>>
  <<updatemeter '$healthBar' `$health / $maxHealth`>>
<</link>>

:: StoryInit
<<newmeter '$healthBar'>><<label '$health' var(--sidebar-text)>>
  <<colors '#5AA600' '#E74A14' 'var(--sidebar-color)'>>
<</newmeter>>