2 changed files with 865 additions and 1 deletions
@ -0,0 +1,836 @@ |
|||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue