Save mouse location then have a second value that updates - javascript

I'm using the Phaser engine, and I want to have a line be drawn on a click and hold event from the initial mouse position and have it constantly update to draw to the mouse position as it moves. My problem is that when i try to store the initial mouse position it keeps changing. This seems like a simple problem but i'm not very good with this stuff. Here is the code:
var unitLine;
if(game.input.activePointer.isDown) {
const firstX = game.input.x;
const firstY = game.input.y;
unitLine = game.add.graphics(100, 100);
unitLine.beginFill(0xFF3300);
unitLine.lineStyle(10, 0xffd900, 1);
unitLine.moveTo(firstX, firstY);
unitLine.lineTo(game.input.x, game.input.y);
}
that firstX and firstY are changing even when i declare them as a const. Not sure what to do here.

The problem is that you're setting firstX and firstY whenever the mouse isDown, so they're basically overwritten every frame that the mouse is down.
To get around this, try using Phaser's game.input.onDown function:
var game = new Phaser.Game(500, 500, Phaser.CANVAS, 'test', {
preload: preload,
create: create,
update: update
});
function preload() {}
let firstX;
let firstY;
function create() {
game.input.onDown.add(function() {
firstX = game.input.x;
firstY = game.input.y;
}, this);
}
var unitLine;
function update() {
if (game.input.activePointer.isDown) {
unitLine = game.add.graphics(0, 0);
unitLine.beginFill(0xFF3300);
unitLine.lineStyle(10, 0xffd900, 1);
unitLine.moveTo(firstX, firstY);
unitLine.lineTo(game.input.x, game.input.y);
}
}
<script src="https://github.com/photonstorm/phaser-ce/releases/download/v2.11.1/phaser.min.js"></script>
(Also, I had to change the 100, 100 to 0, 0)

It's because you're declaring them in the statement, so the declaration is newly hit each time and the variables are created afresh.
Firsly, you need to create the variables outside of the statement.
And then, to fix your issue, I would use a bool to lock them in.
Something like this:
var unitLine;
var firstX;
var firstY;
var needToset_XY = true;
if(game.input.activePointer.isDown) {
if(needToset_XY){
firstX = game.input.x;
firstY = game.input.y;
needToset_XY = false;
}
unitLine = game.add.graphics(100, 100);
unitLine.beginFill(0xFF3300);
unitLine.lineStyle(10, 0xffd900, 1);
unitLine.moveTo(firstX, firstY);
unitLine.lineTo(game.input.x, game.input.y);
}
This means the firstX and firstY values can't be changed after the first time.
If this is all in a game loop, you'll need to declare the top four variables outside of the loop, otherwise they'll renew themselves each time.

Related

Javascript function draw

var random = randomNumber(1, 7);
var Block = createSprite(200, 0);
var Blocks = createGroup();
Blocks.add(Block);
Blocks.setAnimationEach(random.toString());
createEdgeSprites();
function draw() {
background("white");
Move("down", 4, 1);
Move2("right", 4, 0, "left");
Move2("left", -4, 0, "right");
if (Blocks.isTouching(bottomEdge)) {
var Block = createSprite(200, 0);
Blocks.add(Block);
Blocks.setAnimationEach(randomNumber(1, 7).toString());
}
if (Blocks.isTouching(edges)) {
Blocks.collide(edges);
}
drawSprites();
}
function Move(Key, Velocity, Velocity2) {
if (keyDown(Key)) {
Block.velocityY = Velocity;
} else {
Block.velocityY = Velocity2;
}
}
function Move2(Key, Velocity, Velocity2, Key2) {
if (keyDown(Key)) {
Block.velocityX = Velocity;
} else if ((!keyDown(Key2))) {
Block.velocityX = Velocity2;
}
}
My issue is with
if (Blocks.isTouching(bottomEdge)) {
var Block = createSprite(200, 0);
Blocks.add(Block);
Blocks.setAnimationEach(randomNumber(1, 7).toString());
}
That code is basically for when a piece touches the ground, a new piece is made with a random Animation, that is the shape. I know I have to fix other stuff, but I know how to fix it. The thing I don't know how to do is set an animation once? Is there a code for that, because it constantly randomly changes the animation because the function draw() runs multiple times. But if I put it outside of that function, it only runs in the beginning of the program. Should I like push my sprite back up a little afterwards? I can't use const and I don't even know how to use it. Each time a sprite touches the ground, I want a new sprite to made and that animation is only set once. My biggest issue is that, I know there are other issues in that segment, but I can fix it myself

Javascript - element.addEventListener() returns "Uncaught TypeError"

