How do I make an animation go off once in Javascript? - javascript

I've been looking up on this for hours and hours but I couldn't figure out how to fit it to my needs.
I'm trying to make it so that an animation is played at a specific X and Y location, after being triggered. (For example, an explosion animation plays after a tank is destroyed)
I have a function like this:
var explosion = new Image();
explosion.src = "https://i.imgur.com/gWjqlKe.png";
function animate(img, x, y, width, height, steps){
steps = Math.floor(steps);
context.clearRect(x, y, width, height);
context.drawImage(img, width * step, 0, width, height);
step += .3;
requestAnimationFrame(animate(img, x, y, width, height, steps));
}
And then when I needed to call it, I did:
animate(explosions, tank.x, tank.y, 100, 100, 10);
But nothing happens when I destroy a tank.
I based my code off of this tutorial: https://www.youtube.com/watch?v=yna816VY8rg
Any help would be appreciated! Thanks so much!
EDIT:
I'm trying out setInterval now, but it still doesn't work...
var explosion = new Image();
explosion.src = "https://i.imgur.com/gWjqlKe.png";
// Animation Functions
function animate(img, x, y, width, height, endframe){
var frame = 0;
frame = setInterval(animation(frame, img, x, y, width, height), 1000);
if (frame >= endframe){
clearInterval(frame)
}
}
function animation(frame, img, x, y, width, height){
ctx = gameArea.context;
ctx.drawImage(img, x * frame, y, width, height);
frame ++;
return frame;
EDIT 2:
I realized from my Reddit that I made a mistake in the requestAnimationFrame and setInterval, so I edited again but it still does not work:
function animate(img, x, y, width, height, endframe){
var frame = 0;
frame = setInterval(function() {
animation(frame, img, x, y, width, height);
}, 1000);
if (frame >= endframe){
clearInterval(frame)
}
}
function animation(frame, img, x, y, width, height){
ctx = gameArea.context;
ctx.drawImage(img, x * frame, y, width, height);
frame ++;
return frame;
}
function animate(img, x, y, width, height, steps){
steps = Math.floor(steps);
context.clearRect(x, y, width, height);
context.drawImage(img, width * step, 0, width, height);
step += .3;
requestAnimationFrame(animate(img, x, y, width, height, steps));
}

One main loop to rule them all.
Using event timers to start animation loops via requestAnimationFrame will cause you all sort of problems. requestAnimationFrame will fire for the next frame in the same order as the last frame. If your timeout gets in during the frame before any other frames it will be the first function to draw to the canvas, then the other frames will draw over the top.
As nnnnnnn pointed out in the comment use only one game loop and save yourself a lot of unneeded complexity in getting the order of renders correct.
requestAnimationFrame time argument
requestAnimationFrame provides a timer in as the first argument. This timer is the current frame time, not the actual time. This lets you compute times from the V sync (display refresh)
Example of timing and animation
To animate some FX for a set time record the start time, set a duration and animate to the time given in the frames argument.
The example shows a simple explode timer used in a main loop. You begin the timer when needed supplying the current frame time. While it is active you give it the current time, It updates its own relative time, and when done the active flag is set to false. You render your animation via its current time which will be from 0 to length
This is only one animation, you would use a stack of such time objects, adding to the stack for each timed animation.
const explode = {
start : null,
length : 1000,
current : 0,
active : false,
begin(time,length = this.length){
this.start = time;
this.length = length;
this.active = true;
this.current = 0;
},
getCurrent(time){
this.current = time - this.start;
this.active = this.current <= this.length;
return this.current;
}
}
var tankDead = false;
function mainLoop(time){ // time is supplied by requestAnimationFrame
if(tankDead ) {
tankDead = false;
explode.begin(time,2000); // two seconds
}
if(explode.active){
explode.getCurrent(time);
if(explode.active){
// explode.current is the time
// draw the animation at time explode.current
} else {
// explosion is complete
}
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Timing animations via a tick.
Though I prefer the fixed frame rate method and just count down a tick timer
var tankDead = false;
var explodeTick = 0;
function mainLoop(time){ // time is supplied by requestAnimationFrame
if(tankDead ) {
tankDead = false;
explodeTick = 60 * 2; // two seconds
}
if(explodeTick > 0){
explodeTick -= 1;
// draw the animation at time explosion
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Much simpler to manage, and nobody will notice if you drop a frame or two and it takes a fraction of a second longer.

Related

How to make an object move in p5.js in a given time?

Suppose I have a canvas that is 1200px. How do I get an object to move from the starting point (100px) to its endpoint (1000px) within a given time (eg. 10 seconds)? In such a way that it takes the object exactly 10 seconds to traverse from starting point to endpoint.
My code looks like this so far:
function setup()
{
createCanvas(img.width, img.height);
//Initialize x with the start value
x = startX;
}
function draw()
{
image(img, 0, 0);
x = min(endX, x);
x+=2;
//stop the object if it's near enough to endx and endy
if (abs(endX - x) < 30)
{
x = endX;
}
y = 114;
//stop the object if it goes off of the screen
x = min(x, 1200);
x = max(x, 0);
var spotlightSize = 114;
blendMode(BLEND);
background(10);
image(spotlight_image, x-spotlightSize/2, y-spotlightSize/2, spotlightSize, spotlightSize);
blendMode(DARKEST);
image(img, 0, 0);
}
If the frame rate was perfect and constant, you could simply divide the distance to travel by the amount of frames in the time that it takes. That result would be how far you need to travel in each frame. The frame rate is not perfect or constant, but we will write a program assuming a perfect frame rate because it will help us later.
What you need to do is:
Find how many frames will pass in the time you want to move - multiply the time to move by the frames per second
Find the displacement from the start to the end - subtract the start from the end
Divide the displacement by the amount of frames that will pass
Move that far each frame until you are close enough to the end
An example implementation: (you used only x-position but I used vectors as they will probably be useful to someone in the future)
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10; // Move duration in seconds
const moveDurationFrames = moveDurationS / (1 / fps); // How many frames will it take to move the distance
const distToMovePerFrame = p5.Vector.div(distToTravel, moveDurationFrames); // How far to move each frame
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
position.add(distToMovePerFrame);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
The frame rate is not constant, though. Fortunately, p5 has a function that tells us how many milliseconds have passed in the last frame. So what we do is:
Find how many milliseconds pass in the time you want to move - multiply the seconds you want it to move for by 1000
Find out how far it will move per millisecond - divide the start/end displacement by the amount of milliseconds that will pass
Each frame, move the distance per millisecond multiplied by how many milliseconds have gone past in that frame.
Here's that translated into code:
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10;
const moveDurationMs = moveDurationS * 1000;
const distToMovePerMs = p5.Vector.div(distToTravel, moveDurationMs);
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
var thisFrameMovement = p5.Vector.mult(distToMovePerMs, deltaTime);
position.add(thisFrameMovement);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
I tested the above code and it was pretty accurate - it averaged 0.75% off. I hope that this is what you're looking for in your answer!

canvas - how to set interval in requestAnimationFrame() loop?

i was looking for how to stop a loop in requestAnimationFrame() loop.
I'm trying to make a simple top-to-bottom falling circle animation and every time the circle hit the bottom, in this case the window.innerHeight, it will set its position back to its starting position then wait until 12 sec to animate again. i'm trying to use setTimeOut() but the circle got faster instead.
this is my code:
drawCircle = () =>
{
requestAnimationFrame(this.drawCircle);
this._CONTEXT.clearRect(0, 0, this._CANVAS.width, this._CANVAS.height);
this._CONTEXT.beginPath();
this._CONTEXT.arc(this.x, this.y, 20, 0, 2 * Math.PI);
this._CONTEXT.lineWidth = 1;
this._CONTEXT.fillStyle = '#ff0000';
this._CONTEXT.fill();
this.y += 15;
if (this.y > this._CANVAS.height) {
setTimeout(12000);
this.initPos();
}
}

HTML Canvas: Animation Delay

Here is a demo of my Canvas.
The canvas generates a random rectangle and animates it by scaling it from 1.0 to 1.2 and back to 1.0 again. (Kinda like a human heart). This animation takes approximately 2 seconds to complete. There are 60 totalIterations. It starts with 0 and increments by one for every frame until it reaches 60. Once it reaches 60, the iteration is set back to 0 and animates from 1.2 scale back to 1.0.
What I want to do is before the execution of the next cycle (cycle meaning from 1.0 scale, to 1.2, and back to 1.0), I want to defer the scale.
Here is what I tried to do:
Context:
this.intermission = 3; // time to wait, or "defer" for
elapsed = (Date.now() - this.initTime) / 1000; // time elapsed since initialization (in seconds)
Condition:
if((elapsed % this.intermission >= (this.intermission - (this.intermission-1))) && (elapsed % this.intermission <= (this.intermission + (this.intermission-1)))) {
ctx.scale(this.easing, this.easing);
}
Condition Explained (Probably makes no sense):
If the remainder from dividing the elapsed time by 3 is greater than or equal to 2 AND the remainder from dividing the elapsed time by 3 is less than or equal to 5, scale the rectangle using the ease function.
... I wanted to give it some "buffer" room to complete the animation
If I were to increase the intermission to 10, the above condition would not work anymore, so I need a much better solution.
I thought about using setTimeout(function(){...}, x), but this is inside the JavaScript class.
Animation Lists / Stacks.
Keyframing
The best way would be to set up code to manage keyframes so you could just create a list of keyframes for each property of an object you wish to change over time. This allows you to create very complex animations that can be serialized to a JSON file. This decouples the animation from the code, but requires a lot more code.
Animation List
If it is just a simple animation then you can create an animation stack (or list if animation order is static), which is just a set of functions that get called in turn for each part of the animation.
You set a startTime for each part of the animation, being careful to set it consistently so you do not get any time drift. If the animation cycle is 4 seconds long then it should repeat every 4 seconds and in 4,000,000 seconds it should be just as precise.
Always use requestAnimationFrame (rAF) when animating anything on the page. rAF calls the callback passing as the first argument the time in ms (1/1000th) with a precision of 1/1,000,000 (0.001ms).
Endless animation using animation list
const canvas = document.createElement("canvas");
canvas.height = canvas.width = 300;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas)
// ease function
function easeInOut(x, pow = 2) {
x = x < 0 ? 0: x > 1 ? 1 : x;
var xx = Math.pow(x,pow);
return xx/(xx+Math.pow(1-x,pow));
};
function MyObj(){
this.x = 100;
this.y = 100;
this.size = 40;
this.scale = 1;
}
MyObj.prototype = {
getUnitTime(duration){ // get the unit time
var unitTime = (globalTime - startTime) / duration;
if(unitTime >= 1){ // if over time
unitTime = 1; // make sure that the current frame is not over
startTime = startTime + duration; // next frame start (could be in the past)
currentAnim += 1; // next animation in the list
}
return unitTime;
},
grow(){
drawText("Grow 1s");
// grow for 1 second
this.scale = easeInOut(this.getUnitTime(1000)) * 0.6 + 1;
},
shrink(){
drawText("Shrink 1s");
// shrink for 1 second
this.scale = 1.6 - easeInOut(this.getUnitTime(1000)) * 0.6 ;
},
wait(){
drawText("Wait 2s");
this.getUnitTime(2000); // wait two seconds
},
draw(ctx){
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.scale, 0, Math.PI * 2);
ctx.fill();
}
}
function drawText(text){
ctx.fillStyle = "black";
ctx.fillText(text,100,36);
}
var obj = new MyObj(); // create the object
// holds the animation list
const animationList = [
obj.grow.bind(obj), // bind the function calls to the object
obj.shrink.bind(obj),
obj.wait.bind(obj)
];
var currentAnim; // index of current animation
var startTime; // start time of current animation
var globalTime; // time from the requestAnimationFrame callback argument
ctx.font = "32px arial";
ctx.textAlign = "center";
// main animation loop
function update(time){
globalTime = time; // set the global
if(currentAnim === undefined){ // if not set then
startTime = time; // set start time
currentAnim = 0; // set the index of the first animation
}
// clear the screen
ctx.clearRect(0,0,canvas.width,canvas.height);
// call the animation function
animationList[currentAnim % animationList.length]();
// draw the object
obj.draw(ctx);
// request next frame
requestAnimationFrame(update);
}
// start it all happening
requestAnimationFrame(update);
Stacks
Stacks are much the same but used when the animation is conditional. You use some event to push the animation functions onto the stack. Then you shift the animation functions from the stack as needed. Or you may want the animation to repeat 10 times, then do something else, then start again. The animation stack lets you do this rather than have a huge list of animations.
Stack example using click event.
const canvas = document.createElement("canvas");
canvas.height = canvas.width = 300;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas)
// ease function
function easeInOut(x, pow = 2) {
x = x < 0 ? 0: x > 1 ? 1 : x;
var xx = Math.pow(x,pow);
return xx/(xx+Math.pow(1-x,pow));
};
function MyObj(){
this.x = 100;
this.y = 100;
this.size = 40;
this.scale = 1;
}
MyObj.prototype = {
getUnitTime(duration){ // get the unit time
var unitTime = (globalTime - startTime) / duration;
if(unitTime >= 1){ // if over time
unitTime = 1; // make sure that the current frame is not over
startTime = startTime + duration; // next frame start (could be in the past)
currentAnim = undefined
}
return unitTime;
},
grow(){
drawText("Grow 1s");
// grow for 1 second
this.scale = easeInOut(this.getUnitTime(1000)) * 0.6 + 1;
},
shrink(){
drawText("Shrink 1s");
// shrink for 1 second
this.scale = 1.6 - easeInOut(this.getUnitTime(1000)) * 0.6 ;
},
timeup(){
drawText("Click to Animate");
currentAnim = undefined;
},
draw(ctx){
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.scale, 0, Math.PI * 2);
ctx.fill();
}
}
function drawText(text){
ctx.fillStyle = "black";
ctx.fillText(text,100,36);
}
var obj = new MyObj(); // create the object
// holds the animation list
const animationStack = [obj.timeup.bind(obj)];
var currentAnim; // index of current animation
var startTime; // start time of current animation
var globalTime; // time from the requestAnimationFrame callback argument
ctx.font = "26px arial";
ctx.textAlign = "center";
function startAnim(){
animationStack.length = 0;
animationStack.push(obj.grow.bind(obj));
animationStack.push(obj.shrink.bind(obj));
animationStack.push(obj.timeup.bind(obj));
if(currentAnim === undefined){// only restart if animation is not running
requestAnimationFrame(update);
}
startTime = undefined;
currentAnim = undefined;
}
canvas.addEventListener("click",startAnim)
// main animation loop
function update(time){
globalTime = time; // set the global
if(startTime === undefined){ // if not set then
startTime = time; // set start time
}
if(currentAnim === undefined){
if(animationStack.length > 0){
currentAnim = animationStack.shift();
}
}
if(currentAnim === undefined){
return;
}
// clear the screen
ctx.clearRect(0,0,canvas.width,canvas.height);
// call the animation function
currentAnim();
// draw the object
obj.draw(ctx);
// request next frame
requestAnimationFrame(update);
}
// start it all happening
requestAnimationFrame(update);

Canvas - Increase animation speed without jolts

How can I increase the speed of my canvas animation and still get a smoothly moving object?
In my example a boy is moving from the top to the bottom of the canvas. The movement seems to jolt, the bigger the y-step is. How can I avoid the jolts and still have a fast movement to the bottom?
Fiddle
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
(function animloop() {
requestAnimFrame(animloop);
redraw();
})();
function redraw() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.rect(20, y, 90, 90);
ctx.fillStyle = "red";
ctx.fill();
y += 5;
}
You can increase the speed your canvas animation and still get a smoothly moving object by reducing the distance travelled per frame and increasing the frame rate.
RequestAnimationFrame is a performant way of animating at a reasonable framerate given your hardware but it fixes the framerate.
You can have more control over the framerate using setInterval or setTimeout instead, which accepts a second integer argument that determines milliseconds till next callback (i.e. framerate).
setInterval(function () {
redraw();
}, 2);
http://jsfiddle.net/uq14c6bu/2/

