0 votes
by (8.9k points)

I'm using a system of checkboxes to let players select the skills they want, like so:

<<checkbox "$skills.sword" false true>> Sword
<<checkbox "$skills.bow" false true>> Bow
<<checkbox "$skills.magic" false true>> Magic

When the player picks a skill, I'd like the change to be immediately reflected in the passage.  Is there a way to make the passage reload when the player changes a checkbox?

1 Answer

+1 vote
by (44.7k points)
edited by
 
Best answer

Yes, you can do this.

First, you want to change your checkboxes to reflect the current value of those variables.  So that code would look like this:

<<checkbox "$skills.sword" false true `$skills.sword ? 'checked' : ''`>> Sword
<<checkbox "$skills.bow" false true `$skills.bow ? 'checked' : ''`>> Bow
<<checkbox "$skills.magic" false true `$skills.magic ? 'checked' : ''`>> Magic

The backticks/backquotes (the accent mark from the tilde ~ key) cause what is inside to be evaluated, and the result from that will be used.

The "conditional ? t : f" format basically translates as: "if the conditional is truthy, return t, otherwise return f."  So if "$skills.sword" is true, then the "checked" parameter will be set.

The next thing you need to know is that a SugarCube checkbox gets its ID from the variable name.  For story variables (the ones that start with "$") that will be "checkbox-" followed by the lowercase version of the variable name with any dots removed.  For temporary variables (the ones that start with "_") it's almost the same, except there is a second "-".

This means that a checkbox that sets "$skills.bow" will have an ID of "checkbox-skillsbow", and a checkbox that sets "_VariableX" will have an ID of "checkbox--variablex".

Now that you know the ID, you can use that to add a listener to the checkbox.  jQuery makes this really easy, though it's a bit trickier because you're using Twine.  The elements on a page don't always exist in Twine, so you have to wait until they do to attach the event handler.  This should done in the :passagerender event, like this:

$(document).on(':passagerender', function (ev) {
	if (passage() == "Start") {
		$(ev.content).find("#checkbox-skillssword").on('propertychange change', function() { Engine.play(passage()); } );
		$(ev.content).find("#checkbox-skillsbow").on('propertychange change', function() { Engine.play(passage()); } );
		$(ev.content).find("#checkbox-skillsmagic").on('propertychange change', function() { Engine.play(passage()); } );
	}
});

(That code belongs in the JavaScript section.)

This first checks to see if it's the correct passage, using the passage() function to determine what passage we're currently in.  You will need to change "Start" to the name of your passage where this is needed.

Then, if it's the correct passage, it uses jQuery to take the content returned to this function ("ev.content"), find the checkboxes within that content, and add a function to each of them that triggers on "input", "propertychange", or "change" events (which should work for detecting changes on most input elements).  That function uses the Engine.play() function to play the current passage again, which will update the passage and add it to the history.  You can add ", true" to the Engine.play() parameters if you don't want that to add to the history.

If those are the only checkboxes in the passage, you can simplify that code by simply connecting to all checkboxes like this:

$(document).on(':passagerender', function (ev) {
	if (passage() == "Start") {
		$(ev.content).find("[type=checkbox]").on('propertychange change', function() { Engine.play(passage()); } );
	}
});

If you have any questions about how that works, feel free to ask.

Hope that helps! smiley

(NOTE: Edited to remove "input" event from listener.)

by (159k points)

@HiEv (and @Charlie)

WARNING: Including input in the list of checkbox events to listen for results in the value of the related story variable not being changed because the Passage Transition occurs before the current State is updated, or at least it does when using Chrome on Windows.

@Charlie

Simply removing input from the list fixes this issue, so HiEv first example would now be..

$(document).on(':passagerender', function (ev) {
	if (passage() == "Start") {
		$(ev.content).find("#checkbox-skillssword").on('propertychange change', function() { Engine.play(passage()); } );
		$(ev.content).find("#checkbox-skillsbow").on('propertychange change', function() { Engine.play(passage()); } );
		$(ev.content).find("#checkbox-skillsmagic").on('propertychange change', function() { Engine.play(passage()); } );
	}
});

... and their second example would now be..

$(document).on(':passagerender', function (ev) {
	if (passage() == "Start") {
		$(ev.content).find("[type=checkbox]").on('propertychange change', function() { Engine.play(passage()); } );
	}
});


One possible enhancement I would suggest thinking about is changing the Engine.play() calls so that they don't update History, because currently each time a checkbox is changed it adds another copy of the current passage to the Undo stack which seems unnecessary if all you want is for the current page to be updated. Simply change each usage to the following..

Engine.play(passage(), true);


Another possible enhancement to consider, if you only want monitor check-boxes in a single passage, is to localise the setup of the monitoring within the passage containing the check-boxes instead of doing it within the Story Javascript area.

This can be done by simply adding code like the following to that passage if you were using HiEv's 1st example...

<<script>>
$(document).one(':passagerender', function (ev) {
	$(ev.content).find("#checkbox-skillssword").on('propertychange change', function() { Engine.play(passage(), true); } );
	$(ev.content).find("#checkbox-skillsbow").on('propertychange change', function() { Engine.play(passage(), true); } );
	$(ev.content).find("#checkbox-skillsmagic").on('propertychange change', function() { Engine.play(passage(), true); } );
});
<</script>>

... or the following if you were using their 2nd example...

<<script>>
$(document).one(':passagerender', function (ev) {
	$(ev.content).find("[type=checkbox]").on('propertychange change', function() { Engine.play(passage(), true); } );
});
<</script>>

note: The original .on() jQuery function used in both of HiEv's examples has been replaced with the .one() function.

by (8.9k points)

Thank you both, that works perfectly!  I would never have figured that out on my own.

This is really great code and it will significantly improve the user experience of my game.

...