0 votes
by (200 points)
closed by

Hello everyone, I'm currently trying to get a really simple game running and would like to use a JS class based approach to encapsulate variables and functions.

This is my barebones javascript code I'm trying to get running before I expand on it. It's the only existing part to keep it simple.

class CPlayer {
	constructor() {
		this.name= "testplayer";
		this.age= "20";
		return "random test string";
setup.CPlayer = CPlayer;

This is the single .tw file I use (I omitted storytitle and storysettings as they're irrelevant for the problem). By invoking it like this, I hope to receive a gobally available $player token I can then use everywhere to set/get it's member variables and to use it's functions.

:: Start
<<if ndef $player>><<set $player = new setup.CPlayer>><</if>>
<<goto TestPage>>




My problem is that, when I simply open the compiled html file, everything works perfectly fine, but once I either do a manual browser refresh (F5 or derivatives) or save and then load the game. I'm greeted with

Error: <<->>: bad evaluation: State.variables.player.test is not a function

I only recently started with twine/tweego/sugarcube and I'm certainly not an expert when using JS. I'm pretty sure I've read something about not all objects being supported by twine and that they need to implement their own .clone() function to be able to be saved/restored, but I'm at a loss at how to achieve that, as the documentation isn't really helping.

I'm not exactly sure if the .clone() approach would be the solution to this problem, or if my problem comes from something else I'm missing.

closed with the note: Has been answered

1 Answer

0 votes
by (44.7k points)
selected by
Best answer

SugarCube v1 and v2 do not support storing functions on story variables or temporary variables.  See "Supported Types".

For storing functions it's best to put them on either the standard window object or the SugarCube setup object in your JavaScript section.  For example, in your JavaScript section you could have:

window.wFunct = function (param) {
	return param + 1;

setup.sFunct = function (param) {
	return param + 1;

Then you could call those functions in a passage like this:

<<print wFunct(1)>>

<<print setup.sFunct(2)>>

You can see the advantage of using the window object is that you don't have to type "window." in front of the function name.  However, unlike the setup object, there is a small chance that functions on the window object may collide with JavaScript plugins the user is using if they happen to have the same function name, which could affect the functioning of your game.

It should also be noted that data on the window and setup objects do not get saved with the game normally, so any values or objects stored on them should be set on startup in the JavaScript section or in a StoryInit passage, so that they will get restored if you reload the page.  Furthermore, anything on those objects should not change during the course of the game, since those changes won't get saved.

Finally, if you need to access story variables from JavaScript in your functions you can use State.variables, and for temporary variables you'd use State.temporary.  So, for example, you would access $test in JavaScript by using State.variables.test instead.

Hope that helps!  :-)

by (200 points)

SugarCube v1 and v2 do not support storing functions on story variables or temporary variables.  See "Supported Types".

 That's what I was afraid of and what I already somehow expected, after messing around with a large amount of classes, functions, and simple objects in sugarcube.

While the possibility of using globally defined functions using the setup and window classes is there, I'd consider this a stopgap solution if I had to implement that for all of the classes I'd need. Especially considering that I'd love to have classes with their own version of a function instead of one function for all classes. Knowing c/c++, these pleasentries are hard to ignore.

I feel the solution to my problem lies in the .clone() and .toJSON() functions mentioned in the documentation, but I'm unable to find any real information on how to actually implement them to allow my complex objects would work without having to rely on workarounds.

by (68.6k points)

SEE: Non-generic object types (a.k.a. classes) within the documentation's tips guide.

by (200 points)

SEE: Non-generic object types (a.k.a. classes) within the documentation's tips guide.

Oh, why did I not see that earlier? While I still fail to actually implement this correctly, it looks like this could be the solution to my problem.

by (159k points)

> While I still fail to actually implement this correctly...

The following is one possible way to implement you original example with the required clone() and toJSON() functions.

setup.CPlayer = class {
	constructor(options = {}) {
		/* Public fields, with defaults. */
		this.name = "testplayer";
		this.age = "20";

		/* Override default values as needed. */
		Object.keys(options).forEach((key) => {
			this[key] = clone(options[key]);
		}, this);
	test() {
		return "random test string";
	clone() {
		// Return a new instance containing our own data.
		return new setup.CPlayer(this);
	toJSON() {
		/* Return a code string that will create a new instance containing our own data. */
		var data = {};
		Object.keys(this).forEach((key) => {
			data[key] = clone(this[key]);
		}, this);

		return JSON.reviveWrapper('new setup.CPlayer($ReviveData$)', data);

You can now call the constructor either:
a. without arguments

<<set $player = new setup.CPlayer>>

b. with 1 or more arguments

<<set $player = new setup.CPlayer({name: 'banana'})>>

WANRING: Some of web-browsers currently being used by the general public may not include support of ES6 features.

by (200 points)
Thanks, everyone. I finally got it to work after finding, I simply introduced a typo when I tried to implement the .clone and .toJSON functions in my class - it didn't throw an error, as it should have. Everything is working alright now and I can continue on building my little game.