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

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();

Related

Access an instance from a function outside of the object constructor

I have a problem I can't understand after a lot of attempts to solve it.
To help you understand, there are 2 classes (Game and Board), and a third file with the jQuery keypress controls. Game is about the logic of the game, and Board about the display.
Here is a part of the code I hope sufficient to understand.
// GAME CLASS
function Game(width, height) {
this.width = width;
this.height = height;
this.forbiddenPosition = [];
this.chartBoard = this.resetBoard();
this.generateGame();
}
Game.prototype.generateGame = function () {
this.player1 = new Player("Joueur 1", 100, dagger);
this.player2 = new Player("Joueur 2", 100, dagger);
const playerArray = [this.player1, this.player2];
}
Game.prototype.getPlayer1 = function () {
return this.player1;
};
Game.prototype.getPlayer2 = function () {
return this.player2;
};
Game.prototype.switchTurn = function (player1, player2) {
console.log(player1);
console.log(player2);
};
// BOARD CLASS
const ctx = $('#board').get(0).getContext('2d');
function Board (width, height) {
this.width = width;
this.height = height;
this.game = new Game(this.width, this.height);
this.displayInfoPlayers(this.game.getPlayer1(), this.game.getPlayer2());
}
Board.prototype.displayInfoPlayers = function (player1, player2) {
$('.canvas-side__left').css('visibility', 'visible');
$('.canvas-side__right').css('visibility', 'visible');
$('.canvas-side__left').addClass('animated slideInLeft');
$('.canvas-side__right').addClass('animated slideInRight');
$(".canvas-side__left").html("<h2 class='canvas-side--title'>" + player1.name + "</h2><p class='canvas-side--health'>" + player1.health + "</p><p class='canvas-side--health'>" + player1.weapon.name + "</p>");
$(".canvas-side__right").html("<h2 class='canvas-side--title'>" + player2.name + "</h2><p class='canvas-side--health'>" + player2.health + "</p><p class='canvas-side--health'>" + player2.weapon.name + "</p>");
};
// CONTROL
$(document).on('keypress', function (e) {
if (e.which == 13) {
Game.prototype.switchTurn(Game.prototype.getPlayer1(), Game.prototype.getPlayer2());
e.stopPropagation();
}
});
Board class is linked to Game class and so uses this. The control using jQuery code are in a third file and not into a class.
When I press Enter, I get undefined for player1 and 2. I tried different ways to call the getter functions and nothing works. I also tried to put the controls inside the Game file and still nothing.
I get either undefined or getPlayer1() is not a function.
I am looking for a way to call these getter functions from everywhere so I can use player1 and 2 which I need to move on the board.
There are several issues there.
The keypress event handler is using Game.prototype, not an instance of Game. You want to be using an instance of Game you've created and stored somewhere. Game.prototype doesn't have the player1 and player2 properties. They're added to instances of Game by the Game constructor. Nothing ever adds them to Game.prototype (which is correct, they shouldn't be on the prototype).
There's no need for getPlayer1, etc. You can directly access player1 and player2. (It's possible to make player1 and player2 private and only provide accessors for them, but it's a bit complicated at the moment and probably not something you want to take on yet.)
Within Game methods, you need to consistently use this.player1 and this.player2, don't pass the players around.
It seems odd for Board to create an instance of Game. It seems like it should be the other way around.
I suggest stepping back from this task and trying something simpler first (like creating a class, an instance of the class, and using that instance in an event handler), then incrementally adding complexity and making sure at each stage you're clear on what's happening. As you go, you may have more specific questions, which you can post on SO (after thorough research, etc.).
You can do something like this and it should work. Essentially, you prototype the function you're trying to access which is not declared until after the constructor.
class Test {
constructor() {
this.five = Test.prototype.getFive();
}
getFive() {
return 5;
}
}
let test = new Test();
console.log(test.five); // Returns 5

Javascript: using one function on multiple elements

I'm new to JavaScript and I'm having trouble figuring out how to resize multiple elements with one function for my rhythm game. This is for my CSP class and theres no use of jQuery sadly. I'm also limited to the commands that the program (AppLab) I'm using has provided. My goal right now is to make an "animation" of a circle growing to its desired size to indicate that it should be clicked. I need these elements to appear while another one is in the process growing and so on.
I'm aware that my code probably sucks so if there is also a way to simplify or improve it I would love to know.
This is my current program code and the hitIndicator function is the one I'm having the most trouble with:
var circleSizeW = 0;
var circleSizeL = 0;
var score = 0;
hitCircle("hitcircle", 300, 6, 206);
hitCircle("image2", 300, 6, 682);
function circleEffects(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
playSound("47 (1).mp3", false);
hideElement(circleid);
});
}, whentohit);
}
function hitIndicator(circleid, growthRate) {
var xPos = getXPosition(circleid);
var yPos = getYPosition(circleid);
var t = setInterval(function() {
circleSizeW = circleSizeW + growthRate;
circleSizeL = circleSizeL + growthRate;
xPos = xPos - (growthRate/2);
yPos = yPos - (growthRate/2);
showElement(circleid);
setSize(circleid, circleSizeW, circleSizeL);
setPosition(circleid, xPos, yPos);
if (circleSizeW >= 60) {
clearInterval(t);
circleSizeW = 0;
circleSizeL = 0;
}
}, 50);
}
function scoreSystem(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
score = score + 100;
setText("scoreTrack", score);
});
}, whentohit);
}
function hitCircle(circleid, whentohit, growthRate, appearancetime) {
setTimeout(function() {
console.log("hitcircle");
circleEffects(circleid, whentohit);
hitIndicator(circleid, growthRate);
scoreSystem(circleid, whentohit);
}, appearancetime);
My code is nowhere near completion either so there are still many things that needs to be done.
I'm not sure how to have multiple circles running that function at similar times because when I try to fix the errors/change the values of the functions' parameters they sometimes loop twice, infinitely loop, or receive the changed values of the previous circle while the previous circle is still growing.
I am also fairly new with Javascript, but I have a fairly good idea of what should be done here.
I would write an object constructor for instantiating circles.
Scroll down this page to see how to make object constructors.
Then for your circle object add a hitindicator method.
This page covers methods.
Then set up a function that will retrieve and loop through every instantiated circle object and run .hitindicator on each circle. Best way to do this, might be to add every instantiated circle to a circle array, then just loop through the array?
Then have an update() function that calls the function in step 3, and have update() called every "frame" with setInterval.
The pages linked should give you enough information to figure it out from here.

HTML5 Canvas, Having Trouble Running Multiple Animations At Once

I have written code that takes two arrays, both of which contain co-ordinates for a four-cornered shape (effectively a start frame and an end frame), a canvas ID and a time value. The function then calculates dX and dY of each corner and uses window.performance.now() to create a timestamp. Then, on every requestAnimationFrame(), it calculates what the co-ordinates should be by using dX, dY, the old timestamp, a new timestamp and the time value from the function call. It looks like this:
function doAnim(cv, startFrame, endFrame, animTime)
{
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if(startFrame.length != endFrame.length)
{
return('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for(i=1;i<=startFrame.length;i++)
{
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
function draw()
{
this.requestAnimationFrame(draw, cv);
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
this.currentFrame = new Array();
for(i=1;i<=startFrame.length;i++)
{
this.currentFrame[i] = startFrame[i]+(this.animChange[i]*((window.performance.now()-this.timerStart)/animTime));
}
if((window.performance.now()-this.timerStart)>=animTime)
{
this.ctx.beginPath()
this.ctx.moveTo(endFrame[1], endFrame[2]);
this.ctx.lineTo(endFrame[3], endFrame[4]);
this.ctx.lineTo(endFrame[5], endFrame[6]);
this.ctx.lineTo(endFrame[7], endFrame[8]);
this.ctx.fill();
return;
}
else
{
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
draw();
}
The goal is to have multiple animations of objects happening at once. I took the whole co-ordinate approach because I want the objects to appear as if they are coming from the horizon, creating a fake 3D perspective effect (all objects' starting frames would be a single point at the center of the canvas), and I do not want to warp the objects' textures.
Well, it works great for a single animation, but if I try to start a new animation on a completely different canvas while the first one is running, then the first animation stops dead in its tracks.
As you can see from my JS, I've tried getting around this with gratuitous use of this (I do not fully understand how this works yet, and every explanation I've read has left me even more confused), but it has not worked. I also tried a horrific approach which stored all the functions' own variables in one global array (the first time the function runs, all the variables are put in entries 1-30, the second time they're put in 31-60, etc). Unsurprisingly, that did not work either.
Here is a JSFiddle so you can see this scenario for yourself and play with my code. I am officially out of ideas. Any help will be greatly appreciated.
Like markE linked too, trying to call requestAnimationFrame multiple times won't work.
Instead you make multiple objects and then call some sort of function on them each frame.
I have created an example using your code:
https://jsfiddle.net/samcarlin/2bxn1r79/7/
var anim0frame1 = new Array();
anim0frame1[1] = 0;
anim0frame1[2] = 0;
anim0frame1[3] = 50;
anim0frame1[4] = 0;
anim0frame1[5] = 50;
anim0frame1[6] = 150;
anim0frame1[7] = 0;
anim0frame1[8] = 150;
var anim0frame2 = new Array();
anim0frame2[1] = 200;
anim0frame2[2] = 200;
anim0frame2[3] = 300;
anim0frame2[4] = 250;
anim0frame2[5] = 300;
anim0frame2[6] = 300;
anim0frame2[7] = 200;
anim0frame2[8] = 250;
//Call global
animations = [];
requestAnimationFrame( GlobalStep );
function GlobalStep(delta){
//Functions called by request animation frame have the new time as an argument
//so delta should be approximately the same as window.performance.now()
//especially in realtime applications, which this is
//Check if we have any animation objects
if(animations.length > 0){
//Iterate through and call draw on all animations
for(var i=0; i<animations.length; i++){
if(animations[i].draw(delta)){
//Basically we have it so if the draw function returns true we stop animating the object
//And remove it from the array, so have the draw function return true when animation is complete
animations[i].splice(i, 0);
//We removed an object from the array, so we decrement i
i--;
}
}
}
//And of course call requestAnimationFrame
requestAnimationFrame( GlobalStep );
}
function AnimationObject(cv, startFrame, endFrame, animTime){
//Add this object to the objects arrays
animations.push(this);
//We need to store start and end frame
this.startFrame = startFrame;
this.endFrame = endFrame;
this.animTime = animTime;
//Your code
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if (startFrame.length != endFrame.length) {
return ('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for (i = 1; i <= startFrame.length; i++) {
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
}
//This adds a function to an object, but in such a way that every object shares the same function
//Imagine a kitchen, each object is a person, and this function is a spoon
//by defining this function in this manner Object.prototype.function_name = function(arguments){}
//We make it so one function definition is needed, essentially allowing all the people to share one spoon,
//the 'this' variable still refers to whichever object we call this method, and we save memory etc.
AnimationObject.prototype.draw = function(newTime){
//I added this to start frame so we get what we stored earlier
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.currentFrame = new Array();
for (i = 1; i <= this.startFrame.length; i++) {
this.currentFrame[i] = this.startFrame[i] + (this.animChange[i] * ((newTime - this.timerStart) / this.animTime));
}
if ((newTime - this.timerStart) >= this.animTime) {
this.ctx.beginPath()
this.ctx.moveTo(this.endFrame[1], this.endFrame[2]);
this.ctx.lineTo(this.endFrame[3], this.endFrame[4]);
this.ctx.lineTo(this.endFrame[5], this.endFrame[6]);
this.ctx.lineTo(this.endFrame[7], this.endFrame[8]);
this.ctx.fill();
return;
} else {
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
Notes:
Everytime you press the button a new object is added and simply overwrites previous ones for each frame, you should implement your program so that it checks if a specific animation has already started, you could also use the builtin mechanism to stop animation when complete (read the comments in the code)
You also need to change the on button click code
<button onclick="new AnimationObject('canvas1', anim0frame1, anim0frame2, 3000);">
Lastly if you have further questions feel free to contact me

Tween.js Not Calling onUpdate Function

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.

Drawing to canvas acting buggy

I've recently started working on a side project, and experimenting with the canvas as I go. So let me explain what I want to happen:
In my JS, the main() function is set to an interval. During main() the drawStep() function is called. This does several things, or it should.
Sorry for the long explanation.
Clear the canvas, so that what is drawn doesn't stick.
Using a for loop, iterate through the array containing things that need to be drawn. currently only menu objects.
checking if the variable debug is true, if so, draw the mouse coordinates as two red lines.
However, when step 1 is done, drawing the menu objects fails, it flickers and the canvas clears to whatever color step 1 set the canvas to. When the debug var is set to true (through the console) Drawing the debug lines functions fine.
Here is the code blocks that matter.
Variables defined in an init() function, including the cookie cutter function for defining menu objects:
canvas = document.getElemntByID("canvas");
room = canvas.getContext("2d");
gameMode = 1; // telling the code that it is in the main menu
debug = false; //not drawing the cursor coordinates
menObj = new Array(); //an array of menu objects
mouse_x = 0; //variable set through onMouseMove event in the canvas
mouse_y = 0; //variable set through onMouseMove event in the canvas
drawList = {}; //array of menu object draw() functions, and any other item that
needs to be drawn during the main loop
function menu(mxpos,mypos,mwid,mhid,name, funct,drawing){
this.x = mxpos;
this.y = mypos;
this.width = mwid;
this.height = mhid;
this.value = name;
this.doing = funct;
this.canDraw = drawing; //the code relies on a gameMode variable, only drawing what is allowed to when the game mode is set correctly.
this.expand = 0; //not important, but was going to make buttons expand on mouse over
this.maxExpand = 10; // same as above
//The draw function passed on to the drawList array:
this.draw = function(){
if (this.canDraw == gameMode){
room.fillStyle = "rgba(150,150,150,1)";
room.strokeStyle = "rgba(200,200,200,1)"
room.fillRect(this.x-this.width/2,this.y-this.height/2,this.width,this.height);
room.strokeRect(this.x-this.width/2,this.y-this.height/2,this.width,this.height);
room.strokeStyle = "rgb(30,150,90)";
var xoff = room.measureText(this.value).width;
var yoff = room.measureText(this.value).height;
room.strokeText(this.value,this.x-xoff/2,this.y-yoff/2);
}
}
}
Sample menu object creation and the for loop that adds that objects draw event to the drawList array:
var temMenVal = new menu(width/2,height/5,96,32,"Start",function(){gamemode = 1},0)
menObj.push(temMenVal);
for(var mobj in menObj){
if (!menObj.hasOwnProperty(mobj)) continue;
drawList[mobj]=menObj[mobj].draw(); //push wasn't working, so I improvised.
}
Main function, called from an interval timer.
function main(){
drawStep();
}
This is the draw function, where my issue is:
function drawStep(){
//the latest attempt at a fix, instead of using a clearRect(), which failed.
//I tried this
room.save()
room.fillStyle="black";
room.fillRect(0,0,width,height);
room.restore();
for (var n in drawList){
room.save();
if (!drawList.hasOwnProperty(n)) continue;
if (n<drawList.length){
drawList[n](); //calling the draw() from the nested menu object, it DOES work, when the above clear to black is NOT called
}
room.restore();
}
if (debug == true){
room.beginPath();
room.strokeStyle="rgb(255,0,0)";
room.moveTo(mouse_x,0); //variable set through onmousemove event in the canvas
room.lineTo(mouse_x,height);
room.moveTo(0,mouse_y); //variable set through onmousemove event in the canvas
room.lineTo(width,mouse_y);
room.stroke();
room.closePath();
}
}
I can't figure out why it keeps clearing to black, when the menu objects SHOULD be drawn after the clear. Like I said way up there, setting debug to true DOES draw the cursor coordinates correctly.
When you set up your draw list, try removing the two parens in menObj[mobj].draw()
What it seems like is that you are actually calling the method instead of passing it as a variable.
Try init menu with drawing = 1 and edit code as Hylianpuffball point out. I think this.canDraw == gameMode is always false.

Categories

Resources