0 votes
by (210 points)
Hey everyone! So I recently created a health bar and stat system in my Twine game, but I have one issue: neither of them seem to actually be working. They are present and visible via the UI bar in Sugarcube 2, however I cannot seem to get them to change (stats being increased; health being decreased). Here is the Javascript code for the health bar I am using (taken from another thread):

postrender["Update Health Bar"] = function (content, taskName) {
    var hBar = $('#right-ui-bar-body .sanity-bar'),
            bar = hBar.find('.bar'),
            hit = hBar.find('.hit');
    var total = State.variables.totalHealth,
            value = State.variables.health,
            damage = State.variables.damage,
            hitWidth = 0,
            barWidth = (value / total) * 125,
            delayReset = false;
    
    if (damage != 0) {
        hitWidth = (damage / value) * 125;
        value -= damage;
        barWidth = (value / total) * 125;
        State.variables.health = value;
        State.variables.damage = 0;
        delayReset = true;
    }

    hBar.data('total', total);
    hBar.data('value', value);
    hit.css('width', hitWidth + "%");
    bar.css('width', barWidth + "%");
    
    if (delayReset) {
        setTimeout(function(){
            hit.css({'width': '0'});
            bar.css('width', (State.variables.health / State.variables.totalHealth) * 100 + "%");
        }, 500);
    }
};
//

Here is the StoryCaption input code I am using to display the health bar:

<div class="sanity-bar">\
    <div class="bar">\
        <div class="hit"></div>\
    </div>\
</div>\

And here are the macros present in StoryInit for the health bar

<<set $totalSanity to 100>>
<<set $sanity to $totalsanity>>
<<set $damage to 0>>

 

As for the stat system it is far more simple; the player's stats can only go up, and trigger alongside certain events. Here is what I have for the stat system:

<<set setup.INSIGHT to ["Lacking Insight", "Strange Insight", "Unsettling Insight", "Disturbing Insight", "Dark Insight", "Maddening Insight", "Sanguine Insight"]>>

And here is what I set up in the widget to gain the stats:

<<widget "GainInsight">>
    <<if $insight == setup.INSIGHT>>
        <<-setup.INSIGHT[$insight++]>>
    <</if>>
<</widget>>

 

