Tween.js Not Calling onUpdate Function - javascript

I am using tweenjs and Typescript to change the x and y coordinates of a three.js cube. I created the following tween to change the x position of an item of class "FallingItem".
this.movementArcData.horzTween = new TWEEN.Tween(this.movementArcData.pos.x)
.to(this.movementArcData.newPos.x, this.movementArcData.movementTime * 1000)
.onUpdate(this.updateXPos)
.onComplete(this.horzTweenComplete);
where "this.movementArcData" is an object containing the following:
horzTween - the tween itself
pos.x - the original position of the item
movementTime - the time it takes to complete the movement, 2000 milliseconds
updateXPos - a member function of the a FallingItem object with the following code:
updateXPos(){
this.mesh.position.x = this.movementArcData.pos.x;
console.log("update x: " + this.movementArcData.pos.x);
}
horzTweenComplete - a member funtion of the FallingItem object with the following code:
horzTweenComplete(){
this.movementArcData.horzTweenComplete = true;
}
Neither the updateXPos or horzTweenComplete callback is getting fired.
I am calling TWEEN.update in my render loop like so:
TWEEN.update(dt);
Since the tween's onComplete event never fires, the TWEEN.update is called constantly. What am I missing that is causing the tween not to work properly?

I had a similar case when TWEEN was not calling my onUpdate function. Found out I had to call window.requestAnimationFrame() in order to tell the browser that I, i.e. TWEEN, "want to perform an animation and requests that the browser call a specified function to update an animation before the next repaint."
function animate(time) {
window.requestAnimationFrame(animate);
TWEEN.update(time);
}
new TWEEN
.Tween({ y: 0 })
.to({y: 1000}, 700)
.easing(TWEEN.Easing.Exponential.InOut)
.onUpdate(function () {
window.scrollTo(0, this.y);
})
.start();
animate();
The above example was taken from https://github.com/tweenjs/tween.js/blob/master/examples/00_hello_world.html.

Tween.js needs to be passed the elapsed time, not a delta time. Passing a running elapsed time fixed the problem.
Also, it's supposed to be passed an object containing the value you want interpolated. It looks like passing the value itself doesn't work. I had success with this:
let tweenElement = {
x: this.tweenInfo.pos.x,
y: this.tweenInfo.pos.y,
item: this
}
this.tweenInfo.tweenUp = new TWEEN.Tween(tweenElement)
.to({y : this.tweenInfo.newPos.y}
, this.tweenInfo.movementTime * 0.5 * 1000)
.easing( TWEEN.Easing.Cubic.InOut )
.onUpdate(function(){
this.item.updateYPos(tweenElement, this)
})
.onComplete(function(){
this.item.tweenUpComplete();
});

Another potential cause (I know probably not in this particular case) is that TWEEN.removeAll() is being called somewhere else in the code. One sign of this happening is that other tweens are working perfectly fine but some are not.

Related

How to pass object to animation function in JavaScript?

I have been trying to make a JavaScript animation of moving circle in HTML Canvas without using global variables. I am using requestAnimationFrame function. Since JavaScript does not support passing variable by reference, I tried creating a Circle class:
class Circle{
constructor(x, y, dx, dy) //set initial position and velocity of circle
{
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
}
function moveCircle(circle, other variables)
{
//clear canvas
//calculate new position and velocity using circle.x, etc.
//save new values to the object
//draw new circle into the canvas
requestAnimationFrame(moveCircle);
}
function button()//function called after click on button
{
//initial settings of canvas, intial condition
circle = new Circle(x, y, dx, dy);
moveCircle(circle, other vars);
}
This makes one frame and then throws error "Cannot read property 'x' of undefined". What am I doing wrong? Is there any other way of doing this, while avoiding global variables?
First of all, you don't need to create a class you can just pass the coordinates in an object or as separate arguments.
Secondly you should use Function#bind on requestAnimationFrame to pass the same arguments to next call.
Example using object:
function moveCircle(circle) {
console.log(circle.x);
if (circle.x) {
circle.x -= circle.dx;
requestAnimationFrame(moveCircle.bind(null, circle));
}
}
function button() {
moveCircle({
x: 500,
y: 0,
dx: 20,
dy: 0
});
}
button();
Example without object:
function moveCircle(x, dx) {
console.log(x);
if (x) {
requestAnimationFrame(moveCircle.bind(null, x - dx, dx));
}
}
function button() {
moveCircle(500, 20);
}
button();
To keep things simple you could use a closure to create a handler that internally knows what your circle looks like and how it should move. A closure is simply a function that defines its own variables locally, then returns a function that can access those.
We want to return a function that only accepts one argument: time, since that is the argument passed into every handler in the AnimationFrame by the browser. Then we want the closure function to draw into the globally defined canvas. Have a look here:
const canvas = document.body.appendChild( document.createElement( 'canvas' ) );
function makeMovingCircle( canvas, x, y, radius, distance, duration ){
// Lets get the context to draw here so we only need to fetch it once and store it, saving some computing time in favour of storing into memory.
const ctx = canvas.getContext( '2d' );
// We need to return a named function here so it can interally call itself again in requestAnimationFrame
return function AnimationHandler( time ){
// Lets just calculate an offset here based on the distance and duration we passed in above.
const progress = (time % duration / duration) * distance;
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
ctx.arc( x + progress, y, radius, 0, Math.PI*2, true );
ctx.stroke();
// Now call the named handler for the next animationFrame
window.requestAnimationFrame( AnimationHandler );
}
}
// Now lets make an animation handler and add it to the animationFrame. If you ever want to cancel it, you might want to store it in a global variable though so you can call cancelAnimationFrame on it.
window.requestAnimationFrame(
makeMovingCircle( canvas, 15, 15, 10, 100, 2000 )
);
I did a quick example from my memory:
const circle = $('#circle');
class Main {
constructor() {
}
start() {
requestAnimationFrame(this.loop.bind(this))
}
loop() {
const pos = circle.position();
// speed is a constant 1,1. But you could replace it by a variable
circle.css({top: pos.top+1, left: pos.left+1, position:'absolute'});
requestAnimationFrame(this.loop.bind(this))
}
}
const main = new Main();
main.start();
<div>
<img id="circle" src="https://playcode.io/static/img/logo.png"
alt="PlayCode logo">
<h1 id="msg"></h1>
</div>
#circle {
position: absolute;
top: 0;
left: 0;
}
In js, object are passed by reference, and primitive types by value.
I think you should avoid having other variables in moveCircle function. That kind of function is usually called "loop" or "gameLoop" or "update". When your button is clicked, add a speed to the circle, without creating a new circle, something like myCircle.speed = {x:2, y:2}. In the gameloop, add the speed to the position each frame.
Think also about delta time, as requestAnimationFrame will be faster depending on the PC/Mobile that run it.
You can wrap your application into on Main class if you like. (like I did above)
Finnaly, if you insist passing parameters to moveCircle, you can use bind.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function c(a,b,c) {
console.log(a); console.log(b); console.log(c);
}
c.bind(window, 4,3)
// function c() // bind will return a function ! In a class usually the context that you want to pass is `this`. If you are playing directly in the console, the context is window by default (not that it matter in this example)
c.bind(window, 4,3)()
// 4
// 3
// undefined
The first call to moveCircle (from what I understand, it is in button function), should also be a requestAnimationFrame like requestAnimationFrame(moveCircle.bind(window, other vars)), you could re-use that in moveCircle as well, to avoid any global variable. Still, I recommend making a class to wrap your application, so that you can use local variables to your class to save the current state of your game instead of having that same state living inside the function arguments only.

I'm having a lot of trouble with setInterval and class methods

