JavaScript canvas + requestAnimationFrame stuttering - javascript

I'm currently trying to make a sparkle effect that happens within the canvas. However, as I was testing out the animation, I noticed that some circles would be erased for a frame, and then reappear again.
This seems to be caused when a sparkle of a higher index is removed by splicing. You can notice the flash whenever a sparkle shrinks to a radius of 0. If this is the actual reason why it causes the issue, I really don't understand why splicing would cause such an issue.
https://jsfiddle.net/sketti21/2gfarp74/82/
// Handles the animation of the sparkles.
function animate(timestamp)
{
// Observe the timestamp gives by requestAnimationFrame in console.
// console.log(timestamp);
// Clears the previous frame;
draw.clearRect(0, 0, canvas.width, canvas.height);
// Loop through each index of sparkles. Doing this lets us draw multiple
// sparkles at once.
for (i = 0; i < sparkles.length; i++)
{
if (sparkles[i] != null)
{
// If the sparkle's delay is not set, then set it.
// This is necessary to do here since the timestamp that requestAnimationFrame
// puts in as a parameter is always growing.
if (sparkles[i].delay == null)
{
sparkles[i].delay = Math.floor(Math.random()*config.maxDelay) + timestamp;
}
// Handles whether to animate the next frame or to remove this sparkle from array.
// If timestamp is larger than sparkle's delay, this means it's time to
// animate this sparkle.
if (timestamp > sparkles[i].delay)
{
// Increment the input variable and calculate the radius of circle.
sparkles[i].inputX += config.increment;
var radius = getRadius(sparkles[i].inputX, sparkles[i].multiplier);
console.log("Radius of index " + i + ": " + radius);
console.log("Multiplier of index " + i + ": " + sparkles[i].multiplier);
if (radius < 0)
{
radius = 0;
}
// Drawing the circle.
draw.beginPath();
draw.arc(sparkles[i].x,
sparkles[i].y,
radius, 0, 360);
draw.fill();
draw.closePath();
// If the sparkle's radius drops below 0, then remove it from the array.
if (radius == 0)
{
sparkles.splice(i, 1);
console.log("spliced indexes " + i + " to " + (i + 1));
}
}
}
}
if (sparkles.length > 0)
{
animControl = window.requestAnimationFrame(animate);
}
else
{
console.log("Refilled the array.");
for (i = 0; i < config.maxSparkles; i++)
{
sparkles[i] = new Sparkle();
}
animControl = window.requestAnimationFrame(animate);
}
}
Let me summarize the logic of my code to make reading through it quicker and easier:
Create the sparkles array.
Start the animation. (animate())
Clear the canvas.
Fill sparkles array up to config.maxSparkles.
requestAnimationFrame(animate)
Clear the canvas.
If a random delay hasn't been assigned to the Sparkle object, then calculate a delay that's below config.maxDelay and add the timestamp.
If the current timestamp is greater than the Sparkle's delay..
8a. Calculate and draw each sparkle individually for this frame.
8b. If the Sparkle radius = 0, then splice that sparkle out of the sparkles array.
If there are elements in the sparkles array, then requestAnimationFrame(animate)
9a. Else, refill the array up to config.maxSparkles.
... So on and so forth.
Any help is greatly appreciated!

When you splice something from an array, an element gets removed, and thus the length is shortened by one.
So if you loop through an array and remove something, that means that the index for the item you are removing has now become the index for the next item. So on the next loop, that item is skipped.
To solve your problem, you need to decrement i when you splice something from the array, so it isn't skipped on the next iteration.
That should hopefully solve the issue!

Related

Javascript animation infinite loop

