Why is this 'for' loop not working? - javascript

I have a Javascript object called TweenManager which contains an array of Tween objects. The TweenManager should call the step() method on each tween in the 'tweens' array and all the tweens should run at the same time.
However, what's actually happening is that the TweenManager only runs one tween at a time, and doesn't start the next one until the previous tween is complete.
Here's the code for the tween manager
UPDATE: It might make more sense to look at it here
//Manage all tweens
function TweenManager(){
this.tweens = new Array();
this.timer;
this.start = function(){
this.timer = setInterval(this.run, 1, this);
}
// Loop through all tweens and call the step method
this.run = function(myself){
console.log(myself.tweens.length);
// stop the interval if the tween array is empty
if(myself.tweens.length == 0){
clearInterval(myself.timer)
}
// loop through all tweens and call the step() method
// !! Here's there the problem appears to be
for(i = 0; i < myself.tweens.length; i++){
thisTween = myself.tweens[i]
console.log(thisTween.element.attr('id'));
thisTween.step() // if I remove this, the line above logs the id's as expected
// clean up if the tween is complete
if(thisTween.t == thisTween.d){
myself.tweens.splice(i, 1);
}
}
}
this.addTween = function(b,c,d,element,suffix, decimal){
this.tweens.push( new Tween(b,c,d,element,suffix, decimal) )
}
}
The problem appears to be in the for loop. I have a hunch that this might have something to do with passing in this in the setInterval, although it's just a hunch, I don't understand what the problem could be. I get confused with variable scopes and whatnot.
Here's the Tween Object (Yup, ripped off form Robert Penner)
// Tween a number, add a suffix and insert it into an element
function Tween(b, c, d, element, suffix, decimal){
this.t = 0;
this.c = c;
this.d = d;
this.b = b;
this.element = element;
this.suffix = suffix;
this.step = function(){
if(this.t != this.d){
this.t += 1
var flip = 1
if (this.c < 0) {
flip *= -1
this.c *= -1
}
i = flip * (-Math.exp(-Math.log(this.c)/this.d * (this.t-this.d)) + this.c + 1) + this.b
if(!decimal){
this.element.html(Math.round(i) + this.suffix)
}else{
output = (Math.round(i * 10) / 10 + this.suffix)
formattedOutput = ( output - Math.round(output) == 0 ) ? output + ".0" : output;
this.element.html(formattedOutput)
}
}
}
}
And here's the implementation
tweenManager = new TweenManager();
tweenManager.addTween(0,80,300, $("#el1"), "°", false)
tweenManager.addTween(0,60,400, $("#el2"), "’", false)
tweenManager.addTween(0,12.5,300, $("#el3"), "", true)
tweenManager.start()
As always, any help, hinting or nudging the the right direction is greatly appreciated.