HTML5 canvas character jump

I try to make an animation of character by reading this tutorial:
http://mrbool.com/html5-canvas-moving-a-character-with-sprites/26239 .
It's quite ease to make the character go left ('go right' is already done). But how to make the character jump (with animation)?
I was thinking about something like this:
case 38:
if (y + dy > HEIGHT){
y += dy
}
break;
...but it just move character up (without animation). Can someone help me? Some code example will be useful.
You get the jumping behavior like this (using the same code on the tutorial)
JSFiddle
var canvas;// the canvas element which will draw on
var ctx;// the "context" of the canvas that will be used (2D or 3D)
var dx = 50;// the rate of change (speed) horizontal object
var x = 30;// horizontal position of the object (with initial value)
var y = 150;// vertical position of the object (with initial value)
var limit = 10; //jump limit
var jump_y = y;
var WIDTH = 1000;// width of the rectangular area
var HEIGHT = 340;// height of the rectangular area
var tile1 = new Image ();// Image to be loaded and drawn on canvas
var posicao = 0;// display the current position of the character
var NUM_POSICOES = 6;// Number of images that make up the movement
var goingDown = false;
var jumping;
function KeyDown(evt){
switch (evt.keyCode) {
case 39: /* Arrow to the right */
if (x + dx < WIDTH){
x += dx;
posicao++;
if(posicao == NUM_POSICOES)
posicao = 1;
Update();
}
break;
case 38:
jumping = setInterval(Jump, 100);
}
}
function Draw() {
ctx.font="20px Georgia";
ctx.beginPath();
ctx.fillStyle = "red";
ctx.beginPath();
ctx.rect(x, y, 10, 10);
ctx.closePath();
ctx.fill();
console.log(posicao);
}
function LimparTela() {
ctx.fillStyle = "rgb(233,233,233)";
ctx.beginPath();
ctx.rect(0, 0, WIDTH, HEIGHT);
ctx.closePath();
ctx.fill();
}
function Update() {
LimparTela();
Draw();
}
var Jump = function(){
if(y > limit && !goingDown){
y-=10;
console.log('jumping: ' + y);
} else{
goingDown = true;
y +=10;
if(y > jump_y){
clearInterval(jumping);
goingDown = false;
}
}
}
function Start() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
return setInterval(Update, 100);
}
window.addEventListener('keydown', KeyDown);
Start();
There's no one right answer to this question, and unless you find a game-design library, there's no simple one, either. Your problem is that you're moving the character instantaneously in response to input, but a jump requires movement over time. You'll have to either find a moving sprites library - I don't have one in particular to recommend, but I'm sure Google has several - or set up something yourself that runs every so many milliseconds and updates the character's position and some sort of velocity variable.
Edit: Looking at that tutorial, the simplest solution that comes to mind is to put your animation code inside of Update(), like so:
function Update() {
LimparTela();
Animate();
Draw();
}
Inside of Animate(), you should keep track of the character's height and vertical momentum. If the momentum is positive, increase the y position a little, otherwise decrease it a little. Either way, reduce momentum a bit. Add something to keep the character from going through the floor, and have the up key set the character's momentum to be positive if he's on the floor.
Note that this is an incredibly bare-bones solution, but for a basic tutorial it'll do the job.

Categories