So I am building a small game, and one of the characters is this Golem object:
function Golem(){
var self = this;
this.width = 470;
this.height = 360;
this.drawX = canvasEntities.width/3;
this.speed = 30;
this.isLeftKey = false;
this.isRightKey = false;
this.isSpacebar = false;
this.spritesheet = spritesheetgolemleft;
this.animate = function(){
requestAnimFrame(self.animate);
//this console log returns undefined
console.log(self.spritesheet)
this.spritesheet.update();
self.spritesheet.draw(self.drawX,canvasEntities.height/3);
}
}
the spritesheetgolemleft variable was already defined at the very top(global):
var spritesheetgolemleft = new SpriteSheet('images/golem_walkleft.png',470,360,3,6);
And here is the SpriteSheet class:
function SpriteSheet(path, frameWidth, frameHeight, frameSpeed, endFrame){
var image = new Image();
var framesPerRow,
currentFrame = 0,
counter = 0;
//# of frames after image loads
var self = this;
image.onload = function(){
framesPerRow = Math.floor(image.width/frameWidth);
};
image.src = path;
this.update = function(){
if(counter == (frameSpeed - 1))
currentFrame = (currentFrame + 1) % endFrame;
counter = (counter + 1) % frameSpeed;
}
this.draw = function(x,y){
var row = Math.floor(currentFrame / framesPerRow);
var col = Math.floor(currentFrame % framesPerRow);
//draw image into the Entities canvas
ctxEntities.drawImage(
image,
col * frameWidth, row*frameHeight,
frameWidth, frameHeight,
x,y,
frameWidth, frameHeight);
};
};
The error I am getting happens in the second to last line of the Golem() object:
this.spritesheet.update();
It is giving me a TypeError, cannot read property 'update' of undefined. Thinking it was some sort of issue with scopes, I added the self = this hack at the top, but it still does not work. What am I doing wrong? Thanks in advance.
It turns out that I was instantiating an object before creating the spritesheets. My mistake :/
Related
based on this question
How to use LUT with JavaScript?
I tried to use this code
http://jsfiddle.net/gxu080ve/1/
var img = new Image,
canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
src = "http://i.imgur.com/0vcCTK9.jpg?1"; // insert image url here
img.crossOrigin = "Anonymous";
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage( img, 0, 0 );
// alternateImage(img, canvas, ctx);
localStorage.setItem( "savedImageData", canvas.toDataURL("image/png") );
loadNew();
}
img.src = src;
// make sure the load event fires for cached images too
if ( img.complete || img.complete === undefined ) {
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
img.src = src;
}
var imgLut = new Image,
canvasLut = document.getElementById("myCanvasLut"),
ctxLut = canvasLut.getContext("2d"),
srcLut = "https://gm1.ggpht.com/nX2ZUYrMwPSXu5zGeeoMKyZP_R4nE205ivAdc3_yaccMEy8QYInfY_ynUB-NmrjvPKn0i1k7bdtHyk3Ul99ndvjvoCASud8FdIdq1fRrqDOCGK01rXgZZQ34ATKvtrkoysUCBmTUG70ZW_-TQxHExbu8gjhH-haIg0EuiWgJSxkL45jE1B4xWaOQNQXgJtmb7i1bSVcRgdmJq0XbttjsZnZn3YTW_LYw_3F-WyEEryTRritkZm6CW6NgaoVUfGH6XIaHp5Igs_dzm01lci9XwvoUwS0KA85w3npkjseL0zZX6u-pYWbSXOzkTLDJDMKPpOPt1VH6UUwARlD1YH1dQb0qdq4FrN8_beghJc00UaO9WHgyLyQ-NXMXFt5zXpeKuWtGwWtB0bzDhEvUXUhoDeOwaTbHlEjv3NgrqfzzpLBfLMM9J2BZLZodaEFA6WiroIsq70Qh6g_yMCVg02oi3s-L_2SSW2duayIIcfljyOxmC5AHbjzS2i-4RnKlVzK5Ge39wmiXX_4wtL0nb5XeDPwGbqqJsCPaeIYFN7z43HW6bA--5E3pUo3mjxLPTMSa8T-omZIw7w=w1896-h835-l75";
function loadNew(){
imgLut.crossOrigin = "Anonymous2";
imgLut.onload = function() {
canvasLut.width = imgLut.width;
canvasLut.height = imgLut.height;
ctxLut.drawImage( imgLut, 0, 0 );
filterImage(img, imgLut, canvasLut, ctxLut);
localStorage.setItem( "savedImageDataLut", canvasLut.toDataURL("image/png") );
}
imgLut.src = srcLutbyte;
// make sure the load event fires for cached images too
if ( imgLut.complete || imgLut.complete === undefined ) {
imgLut.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
imgLut.src = srcLutbyte;
}
}
function alternateImage(img, canvastoapply, ctxToApply){
//var c=document.getElementById("myCanvas");
//var img=document.getElementById("scream");
ctxToApply.drawImage(img,0,0);
var imgData=ctxToApply.getImageData(0,0,canvastoapply.width,canvastoapply.height);
// invert colors
for (var i=0;i<imgData.data.length;i+=4){
imgData.data[i]=255-imgData.data[i];
imgData.data[i+1]=255-imgData.data[i+1];
imgData.data[i+2]=255-imgData.data[i+2];
imgData.data[i+3]=255;
}
ctxToApply.putImageData(imgData,0,0);
};
function filterImage(img, filter, canvastoapply, ctxToApply){
//var c=document.getElementById("myCanvas");
//var img=document.getElementById("scream");
// ctxToApply.drawImage(img,0,0);
var lutWidth = canvasLut.width;
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var filterData=ctxToApply.getImageData(0,0,canvastoapply.width,canvastoapply.height);
// invert colors
for (var i=0;i<imgData.data.length;i+=4){
var r=Math.floor(imgData.data[i]/4);
var g=Math.floor(imgData.data[i+1]/4);
var b=Math.floor(imgData.data[i+2]/4);
var a=255;
var lutX = (b % 8) * 64 + r;
var lutY = Math.floor(b / 8) * 64 + g;
var lutIndex = (lutY * lutWidth + lutX)*4;
var Rr = filterData.data[lutIndex];
var Gg = filterData.data[lutIndex+1];
var Bb = filterData.data[lutIndex+2];
imgData.data[i] = filterData.data[lutIndex];
imgData.data[i+1] = filterData.data[lutIndex+1];
imgData.data[i+2] = filterData.data[lutIndex+2];
imgData.data[i+3] = 255;
}
ctx.putImageData(imgData,0,0);
for the attached LUT and it does not work properly because it only has 5 colums instead of 8. Of course I read the comment from How to use LUT with JavaScript? which says "This code only applies for 64x64x64 3DLUT images. The parameters vary if your LUT has other dimensions; the / 4, * 64, % 8, / 8 must be changed for other dimensions, but in this question's scope the LUT is 64x64x64."
i tried to do so and only ended in a mess.
What would I please have to change to make it work?
This question already has answers here:
JavaScript setInterval and `this` solution
(9 answers)
Closed 3 years ago.
What Im trying to do is animate circles bouncing off the canvas borders. I can achieve that pretty will with just one ball. But my Home work asks of me to add a new ball every two seconds. Originally I tried this by making new objects listed in an array using a for loop, but had trouble coming up with a way to call "move" method out of each newly created object in an interval. At the end I settled with creating the variable j and setting it to 0 and having it increment by 10 since the interval will execute the function "startAnim" every 10 milliseconds. Then every two seconds have a new object created and then call that objects "move" method.
<!DOCTYPE html>
<html>
<head>
<title>Some document</title>
<body>
<h1></h1>
<canvas id="canvas" width = "400" height = "400"></canvas>
<script src= "https://code.jquery.com/jquery-2.1.0.js"></script>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var arrayOfCollor = ["green", "red", "blue","gold","black","purple"];
var ballFunction = function(){
this.x = 200;
this.y = 200;
this.xSpeed = 2;
this.ySpeed = -3;
this.radius = 10;
ctx.fillStyle = arrayOfCollor[Math.floor(Math.random() *
arrayOfCollor.length)];
}
ballFunction.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x,this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
}
ballFunction.prototype.move = function(){
ctx.clearRect(0,0,400,400);
ctx.strokeRect(0,0,400,400);
this.x += this.xSpeed;
this.y += this.ySpeed;
this.draw();
if(this.x > 400-this.radius|| this.x < 0 + this.radius){
this.xSpeed = -1* this.xSpeed;
}
if(this.y > 400 - this.radius || this.y < 0 + this.radius){
this.ySpeed = -1* this.ySpeed;
}
}
var j = 0;
function startAnim(){
j+= 10;
if( j === 2000){
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move,10)
}
if( j === 4000){
var ballTwo = new ballFunction
var ballTwoInterval = setInterval(ballTwo.move,10)
}
if( j === 6000){
var ballThree = new ballFunction
var ballThreeInterval = setInterval(ballThree.move,10)
}
if( j === 8000){
var ballFour = new ballFunction
var ballFourInterval = setInterval(ballFour.move,10)
}
if( j === 10000){
var ballFive = new ballFunction
var ballFiveInterval = setInterval(ballFive.move,10)
}
if( j === 12000){
var ballSix = new ballFunction
var ballSixinterval = setInterval(ballSix.move,10)
}
}
var startAnimInt = setInterval(startAnim,10);
</script>
</body>
</html>
I keep receiving that the draw function is not a function at ballFunction.move even though I inserted it in.
This is one of several common problems people run into with the this reference in JS. What this actually refers to is determined at runtime, and depends on how the function is called - not on how/where it was defined. The problem you have here is that you are using setInterval:
setInterval(ballOne.move,10)
which of course results in the JS engine calling ballOne.move itself, once every 10 milliseconds. But unfortunately, the way the engine will call it is the same as it will any old "plain function" it is passed - it has no way of knowing that the function it is calling is supposed to "belong" to the ballOne object, and therefore its this reference won't refer to ballOne, but instead to the global (window) object.
You can fix this by using the bind method of JS functions, to create a new version which will always have the specified this reference. That is, you can replace the above line with:
setInterval(ballOne.move.bind(ballOne),10)
and similarly for the other setInterval calls. This will of course quickly get repetitious, so it's probably better to fix this in the constructor, by adding the line:
this.move = this.move.bind(this);
Note that, although your question has nothing to do with React, this is what ReactJS recommends to do in Component classes, for exactly the same reason - to make sure the this reference is always the object intended.
The problem is that you pass the ballFunction method to setInterval as the first parameter and hence its context is lost and the this inside the function will be the window object. You can pass a function calling your move:
<!DOCTYPE html>
<html>
<head>
<title>Some document</title>
<body>
<h1></h1>
<canvas id="canvas" width = "400" height = "400"></canvas>
<script src= "https://code.jquery.com/jquery-2.1.0.js"></script>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var arrayOfCollor = ["green", "red", "blue","gold","black","purple"];
var ballFunction = function(){
this.x = 200;
this.y = 200;
this.xSpeed = 2;
this.ySpeed = -3;
this.radius = 10;
ctx.fillStyle = arrayOfCollor[Math.floor(Math.random() *
arrayOfCollor.length)];
}
ballFunction.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x,this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
}
ballFunction.prototype.move = function(){
ctx.clearRect(0,0,400,400);
ctx.strokeRect(0,0,400,400);
this.x += this.xSpeed;
this.y += this.ySpeed;
this.draw();
if(this.x > 400-this.radius|| this.x < 0 + this.radius){
this.xSpeed = -1* this.xSpeed;
}
if(this.y > 400 - this.radius || this.y < 0 + this.radius){
this.ySpeed = -1* this.ySpeed;
}
}
var j = 0;
function startAnim(){
j+= 10;
if( j === 2000){
var ballOne = new ballFunction
var ballOneInterval = setInterval(function() {ballOne.move();},10)
}
if( j === 4000){
var ballTwo = new ballFunction
var ballTwoInterval = setInterval(function() {ballTwo.move();},10)
}
if( j === 6000){
var ballThree = new ballFunction
var ballThreeInterval = setInterval(function() {ballThree.move();},10)
}
if( j === 8000){
var ballFour = new ballFunction
var ballFourInterval = setInterval(function() {ballFour.move();},10)
}
if( j === 10000){
var ballFive = new ballFunction
var ballFiveInterval = setInterval(function() {ballFive.move();},10)
}
if( j === 12000){
var ballSix = new ballFunction
var ballSixinterval = setInterval(function() {ballSix.move();},10)
}
}
var startAnimInt = setInterval(startAnim,10);
</script>
</body>
</html>
The this inside the move function isn't referencing to the ballFunction object, but to the window.
Use something like:
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move.bind(ballOne),10)
Instead of:
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move,10)
for all balls.
Hello stackoverflow community!
First I must say that I dont have much experience with constructors.
So. What I am trying to do, is to animate a parachutist to fly from top to bottom of the screen.
I thought I could use a constructor to set up a parachutist:
var parachute = function() {
this.height = 35;
this.width = 30;
this.speed = 50;
this.xPos = Math.round(Math.random() * (window.width - this.width));
this.animate = function() {
this.img = new Image();
this.yPos = 0;
this.img.onload = function() {
ctxPara.globalCompositeOperation = 'copy';
ctxPara.translate(0, this.yPos);
ctxPara.drawImage(this.img, this.xPos, 0);
};
this.img.src = 'para.png';
this.yPos++;
};
};
This constructor is used in a function called 'fly':
var fly = function() {
var newParachute = new parachute();
setInterval(newParachute.animate, newParachute.speed);
};
And this 'fly' function is triggered when the window loads:
window.onload = function() {
var canvasBg = document.getElementById('canvasBg');
// I splitt the Background and the parachutists in two canvas elements
// handling the problem (to erase content and draw new content) with
// the canvas animation.
var canvasPara = document.getElementById('canvasPara');
ctxPara = canvasPara.getContext('2d');
canvasPara.width = window.width;
canvasPara.height = window.height;
canvasBg.width = window.width;
canvasBg.height = window.height;
fly();
clouds(); // background is loading here
};
What you should see, is a Parachutist flying down the screen. But unfortunately you don't...
Now, after that Long text. (Iam very sorry that it is so long :-( ) My question is: Do you know what I am doing wrong? Is my constuctor correct? Is, what i am trying to do, supposed to be written like this? Any advices or suggestions for a succesfull opportunity? (I hope my english isn't that terrible I think it is :-) )
Oh i forgot to mention the error. It's a TypeMissMatchError.
That means 'this.img' is not an img element at this line:
ctxPara.drawImage(this.img, this.xPos, 0);
Now, I followed the example of markE.
Instead of showing me a parachutist. It shows me an error in this line: ctxPara.drawImage(this.img, this.xPos, this.yPos);
var fly = function () {
var newParachute = new parachute();
newParachute.img.load.call(newParachute);
setInterval(newParachute.animate.call(newParachute), newParachute.speed);
};
var parachute = function () {
this.height = 35;
this.width = 30;
this.speed = 25;
this.xPos = Math.round(Math.random() * (window.innerWidth - this.width));
this.img = new Image();
this.yPos = 0;
this.img.isLoaded = false;
this.img.load = function () {
this.img.isLoaded = true;
};
this.img.src = 'parachute.png';
this.animate = function () {
if (this.img.isLoaded) {
ctxPara.clearRect(0, 0, canvasPara.width, canvasPara.height);
ctxPara.drawImage(this.img, this.xPos, this.yPos); // ERROR: 'Unknown Error'.
this.yPos++;
console.log('animating');
}
};
};
I am stuck again. But now i don't even know the reason... Please help!?
Demo: http://jsfiddle.net/m1erickson/ym55y/
A couple of issues:
(1) To get the window width you can use:
window.innerWidth
(2) setInterval calls newParachute.animate.
setInterval(newParachute.animate, newParachute.speed);
But this inside animate the window object--not the Parachute object.
To give the correct this to animate you can use the call method like this:
var newParachute = new parachute();
setInterval(function(){newParachute.animate.call(newParachute);}, newParachute.speed);
(3) You need to deal with clearing previously drawn images or they will still show on your canvas.
I am using node.js + socket.io to try and make a multiplier HTML5 canvas game. Unfortunately every time I go to draw all of the players, all the images end up on top of each other.
socket.on('drawPlayers', function(players){
context.clearRect ( 0 , 0 , gameWidth , gameHeight ); //clear canvas
for(var i=0; i<players.length; i++){
var cur = players[i];
var playerSprite = new Image();
playerSprite.onload = function() {
return context.drawImage(playerSprite, cur.x, cur.y);
};
playerSprite.src = 'images/sprite.png';
console.log("drawing player "+cur.un+" at ("+cur.x+","+cur.y+")");
}
});
The console.log() will log the different x and y values correctly, but all of the images end up on top of each other.
function player(id,username){
this.id = id;
this.un = username;
this.y = Math.floor(Math.random() * 501);
this.x = Math.floor(Math.random() * 801);
this.src = 'images/mobile_md_logo.png';
}
I
Jonathan Lonowski is right. For each of the onload triggers, cur points to the same player, the last one. There is only one cur that exists inside drawPlayers event handler.
Use this to separate the scope of cur from one that is in the loop:
function draw(sprite,cur){
return function() {
context.drawImage(sprite, cur.x, cur.y);
};
}
Then call it from inside the loop
for(var i=0; i<players.length; i++){
var cur = players[i];
var playerSprite = new Image();
playerSprite.onload = draw(playerSprite,cur);
playerSprite.src = 'images/sprite.png';
console.log("drawing player "+cur.un+" at ("+cur.x+","+cur.y+")");
}
var canvas = null;
var context = null;
var img = null;
var frames = [];
var assets = [];
var imgLoadCount = 0;
setup = function (fileArray) {
assets = fileArray;
//Loading an image
for(var i = 0; i < assets.length; i++) {
console.log(i);
frames.push(new Image()); // declare image object
frames[i].onload = onImageLoad(assets.length); //declare onload method
frames[i].src = URL.createObjectURL(assets[i]); //set url
}
};
onImageLoad = function (len) {
console.log("IMAGE!!!");
canvas = document.getElementById("my_canvas"); //creates a canvas element
context = canvas.getContext('2d');
canvas.width = window.innerWidth; //for full screen
canvas.height = window.innerHeight;
var x, y, numTilesRow;
numTilesRow = 10;
imgLoadCount++;
console.log("imgLoadCount = " + frames.length + ", length = " + len);
if(imgLoadCount != len) {
return;
}
for(var index = 0; index < len; index++) {
x = Math.floor((index % numTilesRow));
y = Math.floor(index / numTilesRow);
worldX = x * numTilesRow;
worldY = y * numTilesRow;
context.drawImage(frames[index], worldX, worldY);
}
};
I cant tell why drawImage has suddenly stopped working after inserting the code
if(imgLoadCount != len) {return;} that makes sure that all images are properly loaded. Would some one please help me find a solution to this problem.
You'll have to understand the difference between a function reference and a function call. The .onload property expects to be assigned with a function reference, but you assign it with the return value of the immediate(!) call to the function .onImageLoad. The difference is the parentheses ().
If you want to call a function with parameters as a callback to .onload, then you'd have to include it into an anonymous function (which itself is a function reference)
frames[i].onload = function() {onImageLoad(assets.length);};
With this, of course, you create a closure. This means that at the point of execution the onImageLoad() method will have access to the current value of assets.length (and not to the value at the point of assignment!). But in your case this doesn't make any difference, because assets.length never changes.