The issue now is that the health bar does not visibly decrease when it is meant to (an example of the input I use to decrease the player's health being <<-$sanity - 50>>), and the player's Insight does not increase (an example of the marco I am using to increase sanity being present in the above widget). I am guessing I am doing something obvious wrong, but I through the fiddling I have done I can't seem to find any solution. If anyone can provide any advice, I would be really grateful!

1 Answer

+1 vote
by (159k points)
edited by

Your problem is a result of a number of issues:

a. The implementation found in the old Health bar in right side bar or in a status passage thread you are basing your code on was designed to be used in conjunction with the Right Side-bar implementation found in the old Creating A Panel Similar To The UI-Bar thread. This means that it makes a number of assumptions that aren't correct when trying to display the generated status-bar in the StoryCaption special passage instead.

b. The contents of StoryCaption special passage is processed after the code in the postrender task has been executed, this means that any updates to status-bar made within the task are overwritten by the contents of StoryCaption.

WARNING:

  • The JavaScript code in the following answer relies on featues that were added in v2.22.0 of SugarCube 2, which was released after the current v2.2.1 release of the Twine 2 application.
  • I strongly suggest that whenever possible people using the SugarCube 2 story format should keep thier version of it up to date by downloading and installing the latest release found on the its official web-site.
  • For those unable to upgrade to a more up to date SugarCube 2 release I will (hopefully shortly) implement a v2.21.0 compatible version of this answer.

The following is a new implementation of the status-bar code that is compatable StoryCaption, it consists of five parts:

1. A new <<statusbar>> macro.
Place the following code within your project's Story Javascript area.

/*
	<<statusbar "identifier" "$maximum" "$current" "$changer">>

	identifier - The UNIQUE identifier used as the HTML element ID of this bar.
	$maximum   - The story variable that contains the maximum value this bar can be.
	$current   - The story variable than contains the current value of this bar.
	$changer   - The story variable this bar monitors to determine how much the current value should change by.

	eg. <<statusbar "sanity-bar" "$totalSanity" "$sanity" "$abuse">>
*/
Macro.add('statusbar', {
	skipArgs : false,
	handler  : function () {
		/* Check if correct parameters were passed. */
		if (this.args.length < 4) {
			return this.error('not enough parameters specified');
		}
		/* TODO: Validate each of the four parameters being passed to the macro. */

		var identifier  = this.args[0],
			maximum     = State.getVar(this.args[1]),
			current     = State.getVar(this.args[2]),
			change      = State.getVar(this.args[3]),
			barWidth    = (current / maximum) * 100,
			changeWidth = 0,
			delayReset  = false;

		/* Generate the HTML structure. */
		const $parent = jQuery(document.createElement('div'));
		$parent
			.addClass("status-bar")
			.attr({
				'id': identifier,
				'data-maximum': maximum,
				'data-current': current
			});

		const $bar = jQuery(document.createElement('div'));
		$bar
			.addClass("bar")
			.css('width', barWidth + "%")
			.appendTo($parent);

		const $change = jQuery(document.createElement('div'));
		$change
			.addClass("change")
			.css('width', changeWidth + "%")
			.appendTo($bar);

		$parent.appendTo(this.output);

		/* Handle any required change to the current value. */
		if (change != 0) {
			changeWidth = (change / current) * 100;
			current     -= change;
			barWidth    = (current / maximum) * 100;

			State.setVar(this.args[2], current);
			State.setVar(this.args[3], 0);
			delayReset = true;
		}

		/* Apply the change visual effect if needed. */
		if (delayReset) {
			setTimeout(function(){
				$change.css({'width': '0'});
				$bar.css('width', barWidth + "%");
			}, 500);
		}
	}
});


2. New CSS to style the status-bar generated by the new macro.
Place the following code within your project's Story Stylesheet area.

/* Status Bar styling. */
.status-bar {
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
	width: 200px;
	height: 20px;
	padding: 5px;
	background: #ddd;
	-webkit-border-radius: 5px;
	-moz-border-radius: 5px;
	border-radius: 5px;
	position: relative;
}
.status-bar .bar {
	background: #c54;
	width: 100%;
	height: 10px;
	position: relative;
	transition: width .5s linear;
}
.status-bar .change {
	background: rgba(255,255,255,0.6);
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	width: 0px;
	transition: width .5s linear;
}


3. Initialise the Sanity related story variables within your StoryInit passage.

In the original example there were three Health related story variables used by status-bar code:

  • $totalHealth - Used to indicate the maximum value of the status bar.
  • $health - Used to track the current value of the status bar.
  • $damage - Used to indicate how much the current value should lowered or raised by.
    Changing the $damage variable from zero to a possitive integer value would lower the current value by that amount, and changing the $damage variable from zero to a negitive integer value would raise the current value by that amount.

In your example you are tracking Sanity, so I would suggest initialising the following story variables.

  • $totalSanity - the maximum value.
  • $sanity - the current value.
  • $abuse - the value to change the current value by.
<<set $totalSanity to 100>>
<<set $sanity to $totalSanity>>

/* Delete the following $abuse story variable if you choose to use a temporary variable instead. */
<<set $abuse to 0>>

note: Because the $abuse variable's life-span may not need to extend past the life-span of the current Passage, which in turn means that it doesn't need to be persisted in History or a Save, a temporary variable could be used instead of a story variable. In this case delete the line initialising the $abuse variable from the above example.

4. Using the new <<statusbar>> macro in your project's StoryCaption special passage.
note: Only use one of the following two examples in your project, each status-bar displayed on the current page must have it's own UNIQUE identifier.

/* Example using a $abuse Story variable. */
<<statusbar "sanity-bar" "$totalSanity" "$sanity" "$abuse">>

/* Example using a _abuse Temporary variable. */
<<statusbar "sanity-bar" "$totalSanity" "$sanity" "_abuse">>


5. Changing the current value of the Sanity status bar.

Setting the value of the $abuse Story variable (or the _abuse Temporary variable is using the temporary variable based example) to a non zero interger value within the contents of the current Passage will cause the status-bar to change.
eg. Each of the following will lower the current value of the $sanity Story variable, which you use in your project depends on if you chose to.use an $abuse Story variable or an _abuse Temporary variable in points 3 & 4.

/* If you decided to use a $abuse Story variable with <<statusbar>>. */
<<set $abuse to 10>>

/* If you decided to use a )abuse Temporary variable with <<statusbar>>. */
<<set _abuse to 10>>


6. [optional] Individually styling multiple status bars.

The new <<statusbar>> macro can be used to create multiple status-bars as long as each one has it's own UNIQUE identifier, and that identifier can be used to customise the CSS styling for a specific status-bar.

eg. Assuming you have initilised the following variables within your StoryInit.

<<set $totalSanity to 100>>
<<set $sanity to $totalSanity>>

<<set $totalHealth to 100>>
<<set $health to $totalHealth>>

... and you have defined the following status-bars within your StoryCaption.

Sanity: <<statusbar "sanity-bar" "$totalSanity" "$sanity" "_abuse">>
Health: <<statusbar "health-bar" "$totalHealth" "$health" "_damage">>

... then you can use CSS like the following within your Story Stylesheet area to customise the Heath related status-bar to have a green bar.

#health-bar .bar {
	background: green;
}