I keep running into bizarre problems. I've been unable to find anything on them after doing some research, so I thought I'd come here to present them. I have a class which is rather long, but I'll include the relevant bits:
class AnimatedSnake {
constructor(canvasId, coordinates) {
this.coordinates = coordinates;
this.direction = 2;
this.ctx = document.getElementById(canvasId).getContext("2d");
// 0 - .99, describes how far along snake is between coordinates
this.progress = 0;
}
erase() {
for (let i = 0; i < this.coordinates.length; i++) {
let c1 = this.coordinates[i][0],
c2 = this.coordinates[i][1];
this.ctx.clearRect(c1 * 31, c2 * 31, 31, 31);
}
}
next() {
this.progress += 0.01;
if (this.progress >= 1) {
this.progress %= 1;
let nextCoord = this.coordinates[4].slice();
nextCoord[0] += ((this.direction % 2) * this.direction);
nextCoord[1] += ((!(this.direction % 2) * (this.direction / 2)));
this.coordinates.push(nextCoord);
this.coordinates.shift();
}
console.log(this.erase);
this.erase();
this.draw();
}
}
So far, I can call AnimatedSnake.next() indefinitely if I'm doing it manually (i.e. from the console). However, when I put the function in an interval or timeout - setInterval(AnimatedSnake.next, 100) - it all of a sudden, on the first run, claims that AnimatedSnake.erase is not a function. I tried putting AnimatedSnake.erase() directly in the interval, and when I do THAT, for some absurd reason it goes and tells me that it cannot take the length property of AnimatedSnake.coordinates, which it claims is undefined. Nowhere in my code to I redefine any of these things. coordinates is altered, but it should not be undefined at any point. And erase is of course a method that I never change. Does anyone have any insight into why, when these are called with setInterval or setTimeout weird things happen, but if I call the functions repeatedly (even in a for loop) without the JavaScript timing functions everything works out fine? I'm genuinely stumped.
Consider these two snippets:
animatedSnake.next()
And:
let method = animatedSnake.next;
method();
In the first snippet next is called as a member of animatedSnake object, so this within the context of next method refers to the animatedSnake object.
In the second snippet the next method is detached from the object, so this no longer refers to the animatedSnake instance when the method function is invoked. This is how passing a method to another function, like setInterval works. You can either use Function.prototype.bind method for setting the context manually:
setInterval(animatedSnake.next.bind(animatedSnake), 100)
or wrap the statement with another function:
setInterval(() => animatedSnake.next(), 100)

Tween.js | My tween isn't updating even when I loop it

So, I'm creating a game in which I have a ship with a gun that shoots bullets.
I want the gun of the ship to bounce back when I shoot so it looks like the gun actually shot the bullet. Everything is fine so far, the gun is only moving relative to the body, and nothing's been stuck in infinite loops so far.
My ship is made from a constructor I coded that works perfectly, so to create the tweens, instead of making variables inside the constructor that I can't reuse, I made the tweens actual attributes of the object.
This is what the code looks like:
this.gunTweenPosition = {y : unit / -1.52 + (gunLength * unit / -4)};
this.gunTweenTarget = {y : av.gunTweenPosition.y + unit / 6};
this.gunTween1 = new TWEEN.Tween(av.gunTweenPosition.y).to(av.gunTweenTarget.y, 1000);
this.gunTween2 = new TWEEN.Tween(av.gunTweenTarget.y).to(av.gunTweenPosition.y, 1000);
this.gunTween1.easing(TWEEN.Easing.Cubic.In);
this.gunTween2.easing(TWEEN.Easing.Cubic.Out);
this.gunTween1.onUpdate(function() {
this.gun.position.y = av.gunTweenPosition.y;
});
this.gunTween2.onUpdate(function() {
this.gun.position.y = av.gunTweenTarget.y;
});
'this' being the object we're constructing,
and to start the function that will push and pull the gun, I have this function that I just call:
car avatar = this;
var number = 0;
function loopTweenUpdating() {
number++;
if (number < 20) {
avatar.gunTween1.update();
avatar.gunTween1.onComplete(function() {
avatar.gunTween2.update();
});
setTimeout(loopTweenUpdating, 20);
}
}
I can't see what the problem is here.
Click THIS link to see the full code.
Any ideas?
It looks like you are missing to start the tween.
this.gunTween1.start();

How can I transform values inside a function onClick?