I think the problem is that you are trying to use setInterval as some sort of fork() function which means that you should moving it from where it is to put it on the step itself so that you call:
setInterval(thisTween.step, 1, ...
That is how you can make your tweens run in fake 'parallel'.
However what I really think you want is the new HTML5 Web Workers feature; I think that is for exactly this kind of activity.

Related

How to create animation class library for p5js

I am working on an application where I'd like to provide overlays of different animations onto a range of videos using p5js. I'm looking to organize my classes of animation types so that each animation has a similar structure to update and destroy objects during each loop. My plan is to have an array of animations that are currently "active" update them each iteration of the loop and then destroy them when they are completed. I built a class to fade text in this manner but I'm getting some weird flashy behavior that seems to occur every time a new animation is triggered in the middle of another animation. I've been trying to debug it but have been unsuccessful. Do you have any suggestions as to:
(1) if this is due to my code structure? (and maybe you have a suggestion of a better way),
or
(2) I'm doing something else incorrectly?
Here is the code:
// create an array of currently executing animations to update
// each animation class needs to have one function and one attribute:
// (1) update() -- function to move the objects where ever they need to be moved
// (2) done -- attribute to determine if they should be spliced out of the array
var animations = [];
//////////////////////////////////////////
// Global Variables for Animations //
//////////////////////////////////////////
let start = false;
let count = 0;
function setup(){
let canv = createCanvas(1920, 1080);
canv.id = "myP5canvas";
background(0);
}
function draw(){
background(0);
// Check things to see if we should be adding any animations to the picture
var drawText = random(100);
if (drawText > 98) {
//if (start == false) {
let r = 255;
let g = 204;
let b = 0;
let x = random(width-10);
let y = random(height-10);
animations.push(new TextFader("Wowwwzers!", 100, 'Georgia', r, g, b, x, y, count));
start = true;
count += 1;
}
// Update animations that exist!
for (var i=0; i < animations.length; i++) {
// update the position/attributes of the animation
animations[i].update();
// check if the animation is done and should be removed from the array
if (animations[i].done) {
console.log("SPLICE: " + animations[i].id);
animations.splice(i, 1);
}
}
}
// EXAMPLE ANIMATION
// TEXT FADE
let TextFader = function(words, size, font, red, green, blue, xloc, yloc, id) {
this.id = id;
console.log("create fader: " + this.id);
// translating inputs to variables
this.text = words;
this.size = size;
this.font = font;
// To Do: separating out each of the values until I figure out how to fade separately from the color constructor
this.red = red;
this.green = green;
this.blue = blue;
this.xloc = xloc;
this.yloc = yloc;
// Maybe add customization in the future for fading...
this.fade = 255;
this.fadeTime = 3; // in seconds
this.fadeIncrement = 5;
// Variables to use for destruction
this.createTime = millis();
this.done = false;
}
TextFader.prototype.update = function() {
// Update the fade
// If the fade is below zero don't update and set to be destroyed
this.fade -= this.fadeIncrement;
if (this.fade <= 0) {
this.done = true;
} else {
this.show();
}
}
TextFader.prototype.show = function() {
textFont(this.font);
textSize(this.size);
fill(this.red, this.green, this.blue, this.fade);
text(this.text, this.xloc, this.yloc);
console.log("Drawing: " + this.id + " fade: " + this.fade + " done: " + this.done);
}
Yay, I've got you an answer! It works like expected when you reverse the for loop that loops over the animations.
Because you splice elements of the same array inside the loop, some elements are skipped. For example; animations[0].done = true and gets removed. That means that animations[1] is now in the spot of animations[0] and animations[2] is now in the spot of animations[1].
The i variable is incremented to 1, so on the next loop, you update animations[1] (and skip the animation that is now in animation[0]).
When you reverse the loop, everything before the element you splice stays the same and nothing is skipped.
For example; animations[2].done = true and gets removed. That means that animations[1] is still in the spot of animations[1].
The i variable is decremented to 1, so on the next loop, you update animations[1] and don't skip any elements.
// Update animations that exist!
for (var i = animations.length - 1; i >= 0; i--) {
// update the position/attributes of the animation
animations[i].update();
// check if the animation is done and should be removed from the array
if (animations[i].done) {
//console.log("SPLICE: " + animations[i].id);
animations.splice(i, 1);
}
}

Javascript Animation on scroll, Vanilla JS

I want to use this code that i have seen on github, but I don't know how to apply this code on my HTML, to have an scrolling effect.
The point is, I don't know how to run use this code
source https://gist.github.com/andjosh/6764939
document.getElementsByTagName('button')[0].onclick = function () {
scrollTo(document.body, 0, 1250);
}
function scrollTo(element, to, duration) {
var start = element.scrollTop,
change = to - start,
currentTime = 0,
increment = 20;
var animateScroll = function(){
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, start, change, duration);
element.scrollTop = val;
if(currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
//t = current time
//b = start value
//c = change in value
//d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
First you have to replace document.body with document.documentElement, as document.body.scrollTop() has been deprecated.
Edit: it seems that I was not completely right about document.body.scrollTop() being deprecated. The best solution to support multiple browsers is to check for both cases.
Second, you need to set a value > 0 for the 'to' parameter, as Quantastical already pointed out.
Also make sure you have a <button> element. It should work then.

How to properly use setTimeout with immediately invoked function?

I'm making a game where if the player hits the enemy from top, after 1 sec period,(that is to show dying animation), the enemy will splice out of the array.
It works fine while killing each enemy one by one, but when two enemies get killed at the same time, a problem occurs.
For example, if the enemies were in position 2 and 3 of the array when killed. After splicing it, the position 3 comes to position 2.
The second splice doesn't work as the position is already changed.
Is there a fix to this or a different method, or is my logic just plain invalid.
for (var i = 0; i < enemies.length; i++) {
var collWithPlayer= that.collisionCheck(enemies[i], player);
if (collWithPlayer == 't') { //kill enemies if collision is from top
enemies[i].state = 'dead';
player.velY = -((player.speed));
score.totalScore += 1000;
score.updateTotalScore();
//immediately-invoked function for retaining which enemy died
(function(i){
setTimeout(function() { //show squashed enemy for a brief moment then splice
enemies.splice(i, 1);
}, 1000);
})(i);
So what I did was use a filter function on the enemy array that returns a new array containing only enemies that are still alive, or have only been dead for a little while.
Creating a delay between 'dead' and 'remove' can be done with a 'decay' property on an object. You can update/increase the value of this decay-property on every game-tick.
// inside a gametick loop
var enemyCollisions = [];
enemies = enemies.filter(function (item) {
collisionWithPlayer = that.collisionCheck(item, player);
if (collisionWithPlayer === 't') {
item.state = 'dead';
item.decay = 0;
enemyCollisions.push({
direction: collisionWithPlayer,
with: item
});
}
if (typeof item.decay === 'number') {
item.decay = item.decay + 1;
}
return (item.state !== 'dead' && item.decay > 62);
});
enemyCollisions.forEach(function (item) {
if (item.direction === 't') {
player.velY = -((player.speed));
score.totalScore += 1000;
score.updateTotalScore();
} else {
//TODO deal with collisions other then 't'
}
});
Use a reverse for loop.
for (var i = enemies.length; i--;){
// your stuff here
// hopefully the timeout isn't necessary, or this still has a chance of not working, considering race conditions
enemies.splice(i, 1);
}
// if it is, do the timeout outside of the for loop
That way, when you splice, you splice behind you instead of in front of you.
You could also filter the array like below.
function myfunc(){
var enemies = [1,2,3,4,5];
var elementToRemove = 3;
enemies = enemies.filter(function(val){
return (val !== elementToRemove ? true : false);
},elementToRemove);
alert('[' + enemies.join(' , ') + ']');
}
<button id="btn" onclick="myfunc();">Go</button>
You could simply capture the actual enemies[i] object instead of i to remove it correctly from the array once the post-mortem dislay is done no matter what the index will be at that time:
(function(e){
setTimeout(function(){
enemies.splice(enemies.indexOf(e), 1);
}, 1000);
})(enemies[i]);

Queue function call in javascript

I have functions that contain timed animation and i want these functions to run one after the other just when the previous one is done.
function a() {
var i = 0;
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
return true;
}
i++;
}, 1000);
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b());
I tried one here but it's not working. Here's the fiddle.
What i want to achieve is like this:
a0
a1
a2
a3
Done #3
b0
b1
Done 2 #1
but it stops at Done #3. Im looking for any other ways to achieve this.
If you want to call then method – function must return promise, after then you don’t want to immediate call b() but pass reference to it a().then(b);
function a() {
var i = 0;
var defer = $.Deferred();
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
defer.resolve('asdasd');
}
i++;
}, 1000);
return defer;
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b);
jsfiddle: http://jsfiddle.net/Lungx/3/
Two simpler approaches that should generally work, though maybe not in your case.
(1) jQuery. If you're using the jQuery animate() function, then you can set each animation in the callback for the animation previous. That way when animation n finishes, your callback function will start the animation for n+1, etc. There is a discussion and a couple examples here.
(2) If you're animating (or transitioning, really) CSS, you can attach a handler to the transitionend event. That way, similar to the first approach, you can have the next transition begin as soon as the previous transitionend event fires. See here, especially under the header "Detecting the completion of a transition."
I've used the first approach before, and I don't see any reason why the second shouldn't work. I hope this helps. Let me know if it doesn't work; I'm curious.

