[Twine 2][Sugarcube 2] how to deal with variables there changes by time

Please specify version and format if asking for help, or apply optional tags above:
Twine Version: 2
Story Format: Sugarcube 2

Hello, I want to add building construction options to my game. I already know how to do this. However, I need a solution to create resources such as stone or tree trunks. These resources should be collected over the past time, such as in Anno games, for example. Is there a way to change variables in a passage every second without changing the passage? (how a global variable that is changed by an inaccessible passage in the background?)

You can use the setInterval() function for that. For example:

<<set $varName = 0>>
<<set $interval = setInterval(function () {
	State.variables.varName += 1;
	$("#val").empty().wiki("$varName");
}, 1000)>>
<span id="val">0</span>

[[Next Passage|Passage Name][clearInterval($interval)]]

So that adds 1 to the $varName variable every second (1000 milliseconds = 1 second), until the clearInterval() function is called when you click the link to go to the next passage. Make sure you stop the interval using clearInterval() at some point, otherwise the interval will keep going until the game is closed. (See also the SugarCube documentation on State.variables, .wiki(), and link markup.)

So, you may be thinking, “Oh! That’s great. That looks fairly easy to implement.” Well, unfortunately it’s a bit more complex than that. You see, if you load a save while that interval is going, the interval code will keep going in the game you just loaded. In the above example, that would mean that loading a save in that passage would cause the counter to count up one additional time per second, for each time you loaded the save. So the timer would tick twice as fast if they loaded a save there once.

To prevent that problem you have to also add something like the following code to your JavaScript section:

Config.saves.onLoad = function (save) {
	if (State.variables.interval !== undefined) {
		clearInterval(State.variables.interval);
	}
};

That way, if $interval is set to something when you load a save, then that code will try to clear the interval before loading the saved game. (See the Config.saves.onLoad callback for details.) Fortunately, you don’t need to do the same thing for the “Restart”, since that should clear everything, including that interval.

Also, I’d recommend stopping the interval whenever you leave a passage, and if you need it running in the next, then start it again in that passage. The reason why is that, if you start an interval in “Passage A” and leave it running when going to “Passage B”, it will work. However, if the player then saves the game in “Passage B” and later reloads that save, the interval will no longer be running, since loading a save won’t automatically restart the interval. Thus it’s better to start the interval in any passage which needs it, that way it will work when you load a game saved in that passage.

Still, be very careful with using intervals. If you don’t clean them up when you’re done with them, and accumulate a bunch of intervals all running at the same time, then at best your code may slow down, at worst the results could be pretty erratic or even crash the game. Additionally, I’d recommend reading the setInterval() Usage Notes. That said, when used with care, setInterval() can be pretty useful.

Hope that helps! :smiley:

thanks so muich :smiley: thats very helpful for me :smiley:

Alternatively. As long as the gathering is based on time in passage, you could use the time() function to calculate the amount gathered.

E.g.,

[[Next Passage|Passage Name][$amount to Math.trunc(time() / 1000)]]

You don’t get the display that way, but it is less complicated.