For context, I am trying to code a memory game where you have to pair two of the same colored circles until the whole board is complete. I've called it Match-Two. Here is the code that I'll reference from:
class Circle {
constructor(element, circleColor){
this.elem = element;
this.color = circleColor;
}
}
var frequency = [0, 0, 0, 0, 0, 0, 0, 0];
var num;
var hue = new Array(8);
var circle = new Array(16);
hue[0] = "#0039ff";
hue[1] = "#ff0000";
hue[2] = "#43ff00";
hue[3] = "#fffa00";
hue[4] = "#7405b5";
hue[5] = "#ff9d00";
hue[6] = "#ff00c3";
hue[7] = "#00fff6";
onload = function() {
for(var i = 0; i < 16; i++){
circle[i] = new Circle(document.getElementById("circle" + i));
while(circle[i].color === undefined){
num = Math.floor(Math.random() * 8);
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
}
}
}
function main(circle){
circle.elem.style.backgroundColor = circle.color;
}
So in this code I create a class of Circle and I create an array of Circle objects which is identified as 'circle'. When the page is loaded, I give each circle object an element reference from my html document (There are 16 circles and they each have an id of circle0, circle1, circle2.. etc. Then there's a small algorithm to ensure there are only two of each color in the matrix so they all have a matching pair. In each iteration of the for loop, I add an event listener to each circle. If the circle is clicked, I want it to change to its color which is stored in color[i].color. However, when I click the circles all it returns is:
Uncaught TypeError: Cannot read property 'elem' of undefined
at main (script.js:39)
at HTMLDivElement.<anonymous> (script.js:31)
Which is referencing:
circle.elem.style.backgroundColor = circle.color;
So I put some console.log() functions in to see what was going on:
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
And this spits out exactly what I expect:
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"11" class=​"circle" id=​"circle1" /div
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"12" class=​"circle" id=​"circle2" /div
script.js:31 #0039ff
script.js:30 div data-brackets-id=​"13" class=​"circle" id=​"circle3" /div
script.js:31 #0039ff
So it returns the element reference and the color of the circle. So then I try putting the "circle[i].elem.style.backgroundColor = circle[i].color" into the event listener and I get the same issue as before...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
Circles without their colors. The console log statements are on the right-hand side with their specific colors as well...
So I gave up and decided to write that exact line of code outside the event listener to see if that works, and it changed all the circle's colors to their specific color...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.style.backgroundColor = circle[i].color;
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
The circles with their specific colors...
There is some problem the event listener not being able to pass the object of a Circle or something... I don't know please help :(
Your problem boils down to the way JS treats var variables - they sort of "leak" into the global scope.
Consider the event listener that you've attached:
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
So, whenever the listener gets triggered, it calls main() function and passes circle[i] into it. But since i is the variable that's leaked outside of the supposed scope, it always has the value of 16 - the value assigned to it during the last iteration of the for loop. That's why the main() function tries to access a style property of undefined - it's the value of circle[16] that was passed into it.
Here's a couple of ways to fix it:
If you can use ES6 let variables:
Use let i instead of var i in your for loop:
for (let i = 0; i < 16; i++) {
//...
}
If not, a classic way with function closure:
function createListener(j) {
return function () {
main(circle[j])
}
}
// and use it in your 'for' loop later:
circle[i].elem.addEventListener('click', createListener(i));
Here's a useful topic that provides more techniques to avoid this: JavaScript closure inside loops – simple practical example

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

Loop object method

i'm trying to call an objects methods at the same time with a loop.
I would create a game with a group of enemies that are move at the same time.
I create an object "Warrior"
function Warrior(x, y) {
// Coordinates x, y
this.x = x;
this.y = y;
this.character = $('<div class="enemy"></div>');
// Inserting the div inside #map
this.character.appendTo('#map');
// assigning x and y parameters
this.character.css({
marginLeft: x + 'px',
marginTop: y + 'px'
});
// walk method that move the div
this.walk = function() {
// Moving div 10px to the right
this.character.css('left', "+=10");
}
}
var enemies = new Array();
enemies[0] = new Warrior(0, 0);
enemies[1] = new Warrior(10, 20);
enemies[2] = new Warrior(60, 80);
for(var i=0;i<enemies.length;i++) {
setInterval(function(){enemies[i].walk()}, 1000);
}
I've created an array "enemies"
and I tried to call walk() methods at the same time
But nothing it happens. I would like that the divs are moving at the same time!
Thanks!
Based on this question
You might want to pass a parameter by value to setInterval:
for(var i=0;i<enemies.length;i++) {
setInterval(
function(index){ //<-- accept parameter here
enemies[index].walk();
}, 1000, i); //<-- pass i here
}
If you do not pass the parameter, x variable changes while loop is running, thus all the functions will be called having x = 2 (because you have 3 elements in your array)
You have two major problems where:
CSS does not allow programmatic modification in the way that javascript does. You modify values in javascript, and then set them in CSS:
this.walk = function() {
var current = parseInt(this.character.css('left')),
stepped = current + 10;
this.character.css('left', stepped);
}
Your loop creates a closure on var i which is only declared once, so the same value will be shared across the functions. You can either have the function take a parameter, and provide it in the setInterval call:
setInterval(function(index) { /* ... */}, 1000, i);
or you can put the loop within the interval function:
setInterval(function() {
for (var i = 0; i < enemies.length; i++) {
enemies[i].walk();
}
});
You might also want to reconsider whether your walk function should modify the CSS directly. You've already forgotten about your x and y coordinate attributes in this code. Perhaps you should have a walk method that modifies these coordinates, and a draw function that updates the sprites' positions based on their current coordinates.

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