0 votes
by (230 points)

Hi, I tried to use the importScripts() function of sugarCube but I can't make it work.
I need some external libraries to make my p5 sketch work, but for now it only works when I copy past the full content of the lib in the story's javascript which isn't very convenient.
What should I do ?
Here is my story javascript content :

importScripts(
  "https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
  "https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
  "https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"
);

setup.mySketch = function (sketch) {
  sketch.setup = function() {
    sketch.createCanvas(200, 200);
  };

  sketch.draw = function() {
    sketch.background(100+sketch.sin(sketch.millis()*0.005)*60);
    if(sketch.mouseIsPressed){
      Engine.play("entrance");
    }
  };
};

Here is a passage :

!!Below you should see a basic p5js sketch.
<div id="p5sketch"></div>
<<script>>
$(document).on(':passageend', function () {
	new p5(setup.mySketch, 'p5sketch');
});
<</script>>
<div id="link"></div>

Thanks for your help !

 

1 Answer

+1 vote
by (159k points)

There are a couple of issues with your example.

1. You're trying to load the P5 related libraries concurrently instead of sequentially.

Because you're loading the libraries concurrently it is possible for the code of the 2nd and 3rd libraries to be executed before the 1st, and that situation will cause JavaScript errors to be thrown which you can see if you look in the Console of your web-browser.

importScripts([
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"

]);


2. The :passageend event can occur before the P5 related libraries have been successfully executed.

It takes time for the remote libraries to be downloaded and executed, which means that if your example TwineScript is within your launch (Start) passage then it's :passageend event can occur before that execution has finished.

If you plan to use the P5 library at startup then you will need to monitor the Promise returned by the importScripts() function to determine when the loading and execution has finished. There are a number of ways you can do this. the following example uses a variable on the setup special object to do this,

setup.p5promise = importScripts([
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"

]);

You can now use that setup.p5promise variable within your startup related code to delay your p5sketch related initialisation code like so.

<div id="p5sketch"></div>
<div id="link"></div>
<<script>>
	$(document).on(':passageend', function () {
		setup.p5promise.then(function () {
			new p5(setup.mySketch, 'p5sketch');
		});
	});
<</script>>

 

by (63.1k points)

You could also use the LoadScreen API to lock the load screen (and game start up) until the promise is resolved. Docs: http://www.motoslave.net/sugarcube/2/docs/#loadscreen-api

An implementation might look like this: 

var lock = LoadScreen.lock();
importScripts([
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
	"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"

]).then( function () {
    LoadScreen.unlock(lock);
});

At that point, there's no real reason to use a promise, though, I suppose. 

by (159k points)

At that point, there's no real reason to use a promise, though, I suppose. 

You are using a Promise, the .then() part of your example, you're just not persisting that promise like my example did. I forgot about the ability to lock / delay the Loading screen.

by (63.1k points)
I just meant you could synchronously load it if you're going to block user interaction until it's loaded anyway.
by (68.6k points)

You could also use the LoadScreen API to lock the load screen (and game start up) […]

(emphasis mine)  That's not actually true at the moment. The load screen only delays presentation (i.e. it hides what's going on).  It does not delay any functional part of the start up process, so you'd still want to either use the Promise, as shown by greyelf, or load the libraries synchronously (if they need to be available before or during the starting passage, at least).

@colin: If this project will ever be available for local download, and possibly even if not, my suggestion would be to simply inline the libraries so they load synchronously.  The up-front cost of having to paste them into the Story JavaScript should be far less than having to worry about when, or if, they get loaded.

by (230 points)
Thanks for all your answers !

@greyelf: Your solution works fine, thank you

@TheMadExile: Thank you for the recommendation, I think I will switch back to copy/paste the librairies when the project will be done.

Also, I discover the Engine.play() function to make my script lead to another passage, but I noticed my p5 sketch still appear in the next passage, plus it duplicate itself. What should I do to avoid that ?
by (159k points)

still appear in the next passage, plus it duplicate itself. What should I do to avoid that ?

Without seeing your modified code it is difficult to know what is actually happening.