Basically, I've imported a WebGL globe and want to make it stop spinning when I click on the globe.
Here's my attempt at making it stop:
setInterval(function() {
var c = earth.getPosition();
earth.setCenter([c[0], c[1] + 0.9]);
earth.onclick.setCenter({c[0], c[1]);
}, 50);
Basically I want to get rid of the +0.9 onClick. If it helps, also adding the function with the earth object below:
<script src="http://www.webglearth.com/v2/api.js"></script>
<script>
function initialize() {
var options = {atmosphere: true, center: [0, 0], zoom: 0};
var earth = new WE.map('earth_div', options);
WE.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg', {
subdomains: '1234',
attribution: 'Tiles Courtesy of MapQuest'
}).addTo(earth);
</script>
I'm guessing my syntax isn't correct with the .onclick. What should it be instead?
Inspecting the examples on webglearth, the onClick is defined as:
earth.on('click', callback)
So you need to define the callback function as:
callback = function(){···}
or in short:
earth.on('click', function(e){···});
If you need to stop the animation, you have to use clearInterval on onClick event
var interval = setInterval(function() {
var c = earth.getPosition();
earth.setCenter([c[0], c[1] + 0.9]);
}, 50);
···
earth.on('click', function(){
clearInterval(interval);
});
According to http://www.webglearth.org/api, it should be earth.on('click', yourCallback());, not .onclick. Also, I would consider some changes to your code:
First off, don't set the onclick handler within the setatimeout function all the time. Do it one time on intialization and use a variable instead of 0.9 inside the timeout that u can set to 0 inside your onclick callback. In case you want to stop animation forever, you might consider breaking out of the setTimeout loop in case you stumble upon that variable being 0.
Im gonna leave implementation up to you

Why object does not see his method?

I am developing a game using the framework atomJS and library libCanvas. Here is the code where the error occurs:
var Planet=atom.Class({
//other code
clearLayer : function (layer) {
layer.ctx.clearRect(this.x, this.y, this.size, this.size);
},
colonize : function (layer, angle, color,ms) {
**this.clearLayer(layer);**
drawArc({
context: layer.ctx,
x: Math.round(this.x + this.size / 2),
y: Math.round(this.y + this.size / 2),
radius: this.radius + 5,
width: 4,
color: color,
opacity: 0.6,
angleFinish: angle
});
if (this.colonizing) {
//if (this.cursorOnPlanet()) this.context.fillText(COLONIZING, (this.x + this.size / 2) - 30, this.y + this.size - 2);
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
if (angle > 360) {
this.colonizing = false;
this.state = 1;
}
} else {
clearTimeout(this.colonizingTimer);
this.clearLayer(layer);
}
},
});
On this line, this.clearLayer(layer); the script terminates with an error Object [object DOMWindow] has no method 'clearLayer'.Tell me please what's the problem?
Thanks!
It's important to see how whateverObject.colonize() is actually getting called. Anyway, it's clear that the original object's method is being bound to a different object before getting called. This is fairly common in event handlers, for example, where this usually (but not always) ends up being the event target, not the method's original object.
It's common for developers to use a closure to ensure that they have a safe reference for the original this. For example, you might define colonize in a constructor that says var self=this;, which would guarantee the name self points to the original this even if this itself gets rebound.
Another approach is to use Function.prototype.bind (which you'd have to polyfill for old JS engines), which creates a new function with a this object guaranteed to be whatever you specify.
It sounds like the function is being called from the DOM window and not the local class. When the this object is a window, you'll inevitably have scoping issues.
Your problem is with the setTimeout function. When the timeout is called, it's telling the DOMWindow, not the local class, to call the function. To fix this, wrap the call into a function.
function(){<code>}
Edit: I'm not really sure of the purpose of the extra fields in the setTimeout, so I omitted my solution. If you wrap whatever you're doing in a function, it should work though.
change
this.colonizingTimer = setTimeout(this.colonize, ms,layer, angle + 5, color,ms);
to
var self = this;
this.colonizingTimer = setTimeout(function(){self.colonize.call(self);}, ms,layer, angle + 5, color,ms);
The thing is that because of the timeout, the this object is removed from your object scope and at execution time refers to the global object(window) which has no method named clearLayer.
Here's a simplified demo to see the difference.
& the most correct way is to use "delay":
this.colonizingTimer = this.colonize.delay(ms, this, [layer, angle + 5, color, ms]);
But, if i understand right you want to animate angle from zero to 360 degrees? Why dont you use "Animatable" & ".animate" ?
With every question about LibCanvas you can send me an email to shocksilien#gmail.com

Categories

Resources