You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
836 lines
23 KiB
836 lines
23 KiB
4 years ago
|
export class Charm {
|
||
|
constructor(renderingEngine = PIXI) {
|
||
|
|
||
|
if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using charm.js");
|
||
|
|
||
|
//Find out which rendering engine is being used (the default is Pixi)
|
||
|
this.renderer = "";
|
||
|
|
||
|
//If the `renderingEngine` is Pixi, set up Pixi object aliases
|
||
|
if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
|
||
|
this.renderer = "pixi";
|
||
|
}
|
||
|
|
||
|
|
||
|
//An array to store the global tweens
|
||
|
this.globalTweens = [];
|
||
|
|
||
|
//An object that stores all the easing formulas
|
||
|
this.easingFormulas = {
|
||
|
|
||
|
//Linear
|
||
|
linear(x) {
|
||
|
return x;
|
||
|
},
|
||
|
|
||
|
//Smoothstep
|
||
|
smoothstep(x) {
|
||
|
return x * x * (3 - 2 * x);
|
||
|
},
|
||
|
smoothstepSquared(x) {
|
||
|
return Math.pow((x * x * (3 - 2 * x)), 2);
|
||
|
},
|
||
|
smoothstepCubed(x) {
|
||
|
return Math.pow((x * x * (3 - 2 * x)), 3);
|
||
|
},
|
||
|
|
||
|
//Acceleration
|
||
|
acceleration(x) {
|
||
|
return x * x;
|
||
|
},
|
||
|
accelerationCubed(x) {
|
||
|
return Math.pow(x * x, 3);
|
||
|
},
|
||
|
|
||
|
//Deceleration
|
||
|
deceleration(x) {
|
||
|
return 1 - Math.pow(1 - x, 2);
|
||
|
},
|
||
|
decelerationCubed(x) {
|
||
|
return 1 - Math.pow(1 - x, 3);
|
||
|
},
|
||
|
|
||
|
//Sine
|
||
|
sine(x) {
|
||
|
return Math.sin(x * Math.PI / 2);
|
||
|
},
|
||
|
sineSquared(x) {
|
||
|
return Math.pow(Math.sin(x * Math.PI / 2), 2);
|
||
|
},
|
||
|
sineCubed(x) {
|
||
|
return Math.pow(Math.sin(x * Math.PI / 2), 2);
|
||
|
},
|
||
|
inverseSine(x) {
|
||
|
return 1 - Math.sin((1 - x) * Math.PI / 2);
|
||
|
},
|
||
|
inverseSineSquared(x) {
|
||
|
return 1 - Math.pow(Math.sin((1 - x) * Math.PI / 2), 2);
|
||
|
},
|
||
|
inverseSineCubed(x) {
|
||
|
return 1 - Math.pow(Math.sin((1 - x) * Math.PI / 2), 3);
|
||
|
},
|
||
|
|
||
|
//Spline
|
||
|
spline(t, p0, p1, p2, p3) {
|
||
|
return 0.5 * (
|
||
|
(2 * p1) +
|
||
|
(-p0 + p2) * t +
|
||
|
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t +
|
||
|
(-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t
|
||
|
);
|
||
|
},
|
||
|
|
||
|
//Bezier curve
|
||
|
cubicBezier(t, a, b, c, d) {
|
||
|
let t2 = t * t;
|
||
|
let t3 = t2 * t;
|
||
|
return a + (-a * 3 + t * (3 * a - a * t)) * t + (3 * b + t * (-6 * b + b * 3 * t)) * t + (c * 3 - c * 3 * t) * t2 + d * t3;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Add `scaleX` and `scaleY` properties to Pixi sprites
|
||
|
this._addScaleProperties = (sprite) => {
|
||
|
if (this.renderer === "pixi") {
|
||
|
if (!("scaleX" in sprite) && ("scale" in sprite) && ("x" in sprite.scale)) {
|
||
|
Object.defineProperty(
|
||
|
sprite,
|
||
|
"scaleX", {
|
||
|
get() {
|
||
|
return sprite.scale.x
|
||
|
},
|
||
|
set(value) {
|
||
|
sprite.scale.x = value
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
if (!("scaleY" in sprite) && ("scale" in sprite) && ("y" in sprite.scale)) {
|
||
|
Object.defineProperty(
|
||
|
sprite,
|
||
|
"scaleY", {
|
||
|
get() {
|
||
|
return sprite.scale.y
|
||
|
},
|
||
|
set(value) {
|
||
|
sprite.scale.y = value
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
//The low level `tweenProperty` function is used as the foundation
|
||
|
//for the the higher level tween methods.
|
||
|
tweenProperty(
|
||
|
sprite, //Sprite object
|
||
|
property, //String property
|
||
|
startValue, //Tween start value
|
||
|
endValue, //Tween end value
|
||
|
totalFrames, //Duration in frames
|
||
|
type = "smoothstep", //The easing type
|
||
|
yoyo = false, //Yoyo?
|
||
|
delayBeforeRepeat = 0 //Delay in frames before repeating
|
||
|
) {
|
||
|
|
||
|
//Create the tween object
|
||
|
let o = {};
|
||
|
|
||
|
//If the tween is a bounce type (a spline), set the
|
||
|
//start and end magnitude values
|
||
|
let typeArray = type.split(" ");
|
||
|
if (typeArray[0] === "bounce") {
|
||
|
o.startMagnitude = parseInt(typeArray[1]);
|
||
|
o.endMagnitude = parseInt(typeArray[2]);
|
||
|
}
|
||
|
|
||
|
//Use `o.start` to make a new tween using the current
|
||
|
//end point values
|
||
|
o.start = (startValue, endValue) => {
|
||
|
|
||
|
//Clone the start and end values so that any possible references to sprite
|
||
|
//properties are converted to ordinary numbers
|
||
|
o.startValue = JSON.parse(JSON.stringify(startValue));
|
||
|
o.endValue = JSON.parse(JSON.stringify(endValue));
|
||
|
o.playing = true;
|
||
|
o.totalFrames = totalFrames;
|
||
|
o.frameCounter = 0;
|
||
|
|
||
|
//Add the tween to the global `tweens` array. The `tweens` array is
|
||
|
//updated on each frame
|
||
|
this.globalTweens.push(o);
|
||
|
};
|
||
|
|
||
|
//Call `o.start` to start the tween
|
||
|
o.start(startValue, endValue);
|
||
|
|
||
|
//The `update` method will be called on each frame by the game loop.
|
||
|
//This is what makes the tween move
|
||
|
o.update = () => {
|
||
|
|
||
|
let time, curvedTime;
|
||
|
|
||
|
if (o.playing) {
|
||
|
|
||
|
//If the elapsed frames are less than the total frames,
|
||
|
//use the tweening formulas to move the sprite
|
||
|
if (o.frameCounter < o.totalFrames) {
|
||
|
|
||
|
//Find the normalized value
|
||
|
let normalizedTime = o.frameCounter / o.totalFrames;
|
||
|
|
||
|
//Select the correct easing function from the
|
||
|
//`ease` object’s library of easing functions
|
||
|
|
||
|
|
||
|
//If it's not a spline, use one of the ordinary easing functions
|
||
|
if (typeArray[0] !== "bounce") {
|
||
|
curvedTime = this.easingFormulas[type](normalizedTime);
|
||
|
}
|
||
|
|
||
|
//If it's a spline, use the `spline` function and apply the
|
||
|
//2 additional `type` array values as the spline's start and
|
||
|
//end points
|
||
|
else {
|
||
|
curvedTime = this.easingFormulas.spline(normalizedTime, o.startMagnitude, 0, 1, o.endMagnitude);
|
||
|
}
|
||
|
|
||
|
//Interpolate the sprite's property based on the curve
|
||
|
sprite[property] = (o.endValue * curvedTime) + (o.startValue * (1 - curvedTime));
|
||
|
|
||
|
o.frameCounter += 1;
|
||
|
}
|
||
|
|
||
|
//When the tween has finished playing, run the end tasks
|
||
|
else {
|
||
|
sprite[property] = o.endValue;
|
||
|
o.end();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//The `end` method will be called when the tween is finished
|
||
|
o.end = () => {
|
||
|
|
||
|
//Set `playing` to `false`
|
||
|
o.playing = false;
|
||
|
|
||
|
//Call the tween's `onComplete` method, if it's been assigned
|
||
|
if (o.onComplete) o.onComplete();
|
||
|
|
||
|
//Remove the tween from the `tweens` array
|
||
|
this.globalTweens.splice(this.globalTweens.indexOf(o), 1);
|
||
|
|
||
|
//If the tween's `yoyo` property is `true`, create a new tween
|
||
|
//using the same values, but use the current tween's `startValue`
|
||
|
//as the next tween's `endValue`
|
||
|
if (yoyo) {
|
||
|
this.wait(delayBeforeRepeat).then(() => {
|
||
|
o.start(o.endValue, o.startValue);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Pause and play methods
|
||
|
o.play = () => o.playing = true;
|
||
|
o.pause = () => o.playing = false;
|
||
|
|
||
|
//Return the tween object
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
//`makeTween` is a general low-level method for making complex tweens
|
||
|
//out of multiple `tweenProperty` functions. Its one argument,
|
||
|
//`tweensToAdd` is an array containing multiple `tweenProperty` calls
|
||
|
|
||
|
makeTween(tweensToAdd) {
|
||
|
|
||
|
//Create an object to manage the tweens
|
||
|
let o = {};
|
||
|
|
||
|
//Create a `tweens` array to store the new tweens
|
||
|
o.tweens = [];
|
||
|
|
||
|
//Make a new tween for each array
|
||
|
tweensToAdd.forEach(tweenPropertyArguments => {
|
||
|
|
||
|
//Use the tween property arguments to make a new tween
|
||
|
let newTween = this.tweenProperty(...tweenPropertyArguments);
|
||
|
|
||
|
//Push the new tween into this object's internal `tweens` array
|
||
|
o.tweens.push(newTween);
|
||
|
});
|
||
|
|
||
|
//Add a counter to keep track of the
|
||
|
//number of tweens that have completed their actions
|
||
|
let completionCounter = 0;
|
||
|
|
||
|
//`o.completed` will be called each time one of the tweens
|
||
|
//finishes
|
||
|
o.completed = () => {
|
||
|
|
||
|
//Add 1 to the `completionCounter`
|
||
|
completionCounter += 1;
|
||
|
|
||
|
//If all tweens have finished, call the user-defined `onComplete`
|
||
|
//method, if it's been assigned. Reset the `completionCounter`
|
||
|
if (completionCounter === o.tweens.length) {
|
||
|
if (o.onComplete) o.onComplete();
|
||
|
completionCounter = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Add `onComplete` methods to all tweens
|
||
|
o.tweens.forEach(tween => {
|
||
|
tween.onComplete = () => o.completed();
|
||
|
});
|
||
|
|
||
|
//Add pause and play methods to control all the tweens
|
||
|
o.pause = () => {
|
||
|
o.tweens.forEach(tween => {
|
||
|
tween.playing = false;
|
||
|
});
|
||
|
};
|
||
|
o.play = () => {
|
||
|
o.tweens.forEach(tween => {
|
||
|
tween.playing = true;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
//Return the tween object
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
/* High level tween methods */
|
||
|
|
||
|
//1. Simple tweens
|
||
|
|
||
|
//`fadeOut`
|
||
|
fadeOut(sprite, frames = 60) {
|
||
|
return this.tweenProperty(
|
||
|
sprite, "alpha", sprite.alpha, 0, frames, "sine"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//`fadeIn`
|
||
|
fadeIn(sprite, frames = 60) {
|
||
|
return this.tweenProperty(
|
||
|
sprite, "alpha", sprite.alpha, 1, frames, "sine"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//`pulse`
|
||
|
//Fades the sprite in and out at a steady rate.
|
||
|
//Set the `minAlpha` to something greater than 0 if you
|
||
|
//don't want the sprite to fade away completely
|
||
|
pulse(sprite, frames = 60, minAlpha = 0) {
|
||
|
return this.tweenProperty(
|
||
|
sprite, "alpha", sprite.alpha, minAlpha, frames, "smoothstep", true
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//2. Complex tweens
|
||
|
|
||
|
slide(
|
||
|
sprite, endX, endY,
|
||
|
frames = 60, type = "smoothstep", yoyo = false, delayBeforeRepeat = 0
|
||
|
) {
|
||
|
return this.makeTween([
|
||
|
|
||
|
//Create the x axis tween
|
||
|
[sprite, "x", sprite.x, endX, frames, type, yoyo, delayBeforeRepeat],
|
||
|
|
||
|
//Create the y axis tween
|
||
|
[sprite, "y", sprite.y, endY, frames, type, yoyo, delayBeforeRepeat]
|
||
|
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
breathe(
|
||
|
sprite, endScaleX = 0.8, endScaleY = 0.8,
|
||
|
frames = 60, yoyo = true, delayBeforeRepeat = 0
|
||
|
) {
|
||
|
|
||
|
//Add `scaleX` and `scaleY` properties to Pixi sprites
|
||
|
this._addScaleProperties(sprite);
|
||
|
|
||
|
return this.makeTween([
|
||
|
|
||
|
//Create the scaleX tween
|
||
|
[
|
||
|
sprite, "scaleX", sprite.scaleX, endScaleX,
|
||
|
frames, "smoothstepSquared", yoyo, delayBeforeRepeat
|
||
|
],
|
||
|
|
||
|
//Create the scaleY tween
|
||
|
[
|
||
|
sprite, "scaleY", sprite.scaleY, endScaleY,
|
||
|
frames, "smoothstepSquared", yoyo, delayBeforeRepeat
|
||
|
]
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
scale(sprite, endScaleX = 0.5, endScaleY = 0.5, frames = 60) {
|
||
|
|
||
|
//Add `scaleX` and `scaleY` properties to Pixi sprites
|
||
|
this._addScaleProperties(sprite);
|
||
|
|
||
|
return this.makeTween([
|
||
|
|
||
|
//Create the scaleX tween
|
||
|
[
|
||
|
sprite, "scaleX", sprite.scaleX, endScaleX,
|
||
|
frames, "smoothstep", false
|
||
|
],
|
||
|
|
||
|
//Create the scaleY tween
|
||
|
[
|
||
|
sprite, "scaleY", sprite.scaleY, endScaleY,
|
||
|
frames, "smoothstep", false
|
||
|
]
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
strobe(
|
||
|
sprite, scaleFactor = 1.3, startMagnitude = 10, endMagnitude = 20,
|
||
|
frames = 10, yoyo = true, delayBeforeRepeat = 0
|
||
|
) {
|
||
|
|
||
|
let bounce = "bounce " + startMagnitude + " " + endMagnitude;
|
||
|
|
||
|
//Add `scaleX` and `scaleY` properties to Pixi sprites
|
||
|
this._addScaleProperties(sprite);
|
||
|
|
||
|
return this.makeTween([
|
||
|
|
||
|
//Create the scaleX tween
|
||
|
[
|
||
|
sprite, "scaleX", sprite.scaleX, scaleFactor, frames,
|
||
|
bounce, yoyo, delayBeforeRepeat
|
||
|
],
|
||
|
|
||
|
//Create the scaleY tween
|
||
|
[
|
||
|
sprite, "scaleY", sprite.scaleY, scaleFactor, frames,
|
||
|
bounce, yoyo, delayBeforeRepeat
|
||
|
]
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
wobble(
|
||
|
sprite,
|
||
|
scaleFactorX = 1.2,
|
||
|
scaleFactorY = 1.2,
|
||
|
frames = 10,
|
||
|
xStartMagnitude = 10,
|
||
|
xEndMagnitude = 10,
|
||
|
yStartMagnitude = -10,
|
||
|
yEndMagnitude = -10,
|
||
|
friction = 0.98,
|
||
|
yoyo = true,
|
||
|
delayBeforeRepeat = 0
|
||
|
) {
|
||
|
|
||
|
let bounceX = "bounce " + xStartMagnitude + " " + xEndMagnitude;
|
||
|
let bounceY = "bounce " + yStartMagnitude + " " + yEndMagnitude;
|
||
|
|
||
|
//Add `scaleX` and `scaleY` properties to Pixi sprites
|
||
|
this._addScaleProperties(sprite);
|
||
|
|
||
|
let o = this.makeTween([
|
||
|
|
||
|
//Create the scaleX tween
|
||
|
[
|
||
|
sprite, "scaleX", sprite.scaleX, scaleFactorX, frames,
|
||
|
bounceX, yoyo, delayBeforeRepeat
|
||
|
],
|
||
|
|
||
|
//Create the scaleY tween
|
||
|
[
|
||
|
sprite, "scaleY", sprite.scaleY, scaleFactorY, frames,
|
||
|
bounceY, yoyo, delayBeforeRepeat
|
||
|
]
|
||
|
]);
|
||
|
|
||
|
//Add some friction to the `endValue` at the end of each tween
|
||
|
o.tweens.forEach(tween => {
|
||
|
tween.onComplete = () => {
|
||
|
|
||
|
//Add friction if the `endValue` is greater than 1
|
||
|
if (tween.endValue > 1) {
|
||
|
tween.endValue *= friction;
|
||
|
|
||
|
//Set the `endValue` to 1 when the effect is finished and
|
||
|
//remove the tween from the global `tweens` array
|
||
|
if (tween.endValue <= 1) {
|
||
|
tween.endValue = 1;
|
||
|
this.removeTween(tween);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
//3. Motion path tweens
|
||
|
|
||
|
followCurve(
|
||
|
sprite,
|
||
|
pointsArray,
|
||
|
totalFrames,
|
||
|
type = "smoothstep",
|
||
|
yoyo = false,
|
||
|
delayBeforeRepeat = 0
|
||
|
) {
|
||
|
|
||
|
//Create the tween object
|
||
|
let o = {};
|
||
|
|
||
|
//If the tween is a bounce type (a spline), set the
|
||
|
//start and end magnitude values
|
||
|
let typeArray = type.split(" ");
|
||
|
if (typeArray[0] === "bounce") {
|
||
|
o.startMagnitude = parseInt(typeArray[1]);
|
||
|
o.endMagnitude = parseInt(typeArray[2]);
|
||
|
}
|
||
|
|
||
|
//Use `tween.start` to make a new tween using the current
|
||
|
//end point values
|
||
|
o.start = (pointsArray) => {
|
||
|
o.playing = true;
|
||
|
o.totalFrames = totalFrames;
|
||
|
o.frameCounter = 0;
|
||
|
|
||
|
//Clone the points array
|
||
|
o.pointsArray = JSON.parse(JSON.stringify(pointsArray));
|
||
|
|
||
|
//Add the tween to the `globalTweens` array. The `globalTweens` array is
|
||
|
//updated on each frame
|
||
|
this.globalTweens.push(o);
|
||
|
};
|
||
|
|
||
|
//Call `tween.start` to start the first tween
|
||
|
o.start(pointsArray);
|
||
|
|
||
|
//The `update` method will be called on each frame by the game loop.
|
||
|
//This is what makes the tween move
|
||
|
o.update = () => {
|
||
|
|
||
|
let normalizedTime, curvedTime,
|
||
|
p = o.pointsArray;
|
||
|
|
||
|
if (o.playing) {
|
||
|
|
||
|
//If the elapsed frames are less than the total frames,
|
||
|
//use the tweening formulas to move the sprite
|
||
|
if (o.frameCounter < o.totalFrames) {
|
||
|
|
||
|
//Find the normalized value
|
||
|
normalizedTime = o.frameCounter / o.totalFrames;
|
||
|
|
||
|
//Select the correct easing function
|
||
|
|
||
|
//If it's not a spline, use one of the ordinary tween
|
||
|
//functions
|
||
|
if (typeArray[0] !== "bounce") {
|
||
|
curvedTime = this.easingFormulas[type](normalizedTime);
|
||
|
}
|
||
|
|
||
|
//If it's a spline, use the `spline` function and apply the
|
||
|
//2 additional `type` array values as the spline's start and
|
||
|
//end points
|
||
|
else {
|
||
|
//curve = tweenFunction.spline(n, type[1], 0, 1, type[2]);
|
||
|
curvedTime = this.easingFormulas.spline(normalizedTime, o.startMagnitude, 0, 1, o.endMagnitude);
|
||
|
}
|
||
|
|
||
|
//Apply the Bezier curve to the sprite's position
|
||
|
sprite.x = this.easingFormulas.cubicBezier(curvedTime, p[0][0], p[1][0], p[2][0], p[3][0]);
|
||
|
sprite.y = this.easingFormulas.cubicBezier(curvedTime, p[0][1], p[1][1], p[2][1], p[3][1]);
|
||
|
|
||
|
//Add one to the `elapsedFrames`
|
||
|
o.frameCounter += 1;
|
||
|
}
|
||
|
|
||
|
//When the tween has finished playing, run the end tasks
|
||
|
else {
|
||
|
//sprite[property] = o.endValue;
|
||
|
o.end();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//The `end` method will be called when the tween is finished
|
||
|
o.end = () => {
|
||
|
|
||
|
//Set `playing` to `false`
|
||
|
o.playing = false;
|
||
|
|
||
|
//Call the tween's `onComplete` method, if it's been
|
||
|
//assigned
|
||
|
if (o.onComplete) o.onComplete();
|
||
|
|
||
|
//Remove the tween from the global `tweens` array
|
||
|
this.globalTweens.splice(this.globalTweens.indexOf(o), 1);
|
||
|
|
||
|
//If the tween's `yoyo` property is `true`, reverse the array and
|
||
|
//use it to create a new tween
|
||
|
if (yoyo) {
|
||
|
this.wait(delayBeforeRepeat).then(() => {
|
||
|
o.pointsArray = o.pointsArray.reverse();
|
||
|
o.start(o.pointsArray);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Pause and play methods
|
||
|
o.pause = () => {
|
||
|
o.playing = false;
|
||
|
};
|
||
|
o.play = () => {
|
||
|
o.playing = true;
|
||
|
};
|
||
|
|
||
|
//Return the tween object
|
||
|
return o;
|
||
|
}
|
||
|
|
||
|
walkPath(
|
||
|
sprite, //The sprite
|
||
|
originalPathArray, //A 2D array of waypoints
|
||
|
totalFrames = 300, //The duration, in frames
|
||
|
type = "smoothstep", //The easing type
|
||
|
loop = false, //Should the animation loop?
|
||
|
yoyo = false, //Shoud the direction reverse?
|
||
|
delayBetweenSections = 0 //Delay, in milliseconds, between sections
|
||
|
) {
|
||
|
|
||
|
//Clone the path array so that any possible references to sprite
|
||
|
//properties are converted into ordinary numbers
|
||
|
let pathArray = JSON.parse(JSON.stringify(originalPathArray));
|
||
|
|
||
|
//Figure out the duration, in frames, of each path section by
|
||
|
//dividing the `totalFrames` by the length of the `pathArray`
|
||
|
let frames = totalFrames / pathArray.length;
|
||
|
|
||
|
//Set the current point to 0, which will be the first waypoint
|
||
|
let currentPoint = 0;
|
||
|
|
||
|
//The `makePath` function creates a single tween between two points and
|
||
|
//then schedules the next path to be made after it
|
||
|
let makePath = (currentPoint) => {
|
||
|
|
||
|
//Use the `makeTween` function to tween the sprite's
|
||
|
//x and y position
|
||
|
let tween = this.makeTween([
|
||
|
|
||
|
//Create the x axis tween between the first x value in the
|
||
|
//current point and the x value in the following point
|
||
|
[
|
||
|
sprite,
|
||
|
"x",
|
||
|
pathArray[currentPoint][0],
|
||
|
pathArray[currentPoint + 1][0],
|
||
|
frames,
|
||
|
type
|
||
|
],
|
||
|
|
||
|
//Create the y axis tween in the same way
|
||
|
[
|
||
|
sprite,
|
||
|
"y",
|
||
|
pathArray[currentPoint][1],
|
||
|
pathArray[currentPoint + 1][1],
|
||
|
frames,
|
||
|
type
|
||
|
]
|
||
|
]);
|
||
|
|
||
|
//When the tween is complete, advance the `currentPoint` by one.
|
||
|
//Add an optional delay between path segments, and then make the
|
||
|
//next connecting path
|
||
|
tween.onComplete = () => {
|
||
|
|
||
|
//Advance to the next point
|
||
|
currentPoint += 1;
|
||
|
|
||
|
//If the sprite hasn't reached the end of the
|
||
|
//path, tween the sprite to the next point
|
||
|
if (currentPoint < pathArray.length - 1) {
|
||
|
this.wait(delayBetweenSections).then(() => {
|
||
|
tween = makePath(currentPoint);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
//If we've reached the end of the path, optionally
|
||
|
//loop and yoyo it
|
||
|
else {
|
||
|
|
||
|
//Reverse the path if `loop` is `true`
|
||
|
if (loop) {
|
||
|
|
||
|
//Reverse the array if `yoyo` is `true`
|
||
|
if (yoyo) pathArray.reverse();
|
||
|
|
||
|
//Optionally wait before restarting
|
||
|
this.wait(delayBetweenSections).then(() => {
|
||
|
|
||
|
//Reset the `currentPoint` to 0 so that we can
|
||
|
//restart at the first point
|
||
|
currentPoint = 0;
|
||
|
|
||
|
//Set the sprite to the first point
|
||
|
sprite.x = pathArray[0][0];
|
||
|
sprite.y = pathArray[0][1];
|
||
|
|
||
|
//Make the first new path
|
||
|
tween = makePath(currentPoint);
|
||
|
|
||
|
//... and so it continues!
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Return the path tween to the main function
|
||
|
return tween;
|
||
|
};
|
||
|
|
||
|
//Make the first path using the internal `makePath` function (below)
|
||
|
let tween = makePath(currentPoint);
|
||
|
|
||
|
//Pass the tween back to the main program
|
||
|
return tween;
|
||
|
}
|
||
|
|
||
|
walkCurve(
|
||
|
sprite, //The sprite
|
||
|
pathArray, //2D array of Bezier curves
|
||
|
totalFrames = 300, //The duration, in frames
|
||
|
type = "smoothstep", //The easing type
|
||
|
loop = false, //Should the animation loop?
|
||
|
yoyo = false, //Should the direction reverse?
|
||
|
delayBeforeContinue = 0 //Delay, in milliseconds, between sections
|
||
|
) {
|
||
|
|
||
|
//Divide the `totalFrames` into sections for each part of the path
|
||
|
let frames = totalFrames / pathArray.length;
|
||
|
|
||
|
//Set the current curve to 0, which will be the first one
|
||
|
let currentCurve = 0;
|
||
|
|
||
|
//The `makePath` function
|
||
|
let makePath = (currentCurve) => {
|
||
|
|
||
|
//Use the custom `followCurve` function to make
|
||
|
//a sprite follow a curve
|
||
|
let tween = this.followCurve(
|
||
|
sprite,
|
||
|
pathArray[currentCurve],
|
||
|
frames,
|
||
|
type
|
||
|
);
|
||
|
|
||
|
//When the tween is complete, advance the `currentCurve` by one.
|
||
|
//Add an optional delay between path segments, and then make the
|
||
|
//next path
|
||
|
tween.onComplete = () => {
|
||
|
currentCurve += 1;
|
||
|
if (currentCurve < pathArray.length) {
|
||
|
this.wait(delayBeforeContinue).then(() => {
|
||
|
tween = makePath(currentCurve);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
//If we've reached the end of the path, optionally
|
||
|
//loop and reverse it
|
||
|
else {
|
||
|
if (loop) {
|
||
|
if (yoyo) {
|
||
|
|
||
|
//Reverse order of the curves in the `pathArray`
|
||
|
pathArray.reverse();
|
||
|
|
||
|
//Reverse the order of the points in each curve
|
||
|
pathArray.forEach(curveArray => curveArray.reverse());
|
||
|
}
|
||
|
|
||
|
//After an optional delay, reset the sprite to the
|
||
|
//beginning of the path and make the next new path
|
||
|
this.wait(delayBeforeContinue).then(() => {
|
||
|
currentCurve = 0;
|
||
|
sprite.x = pathArray[0][0];
|
||
|
sprite.y = pathArray[0][1];
|
||
|
tween = makePath(currentCurve);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//Return the path tween to the main function
|
||
|
return tween;
|
||
|
};
|
||
|
|
||
|
//Make the first path
|
||
|
let tween = makePath(currentCurve);
|
||
|
|
||
|
//Pass the tween back to the main program
|
||
|
return tween;
|
||
|
}
|
||
|
|
||
|
//4. Utilities
|
||
|
|
||
|
/*
|
||
|
The `wait` method lets you set up a timed sequence of events
|
||
|
|
||
|
wait(1000)
|
||
|
.then(() => console.log("One"))
|
||
|
.then(() => wait(1000))
|
||
|
.then(() => console.log("Two"))
|
||
|
.then(() => wait(1000))
|
||
|
.then(() => console.log("Three"))
|
||
|
|
||
|
*/
|
||
|
|
||
|
wait(duration = 0) {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
setTimeout(resolve, duration);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
//A utility to remove tweens from the game
|
||
|
removeTween(tweenObject) {
|
||
|
|
||
|
//Remove the tween if `tweenObject` doesn't have any nested
|
||
|
//tween objects
|
||
|
if (!tweenObject.tweens) {
|
||
|
tweenObject.pause();
|
||
|
|
||
|
//array.splice(-1,1) will always remove last elemnt of array, so this
|
||
|
//extra check prevents that (Thank you, MCumic10! https://github.com/kittykatattack/charm/issues/5)
|
||
|
if (this.globalTweens.indexOf(tweenObject) != -1) {
|
||
|
this.globalTweens.splice(this.globalTweens.indexOf(tweenObject), 1);
|
||
|
}
|
||
|
|
||
|
//Otherwise, remove the nested tween objects
|
||
|
} else {
|
||
|
tweenObject.pause();
|
||
|
tweenObject.tweens.forEach(element => {
|
||
|
this.globalTweens.splice(this.globalTweens.indexOf(element), 1);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
update() {
|
||
|
|
||
|
//Update all the tween objects in the `globalTweens` array
|
||
|
if (this.globalTweens.length > 0) {
|
||
|
for (let i = this.globalTweens.length - 1; i >= 0; i--) {
|
||
|
let tween = this.globalTweens[i];
|
||
|
if (tween) tween.update();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|