Javascript stop repeating collision detection in canvas

I'm trying to do some simple collisions in javascript and html5 canvas. So far I have this:
checkCollision = (function(){
var l, i, ni,dis = 0;
var ob1,ob2 = {};
return function(){
//collisions is the array holding all the objects
l = collisions.length;
i = 0;
ni = 1;
while(i<l){
//grab the first object
ob1 = collisions[i];
while(ni<l){
//get the object to check against
ob2 = collisions[ni];
//find the distance between the two
dis = Math.sqrt(Math.pow((ob1.pos[0]-ob2.pos[0]),2)+Math.pow((ob1.pos[1]-ob2.pos[1]),2));
//rad is the radius
if(ob1.rad+ob2.rad >= dis){
console.log("collision")
}
//move forward second increment
ni++;
}
i++;
//keep one place ahead
ni=i+1;
}
};
})();
I did it without any help of any kind, but now I guess my brain is too much mush to figure this last part out. The collision is happening every frame, which I don't want. I just want it to fire once when the collision first happens. I've tried by giving each object a collide variable that's true if there's already a collision but it's not working very well. Some it fires once and some it fires constantly.
Does anyone have any pointers?
What do you mean by "happening every frame"? The algorithm seems ok, but there doesn't seem to be any conesquence from the collision, it is just logged. Do you want to break out of the loop?
Some comments that have nothing to do with your issue but might make your code a bit more readable and concise:
> checkCollision = (function(){
> var l, i, ni,dis = 0;
> var ob1,ob2 = {};
I don't know why you initialise dis and ob2, they are assigned values later. Using a closure like this means that the values persist to subesquent calls until new values are assigned. Is the closure really needed? It may be a performance hit for animations like this.
> ni = 1;
> while(i<l){
You can put ni after the while, like this:
while(i < l){
ni = i + 1;
and get rid of the last ni = i + 1. You can also do:
> ob1 = collisions[i++];
and get rid of the last i++;, an do the same for ni when assigning to ob2.
Doing the above reduces the number of lines of code so it is more digestable, e.g.
function checkCollision() {
var ni, dis, ob1, ob2;
//collisions is the array holding all the objects
var l = collisions.length;
var i = 0;
while (i < l) {
ni = i + 1;
//grab the first object
ob1 = collisions[i++];
while (ni < l) {
//get the object to check against
ob2 = collisions[ni++];
//find the distance between the two
dis = Math.sqrt(Math.pow((ob1.pos[0] - ob2.pos[0]), 2) +
Math.pow((ob1.pos[1] - ob2.pos[1]), 2));
//rad is the radius
if (ob1.rad + ob2.rad >= dis) {
console.log("collision");
// And what else?
}
}
}
}

Categories