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);
}
}
Related
I'm new to this and can't find a way to do it.
I have 4 images but can't set a rule that moves changes correctly that slideshow image.
Example: 1 - 2 - 3 - 4, if I go from 1 and click to go to 3, timer makes me go to 2 because it was in 1.
onload = start;
function start(){
var i = 1;
function Move(){
i = (i%4)+1;
document.getElementById('i'+i).checked = true;
}
setInterval(Move,10000);
}
You need to keep the current element index out of the start function and change it when you select an element manually:
onload = start;
var currIndex = 1;
function start() {
function Move(){
currIndex = (currIndex % 4) + 1;
document.getElementById('i' + currIndex).checked = true;
}
setInterval(Move,10000);
}
// better add a class here for simpler selector
document.querySelectorAll('#i1, #i2, #i3, #i4').addEventListener('whatever-event-you-get-on-selection', function() {
currIndex = yourSelectedId; // use data attributes or something for easier index extraction
});
I'm tinkering with my keyboard lighting using a node wrapper. The included fade function uses a huge array with values from [0,0,0] to [255,255,255] then finds the start and end points and just iterates the array. That means that when fading from blue [0,0,255] to black [0,0,0] it actually passes through other colors instead of just counting down the blue value down from 255 to 0. So all fades between colors are just a mess and look horrible.
So I created my own fade function like this:
function ledSet(led, r, g, b, timeout) {
setTimeout(function() { cue.set(led, r, g, b); }, timeout);
}
function difference(array1, array2) {
var max = 0;
for(var i in array1) {
for(var j in array2) {
var value = Math.abs(array1[i]-array2[j]);
if(value > max) {
max = value;
}
}
}
return max;
}
function fadeLed(key, from, to, duration) {
var steps = difference(from, to);
var interval = duration/steps;
for(var i = 1; i <= steps; i++) {
var rgb = [];
for(var j in from) {
var change = Math.abs(from[j]-to[j])/steps*i;
var value = Math.ceil((from[j] > to[j]) ? from[j]-change : from[j]+change);
rgb.push(value);
}
ledSet(key, rgb[0], rgb[1], rgb[2], interval*i);
}
}
fadeLed('Enter', [0,0,255], [0,0,0], 500);
It works great on a single or few LED's, but is insanely slow for multiple LEDs (like changing colors for the entire keyboard). I'll try optimizing using an option to set multiple LEDs with one command (if multiple LEDs share the same start and end color), but I'm wondering if there is a more mathematically efficient way to calculate the needed values for fading between two RGB colors.
Thanks!
You are scheduling a lot of timeouts that way.
What you can do alternatively is to only schedule one timeout function and schedule another one from within that function.
To make a simple example - instead of something like this:
function f(i) {
setTimeout(() => console.log(i), i * 100);
}
for (let i = 0; i < 10; i++) {
f(i);
}
you can do something like this:
function f(i, m) {
if (i < m) {
console.log(i);
setTimeout(() => f(i + 1, m), 100);
}
}
f(0, 10);
Some other things that you can do:
precompute the values before you start
optimize the steps to not make too much of them
have one function in fixed intervals that runs multiple other functions
use a polyfill of requestAnimationFrame to make some consistent intervals
I have a circle, consisting of 12 arc segments and I want to allow the user to see the transition from the start pattern to the end pattern. (there will be many start and end patterns).
Here is my code so far:
http://codepen.io/blazerix/pen/jrwNAG
function playAnimations(){
var totalLength = document.getElementsByClassName("container")[0].children.length
for(var i = 0; i < totalLength; i++){
var current_pattern = document.getElementsByClassName("container")[0].children[i]
console.log(current_pattern)
for(var j = 0; j < 12; j++){
$('#LED' + (j+1) ).css('transition-duration', '0s');
$('#LED' + (j+1) ).css({fill: current_pattern.children[1].children[j].style.backgroundColor});
}
setTimeout(function () {
for(var k = 0; k < 12; k++){
$('#LED' + (k+1) ).css('transition-duration', "" + current_pattern.children[3].children[0].value + "ms");
$('#LED' + (k+1) ).css({fill: current_pattern.children[2].children[k].style.backgroundColor});
}
}, 150);
}
}
The outer for loop goes through all of the patterns, and the two inner for loops will go through the start and end pattern respectively. For some reason, my program only displays the animation of the very last pattern. I suspect this is because the code is executing really quickly - however I am unsure of how to fix this.
Does anyone know a good workaround or what I could possibly do to rectify this issue? Any feedback or help is appreciated.
Ok, not entirely understanding all the parts of your code, I've whipped this up. It doesn't work just yet, but you may get the idea of what I'm trying to do: wait 250 milliseconds before you fire off the next animation, once you run out of siblings, bounce to the other animation. I can't spend any more time on this, but I hope this gets you where you want to be:
function playAnimations() {
var $patternHolder = $(".container");
playAnimation($('#LED1'), 0, $patternHolder, 1, 1);
}
function playAnimation($target, index, $patternHolder, childPatternIndex, animationNumber) {
//just set both fill color and transition in the object you pass in:
//.eq() chooses a child, returns jQuery object of that child by index
//Use jQuery to get background-color style
$target.css({ fill: $patternHolder.children(childPatternIndex).children().eq(index).css("background-color"), transition: "0s" });
setTimeout(function () {
if ($target.parent().next().length > 0) {
playAnimation($target.parent().next(), index++);
} else if (animationNumber == 1) {
playAnimation($("#LED1"), 0, patternHolder, 3, 2);
}
}, 250);
}
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]);
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.