edited [23-aug-2018]: Added a warning about the minimum SugarCube 2 version required by this answer.

by (210 points)
Oh man, thank you! That is such a great breakdown! One problem though: I put everything into Javascript, but the error message:

Error: cannot execute macro <<statusbar>>: State.getVar is not a function

I changed all the variables in the example to coincide with the variables I am using (

var sanitybar  = this.args[0],
            $totalSanity     = State.getVar(this.args[1]),
            $sanity     = State.getVar(this.args[2]),
            $abuse      = State.getVar(this.args[3]),
            barWidth    = (current / maximum) * 100,
            changeWidth = 0,
            delayReset  = false;

), but it still registers State.getVar as a non-function.
by (159k points)

Sorry.

The reason you are recieving that error is because I inadvertently used a feature that isn't in the v2.21.0 release of SugarCube 2 that is included with the current (v2.2.1) release of the Twine 2 application. I have now added a warning about this to my previous answer.

The best solution (for both of us) would be for you to update the version of SugarCube 2 you are using to the latest release, because that would both solve the error message as well as give you access to all the new features & bug fixes of the story format.

I will try to create a SugarCube v2.21.0 compatible version of this answer shortly.

by (380 points)

Thanks for this code greyelf, it's made implementing healthbars and such very easy.

I have a couple of questions though. Firstly I am trying to implement a colour change of the bar when it is decreasing in value, for the time it is decreasing. I would like it to slowly change colour and then change in width before returning to it's original colour. 

.status-bar .bar {
	background: #c54;
	width: 100%;
	height: 11px;
	position: relative;
	transition: width .5s linear, background-color .5s ease-out;
}
/* Handle any required change to the current value. */
if (change != 0) {
	changeWidth = (change / current) * 100;
	current     -= change;
	barWidth    = (current / maximum) * 100;
    
    State.setVar(this.args[2], current);
	State.setVar(this.args[3], 0);
	delayReset = true;
}

if (change > 0) {
	$bar
	.css('background', "white")
}

/* Apply the change visual effect if needed. */
if (delayReset) {
	setTimeout(function(){
		$change.css({'width': '0'});
		$bar.css('width', barWidth + "%");
	}, 500);
}

I added a transition for colour changes in CSS and changed the javascript to include an if clause that changes colour. However the colour instantly changes rather than fading and I am not sure why.

 

I was also wondering what the purpose of the css for the status_bar.change is. Deleting to not effect the performance of the macro.

...