Hey,
I’m using Twine 2.3.5, running Sugarcube 2.33.2. I have combined and included two P5.js sketches in my game’s JavaScript file. One is an animation and the other a graphic that maps onto image files stored on a server. I call them in various passages, of which I have around 50 so far - I need to include another 30, probably.
Everything works fine and they run beautifully when I test them. But when I play my story from the beginning, all graphics and text starts to lag around the seventh passage before getting progressively worse.
Here is my js code:
//////////sets up mifir land sketch and Uma people sketch/////////////
window.setup = window.setup || {};
window.setup.p5Loaded = false;
//window.setup.dotGUILoaded = false;
(function(){
var p5 = document.createElement("script");
// var dotGUI = document.createElement("script");
p5.type ="text/javascript";
// dotGUI.type = "text/javascript";
p5.src = "https://cdn.jsdelivr.net/npm/p5@0.8.0/lib/p5.js";
// dotGUI.src = "https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js";
p5.onload = function() {
window.setup.p5Loaded = true;
};
/*dotGUI.onload = function() {
window.setup.dotGUILoaded = true;
};*/
document.head.appendChild(p5);
// document.head.appendChild(dotGUI);
}());
const UMA = function(imgPath){
return function(p){
//Variables
p.img;
p.segments = [];
p.segmentLength;
p.segmentSpacing;
p.numSegments = 40;
p.segmentResolution = 400;
p.heightScale = 150;
p.fadeIn = 0;
p.speed = 0.51;
p.mp = false;
p.mouseIsDown = false;
p.loaded = false;
p.tt = 0;
p.glitchFreq = 0.5;
p.now = 0;
p.lastTime = 0;
p.id = 0;
p.preload = function(){
p.loadImage(imgPath, function(_img) {
p.img = _img;
p.img.loadPixels();
p.loaded = true;
p.segmentSpacing = (p.img.height / p.numSegments);
p.segmentLength = p.img.width;
for (let i = 0; i < p.numSegments; i++) {
p.segments.push(new p.Segment({
pos: { x: 0, y: i },
len: p.segmentLength
}));
}
});
}
p.setup = function(){
p.createCanvas(p.windowWidth*2, p.windowHeight/1.5);
}
p.update = function(dt){
p.segments.forEach(
s => {
s.update(dt)
}
);
}
p.mousePressed = function(){
p.mouseIsDown = true;
p.glitchFreq = 0.3;
}
p.mouseReleased = ()=>{
p.mouseIsDown = false;
p.glitchFreq = 0.5;
}
p.draw = function(){
if(!p.loaded)
return;
let dt = (p.millis() - p.lastTime) / 1000;
p.lastTime = p.millis();
p.tt += dt;
p.update(dt);
p.fill(0, 250);
p.noStroke();
p.rect(0,0, p.windowWidth, p.windowHeight);
let gridHeight = p.numSegments * p.segmentSpacing;
p.push();
p.heightScale = 60 + (p.sin(p.millis()/1000) / p.TAU) * 50;
p.translate(p.windowWidth / 2 - p.img.width, p.windowHeight / 2 - p.img.height / 2 - 100);
p.scale(2, 1.25);
if(p.mouseIsDown){
p.tint(156, 190, 48);
p.image(p.img, 0, 0);
}
else{
p.tint(255);
}
let trans = false;
let ss = 0;
p.segments.forEach(s => {
ss++;
let n = 0;
if (ss > 30) {
n = p.noise(p.millis() / 200);
}
s.draw();
});
if (trans) {
p.pop();
}
p.pop();
}
p.Segment = class Segment {
constructor(cfg) {
Object.assign(this, cfg);
this.t = 0;
this.dist = 0;
this.id = p.id++;
this.nextNoise = 0;
// [x,y, x,y, ...]
// add 2 for a point starting at the very start of the viewport and one of the end.
this.vertices = new Float32Array(p.segmentResolution * 2 + 2);
this.update(0);
}
update(dt) {
this.t += dt * 1;
p.fadeIn += dt * .008;
p.fadeIn = p.constrain(p.fadeIn, 0, 1);
let xSpacing = this.len / p.segmentResolution;
this.pos.y -= dt * p.speed;
this.dist += dt * p.speed;
//if (this.pos.y > img.height / segmentSpacing) {
// this.pos.y -= img.height / segmentSpacing;
//}
if (this.pos.y < 0) {
this.pos.y += p.img.height / p.segmentSpacing;
}
for (let i = 0; i < this.vertices.length; i += 2) {
let x = (i / 2) * xSpacing;
let y = this.pos.y * p.segmentSpacing;
if (p.mp) {
let d = dist(x, y, (p.mouseX - p.windowWidth / 2 + p.windowWidth / 4 + 50) / 2, (p.mouseY - p.windowHeight / 2 + p.windowHeight / 4 + 250) / 2);
if (d < 40) {
let a = (8 / d) * 8;
y += (a > 50) ? 50 : a;
}
}
let col = p.img.get(x, y);
let intensity = col[0] / 255;
this.vertices[i + 0] = x;
this.vertices[i + 1] = y - (intensity) * p.heightScale;
}
}
draw() {
p.strokeWeight(1);
let waves = (p.sin(this.pos.y / 2.0 + this.t ) / p.PI) + 0.5;
let vignette = p.sin(this.pos.y * p.segmentSpacing / p.img.height * p.PI);
p.stroke(255, 255 * p.fadeIn * waves * vignette + 50 * vignette);
// fill(0);
p.noFill();
p.beginShape();
for (let i = 0; i < this.vertices.length; i += 2) {
if (i === 0) {
p.vertex(-1000, this.vertices[i + 1]);
} else if (i + 2 === this.vertices.length) {
p.vertex(10000, this.vertices[i + 1]);
} else {
p.vertex(this.vertices[i + 0], this.vertices[i + 1]);
if (this.id < p.numSegments - 1) {
let s = p.segments[p.id + 1];
p.vertex(this.vertices[i + 0], this.vertices[i + 1]);
}
}
}
p.endShape();
}
}
}
}
////////////////////////////Mifir sketch////////////////////////////////
const Mifir = function(p){
p.scale = 20;
p.cols;
p.rows;
p.w = 1400;
p.h = 1000;
p.flightPos = 0;
// let flightSpeed = 0.08;
// let noiseDelta = 0.16;
p.terrain = [];
// let terrainHeight = 112;
p.Controls = function() {
this.flightSpeed = 0.04;
this.noiseDelta = 0.16;
this.terrainHeight = 100;
};
p.controls = new p.Controls();
p.setup =function() {
// createCanvas(displayWidth, displayHeight, WEBGL);
p.createCanvas(1400, 720,p.WEBGL);
//p.createCanvas(1280, 720,p.WEBGL);
/* let gui = new dat.GUI({width: 295});
gui.close();
gui.add(p.controls, 'flightSpeed', 0, 0.4).name("Flight speed").step(0.02);
gui.add(p.controls, 'noiseDelta', 0.05, 0.4).name("Noise delta").step(0.01);
gui.add(p.controls, 'terrainHeight', 0, 200).name("Terrain height").step(1); */
p.cols = p.w / p.scale;
p.rows = p.h / p.scale;
for (let x = 0; x < p.cols; ++x) {
p.terrain[x] = [];
}
}
p.draw = function() {
p.flightPos -= p.controls.flightSpeed;
p.shiftNoiseSpace();
p.background(0);
p.stroke(255);
p.noFill();
p.rotateX(p.PI / 3);
p.translate((-p.w / 2) + 1, (-p.h / 2) + 30);
for (let y = 0; y < p.rows - 1; ++y) {
p.beginShape(p.TRIANGLE_STRIP);
for (let x = 0; x < p.cols; ++x) {
p.vertex(x * p.scale, y * p.scale, p.terrain[x][y]);
p.vertex(x * p.scale, (y + 1) * p.scale, p.terrain[x][y + 1]);
}
p.endShape();
}
}
p.shiftNoiseSpace = function(){
let yOffset = p.flightPos;
for (let y = 0; y < p.rows; ++y) {
let xOffset = 0;
for (let x = 0; x < p.cols; ++x) {
p.terrain[x][y] = p.map(p.noise(xOffset, yOffset), 0, 1, -p.controls.terrainHeight, p.controls.terrainHeight);
xOffset += p.controls.noiseDelta;
}
yOffset += p.controls.noiseDelta;
}
}
}
function GenerateSketch(sketch,canvasID)
{
let newSketch = new p5(sketch,canvasID);
console.log("Sketch created with name: " + canvasID);
return newSketch;
}
function GetSketch(ImgPath, SketchType)
{
switch (SketchType) {
case "UMA":
return UMA(ImgPath);
break;
case "Mifir":
return Mifir;
}
}
function CheckDependeciesStatus()
{
if(!window.setup.p5Loaded || typeof p5 !== 'function' /*|| !window.setup.dotGUILoaded || typeof dat.GUI !== 'function'*/)
return false;
else
return true;
}
let ProcessDependecies = function(imgPath, canvasID, sketchType)
{
console.log("Starting to create sketch!");
if(!CheckDependeciesStatus())
{
console.log("Dependecies are not loaded, waiting for them to laod...");
let waitForDependecies = setInterval(() => {
if(CheckDependeciesStatus())
{
console.log("Dependecies are loaded, creating sketch");
clearInterval(waitForDependecies);
let sketch = GetSketch(imgPath,sketchType);
return GenerateSketch(sketch,canvasID);
}
}, 100);
}
else{
let sketch = GetSketch(imgPath,sketchType);
return GenerateSketch(sketch,canvasID);
}
}
window.setup.CreateSketch = {
UMA: (imgPath, canvasID) => ProcessDependecies(imgPath,canvasID,"UMA"),
Mifir: (canvasID) => ProcessDependecies(null,canvasID,"Mifir"),
}
Here is how I call the Uma sketch in a passage:
<div id="Homeland"></div><script>window.setup.CreateSketch.UMA("http://localhost:3000/public/landscape.jpg", "Homeland");</script>
Note: Homeland is the passage name
And here is how I call the Mifir sketch:
<div id="Pathway1"></div>
<script>
console.log(window.setup);
window.setup.CreateSketch.Mifir("Pathway1");
</script>
Again: Pathway1 is the passage name
After reading this forum post, I put the following into a passage with a script tag:
config.disableHistoryTracking = true;
But this made no difference.
Perhaps there is a more economic way of calling my sketches? Any help and advice is appreciated, thanks.