Does your p5sketch element exist in more that one passage? Is that element in a header / footer?, is that element in a StoryCaption passage, etc...

by (230 points)

Without seeing your modified code it is difficult to know what is actually happening.

Sorry about that

Here is my JS :

setup.mySketch = function (sketch) {

  sketch.setup = function() {
    sketch.createCanvas(200, 200);
  };

  sketch.draw = function() {
    sketch.background(100+sketch.sin(sketch.millis()*0.005)*60);
    if(sketch.mouseIsPressed){
      Engine.play("entrance");
    }
  };
};

setup.p5promise = importScripts([
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"
]);

And here is my first passage, the second one is empty

<div id="p5sketch"></div>
<div id="link"></div>
<<script>>
	$(document).on(':passageend', function () {
		setup.p5promise.then(function () {
			new p5(setup.mySketch, 'p5sketch');
		});
	});
<</script>>

Thanks for your help !

by (68.6k points)

You should switch from using <jQuery>.on() to <jQuery>.one() when setting up the event handler.  Doing so will make the handler single-use.

 

FIND:

	$(document).on(':passageend', function () {

REPLACE WITH:

	$(document).one(':passageend', function () {

 

by (230 points)

Thanks ! The sketch is no longer displaying in the second passage but it still seems to read the line "Engine.play("entrance");" and refresh the page each time the user is clicking.

setup.mySketch = function (sketch) {

  sketch.setup = function() {
    sketch.createCanvas(200, 200);
  };

  sketch.draw = function() {
    sketch.background(100+sketch.sin(sketch.millis()*0.005)*60);
    if(sketch.mouseIsPressed){
      Engine.play("entrance");
    }
  };
};

setup.p5promise = importScripts([
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js"
]);
<div id="p5sketch"></div>
<div id="link"></div>
<<script>>
	$(document).one(':passageend', function () {
		setup.p5promise.then(function () {
			new p5(setup.mySketch, 'p5sketch');
		});
	});
<</script>>

 

by (159k points)

If you add temporarily change your sketch.draw() function to the following.. 

	sketch.draw = function() {
		console.log('draw: mouseIsPressed: ' + sketch.mouseIsPressed);
		sketch.background(100 + sketch.sin(sketch.millis() * 0.005) * 60);
		if(sketch.mouseIsPressed){
			Engine.play("entrance");
		}
	};

and then view your web-browser's Console while running your HTML file you will notice two things:
1. The sketch's draw() function keeps getting called even when the sketch is no long visible.
2. The P5 object is monitoring ALL click events on the page, including ones on the left side-bar.

To fix the first issue you need to call the P5 remove() function which will stop and clear up that P5 object. The following example demonstrates calling the function and also defines a mousePressed() function instead of checking the mouseIsPressed state.

setup.mySketch = function (sketch) {

	sketch.setup = function() {
		sketch.createCanvas(200, 200);
	};

	sketch.draw = function() {
		sketch.background(100 + sketch.sin(sketch.millis() * 0.005) * 60);
	};

	sketch.mousePressed = function () {
		sketch.remove();
		Engine.play("entrance");
	}
};


If you want to limit the P5 object to monitoring only the click events on the sketch itself then you need to associate the above mousePressed() function code to the canvas itself. The following example demonstrates how to do this.

setup.mySketch = function (sketch) {

	sketch.setup = function() {
		var canvas = sketch.createCanvas(200, 200);
		canvas.mousePressed(onClick);
	};

	sketch.draw = function() {
		sketch.background(100 + sketch.sin(sketch.millis() * 0.005) * 60);
	};

	function onClick() {
		sketch.remove();
		Engine.play("entrance");
	};
};

 

by (230 points)
Thank you so much greygef ! It was so simple I didn't think of that, simply deleting the sketch via p5. Now I think I'm ready to make games that combine twine and p5, so many possibilities !

I have a last question if you don't mind. Let's say I have a mini game made in p5 in my story, and I want it to appear in different passages but with few changes. What could be the easiest way to do it ? Is there a way to load the same script with some specific parameters ?
...