I am writing a simple animation in javascript (I can't use css) and I don't seem to make it infinite. this is what I have so far:
myMove(ball , i, size) {
var id = setInterval(frame, 3* i);
function frame() {
if (size == 100) {
size = 0;
} else {
size++;
ball.style.width = size/10 + 'px';
ball.style.height = size/10 + 'px';
}
}
}
the ball would be the element, the i is the iterated element in a for loop in which the function is called for each element.
for (var i = 0; i < numberOfBalls; i++{
myMove(document.getElementByClassName[i]('ball'), i, 10)
}
and size is the max size I wish my element expands to.
I wish to have a smooth transition, where the 5 balls will expand in succession (so first ball after 300ms, the second, after 300ms the third and so on) and when the last ball has expanded, repeat the process. I'm really struggling to get a decent result.
Thanks for your help

For loop inside if statement will not run and I'm not sure why

A brief run-down - I'm using the wad JavaScript library to create a random tone generator. I am trying to have the generator 'slide' between pitches, as follows in pseudocode:
Determine the current pitch (randomNum)
Determine the next pitch (randomNext)
Determine whether the next pitch is higher or lower
Play the current pitch and hold it for a second
Play all the pitches between the current pitch and the next pitch quickly to give a 'sliding' effect - this is the part that isn't working!
Play the next pitch and hold it for a second (and then reassign it as the current pitch).
I have got everything working except for step 5. I am trying to use a for loop (within an if/else statement according to whether the next pitch is higher or lower) to iterate over each pitch using a counter, but according to my logged console statements, the for loop never runs at all.
If I remove the for loop from the if statement and set its starter pitch manually from a variable, it runs as expected.
What am I doing wrong? Is there something wrong with my if statement? With my for loop?
The problematic bit of code is this:
// play the first pitch
function playFirst() {
if(!stopped){
window.randomNum = Math.round(Math.random()*200 + 100);
console.log("randomnum is now "+ randomNum);
}
playRandom();
}
// play and loop subsequent pitches
function playRandom(){
if(!stopped){
var randomNext = Math.round(Math.random()*200 + 100);
console.log("randomNext is now " + randomNext);
var howManyCents = randomNum - randomNext;
console.log(howManyCents + " cents");
// ascending note slide condition
if (randomNum < randomNext) {
console.log("randomnum is less!");
var inbetweenNum = randomNum + 1;
// for loop - the part with the problem!
for (var i = 0; i < howManyCents; i++) {
inbetweenNum = randomNum + i;
console.log("inbetween number is " + inbetweenNum);
inbetween.play({ pitch : inbetweenNum });
console.log("played inbetween up");
}
// descending note slide condition
} else {
console.log("randomnum is more!");
var inbetweenNum = randomNum - 1;
// another problematic for loop
for (var i = 0; i > howManyCents; i--) {
inbetweenNum = randomNum - i;
console.log("inbetween number is " + inbetweenNum);
inbetween.play({ pitch : inbetweenNum });
console.log("played inbetween down");
}
}
// actually play the note
bell.play({ pitch : randomNext, wait: 0 });
console.log("played randomnext" + randomNext);
// reassign the new note as the current note
randomNum = randomNext;
console.log("randomnum is now" + randomNum);
setTimeout(playRandom,1500); // and loop it
}
}
and I have made a JSFiddle of the full program here.
Any assistance would be very much appreciated!
The condition for that block is that randomNum < randomNext, but howManyCents is randomNum - randomNext, which will be negative in that case. The loop condition, then – i < howManyCents, with i starting at 0, will never be true.
You can use i < -howManyCents, or assign Math.abs(randomNum - randomNext) to howManyCents, or i > howManyCents and i--.

compare multiple values in changing arrays

I'm making a basic javascript 'game', where objects (enemies) travel down from the top of the container, and a 'ship' shoots 'bullets' at the objects. There are many bullets and many enemies. I'm pushing every enemy object into an array, and the Y (top) value changes and is pushed into the array and updated with every change.
This is an interval that pushes the enemy down on the screen, and updates its _top value in the array.
enemyCoord = {
_left : parseInt(enemy.style.left, 10),
_top : enemyTop
}
enemyArray.push(enemyCoord);
var enemyInterval = setInterval(enemyMovement, 7 );
function enemyMovement(){
enemyTop += 1;
enemyArray[enemyCount]._top = enemyTop; // enemyCount is unique number given to each enemy object
enemy.style.top = enemyTop + 'px';
if (enemyTop >= document.getElementById('gc_1').offsetHeight -20 ) {
clearInterval(enemyInterval);
container.removeChild(enemy);
}
}
This is the bullet movement and where I attempt to make the values meet
function shoot(bullet, topValue) {
var topValue = parseInt(topValue, 10);
var bulletTop = topValue;
var bulletInterval = setInterval(bulletMovement, 7);
var arrayLength = enemyArray.length;
function bulletMovement (){
bulletTop = bulletTop - 1 ;
bullet.style.top = bulletTop + 'px';
if (bulletTop <= 20) {
var container = document.getElementById('gc_1');
container.removeChild(bullet);
clearInterval(bulletInterval);
}
// This is the part I really need help with! Where the values meet
for (var i=0;i<arrayLength;i++) {
if (bulletTop === enemyArray[i]._top) {
alert('we met :)');
}
}
}
}
How do I compare the parsed top style value of the bullets and the enemies? each bullet is checking against the array of enemy objects top value to see if it equals the bullets top value, but it doesn't work. How would I do this in a more efficient way?

Shoot 3 bullets on set times from each enemy

I'm trying to create a simple game with kineticJS on my canvas(Just some practice) and managed to get my player to shoot bullets. The same goes for the enemies that are spawned. They shoot a bullet each time the last bullet leaves the stage.
However: I'd like all the enemies(Variable number) to shoot 3 bullets at a 2 seconds interval. But i'm stuck completely and can't figure a way how to get it done.
Could anyone please look at my fiddle and see what's up?
http://jsfiddle.net/eRQ3P/6/
Note: line 573 is the function that loops(And draws the bullets and such every 30FPS)
Here's the code where i create a new bullet object:(Line 406 in fiddle)
function Enemybullet(destinationX, destinationY, enemySprite) {
this.id = 'bullet';
this.x = enemySprite.getX()+(enemySprite.getWidth()/2);
this.y = enemySprite.getY()+(enemySprite.getHeight()/2);
var targetX = destinationX - this.x,
targetY = destinationY - this.y,
distance = Math.sqrt(targetX * targetX + targetY * targetY);
this.velX = (targetX / distance) * 5;
this.velY = (targetY / distance) * 5;
this.finished = false;
this.sprite = new Kinetic.Circle({
x: this.x,
y: this.y,
radius: 3,
fill: 'black',
name: 'enemyProjectile'
});
this.draw = function(index) {
var mayDelete = false;
this.x += this.velX;
this.y += this.velY;
this.sprite.setAbsolutePosition(this.x, this.y);
//console.log(this.sprite.getX());
/*
if(enemyCollision(this) == true) {
mayDelete = true;
}*/
if (bulletLeftField(this.sprite) == true) {
mayDelete = true;
}
if (mayDelete == true) {
this.sprite.remove();
enemies[index].bullets.splice(0, 1);
createEnemyBullet(enemies[index]);
}
ammoLayer.draw();
}
}
And the function providing a new bullet: (line 247 in fiddle)
function createEnemyBullet(enemy) {
var blt = new Enemybullet(player.sprite.getX(), player.sprite.getY(), enemy.sprite);
ammoLayer.add(blt.sprite);
enemy.bullets.push(blt);
}
Probably the hardest part of this problem is figuring out when to draw each bullet to make three be fired in every 2-second interval. To make the bullets fire evenly, you want to divide the number of frames in the interval by the number of bullets to fire in that interval.
Because you're running the game at 30 frames per second, 2 seconds equals 60 frames.
60 frames / 3 bullets = 20 frames/bullet
So, we're going to create a new bullet for each enemy every 20 frames, or every 20th time refreshLoop() is called, and inside refreshLoop(), you now have to loop through all of the bullets each enemy has in its bullets array, because there can now be more than just one.
The fact that there can be more than one bullet in the bullets array introduces a new problem to the way bullets are removed from the array. Previously, you relied on the fact that one bullet at a time means it will always be the first one in the array, thus your code called bullets.splice(0, 1);. However, when the player is moving around and the enemies fire at the different locations, it is completely possible to have a bullet leave the screen and be removed sooner than one that was fired before it. This would cause the correct bullet sprite to be removed, but the first bullet in the array would be removed from bullets, so it wouldn't be updated anymore in refreshLoop(), and it would just sit on the screen doing nothing.
In order to avoid this, it is necessary to pass to the enemy bullets' draw() function the index in bullets at which the bullet being drawn is located. Since you need to loop through the array anyway, the index is already at hand in refreshLoop(), so just pass this to draw(). Now, every time a bullet needs to be removed, you can just call bullets.splice(bulletIndex, 1);
I hope you don't mind; I forked your fiddle to update it with the changes listed below.
EDIT: A new fiddle for burst-fire instead of sustained fire.
// Inside your Enemybullet definition
// One simple change to draw(), pass in the index of the bullet in the array
this.draw = function(indexEnemy, indexBullet) {
var mayDelete = false;
...
if (bulletLeftField(this.sprite) == true) {
mayDelete = true;
}
if (mayDelete == true) {
this.sprite.remove();
// Since you now have multiple bullets, you'll have to make
// sure you're removing the correct one from the array
enemies[indexEnemy].bullets.splice(indexBullet, 1);
}
ammoLayer.draw();
}
...
// Inside your refreshLoop function
// If there are enemies they should be checked
if (enemies.length > 0) {
for (var i = 0; i < enemies.length; i++) {
enemies[i].draw();
// At 30 frames per second, 3 bullets in 2 seconds would be
// one bullet for every 20 frames. So, every 20 frames,
// create a new bullet for each enemy
if ((enemyShootTimer % 20) == 0) {
createEnemyBullet(enemies[i]);
}
// The same way you draw all of the player's bullets,
// loop through the array of bullets for this enemy,
// and draw each one, passing in the new parameters
if (enemies[i].bullets.length > 0) {
for (var j = 0; j < enemies[i].bullets.length; j++) {
enemies[i].bullets[j].draw(i, j);
}
}
}
}
// Update loop for burst-fire instead of sustained fire
var burstTime = 10; // 10 frames between bullets, 3 per second
var needToShoot = ((enemyShootTimer % burstTime) == 0);
if (enemies.length > 0) {
for (var i = 0; i < enemies.length; i++) {
enemies[i].draw();
// if the enemies still have bullets to shoot this burst
// and if 10 frames have passed since the last shot
// ( enemyBurstCounter is declared outside refreshLoop() )
if (enemyBurstCounter < 3 && needToShoot) {
createEnemyBullet(enemies[i]);
}
if (enemies[i].bullets.length > 0) {
for (var j = 0; j < enemies[i].bullets.length; j++) {
enemies[i].bullets[j].draw(i, j);
}
}
}
if ((enemyShootTimer % 60) == 0) {
enemyBurstCounter = 0; // if 2 seconds have passed, reset burst counter
} else if (needToShoot) {
enemyBurstCounter++; // if the enemies shot, update burst counter
}
}

Javascript animate math

I'm struggling to get my head around such simple Math, well at least it seems it should be simple.
I'm basically trying to mirror what jQuery's .animate does, but to no luck.
Here's a simplified version of what I have so far:
var args = {
speed: 1000, // 1 second.
left: 65 // distance.
}, rot, step;
// Terrible math.
rot = step = (((args.left / args.speed) * 10) - 0.10);
var t = setInterval(function() {
if(elem.style.left >= args.left) {
clearInterval(t);
return;
}
rot += step;
elem.style.left = rot;
}, 10);
Please excuse any illogical code (or math), I've been messing around for a good few hours and totally lost my sanity.
Edit:
Here's the way I would do it.
var start_time = Date.now();
// Get the starting time in milliseconds
var t = setInterval(function() {
var delta_time = Date.now() - start_time;
// Get time that has elapsed since starting
if (delta_time >= 1000) {
// if it's been a second
clearInterval(t);
// Stop the timer
elem.style.left = args.left + 'px';
// Set the element to exactly the value it should be (avoids having it set to a float value)
return;
}
elem.style.left = delta_time * args.left / args.speed + 'px';
// Move the element according to how much time has elapsed
}, 10);​
This method has a few advantages. For example, you can adjust the interval to make it more or less smooth, and it won't mess up the animation.
The reason why your solution was taking longer than one second is because of how you used setInterval. setInterval doesn't account for the time your code takes to run, so the total time is always increased by a bit. You can fix this by using delta timing (like in my example).
Try using useing sin and cos to calculate rotation Some what like this
newx = distance * Math.cos(direction) + x
newy = distance * Math.sin(direction) + y
Not sure , this will solve your problem I guess you want to to do a smooth rotation
Try making it as a function it will work , I am not seeing any problem in your math ,
like this
function move(elem) {
var left = 0
function frame() {
left++ // update parameters
elem.style.left = left // show frame
if (left == 100) // check finish condition
clearInterval(id)
}
var id = setInterval(frame, 10) // draw every 10ms
}
Well for one it should be
var args = { ... }
assuming you have the elem set up correctly, you're going to need a inline styling of the attribute you want to animate. Also, you're going to need to parse the style since it has the 'px' attached to it, but you can always add that after you do the math within the interval function.
I set up something here so you can mess around with the settings and whatnot.
edit:
http://jsfiddle.net/mb4JA/2/
edit2:
this should be one second
http://jsfiddle.net/mb4JA/4/
final answer ;) http://jsfiddle.net/mb4JA/10/
You should be able to put any speed in there, and have it animate for that amount of seconds.

Categories

Resources