Rendering a tile map on html5 canvas using and array and fillRect - javascript

I'm very much an amateur enthusiast trying to make a basic 2d game map with html canvas. I've done this before by using arrays to create div/img tags and position them. I'm now trying to do this with canvas, not with images but simply drawing squares with fillRect().
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map =
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 0, 0]
[1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0];
window.addEventListener("load", function()
{
update();
}, false);
function update()
{
window.requestAnimationFrame(update, canvas);
render();
}
function render()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var row = 0; row < map.length; row++)
{
for(var column = 0; column < map[0].length; column++)
{
switch(map[row][column])
{
case 0:
ctx.fillStyle = #ffffff;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
case 1:
ctx.fillStyle = #009900;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
}
}
}
}
I'm getting the error: 'Uncaught SyntaxError: Unexpected token ILLEGAL' on line 46 which is 'ctx.fillStyle = #ffffff;'.
I'm a bit stuck as to why it's giving this error, I'm wondering if you can't use the context methods in this way with an array but I can't understand why.
If anyone has any advice, I would be very grateful.

Related

How to refactor my code to a game loop?

Now I have a simple code without a main game loop. I'm rendering the sprites on the canvas using the Loader constructor and its image.onload function (because without image.onload I won't see any sprites) Now I want to animate on of my sprites and for that I need to create a draw loop. Unfortunately, this is where my knowledge ends. I tried creating render function and just copy pasting my ship.drawimage(boat, boatPosX, boatPosY, 50, 50); methods what so ever, but it's not working because I need image.onload function which is inside Loader. And I can't put Loader constructor to my render() function because then var background = new Loader("ground.png");
var boat = new Loader("ship.png"); can't access the constructor variable to init new object.
So at this point I'm pretty lost how I should refactor my code better?
Here is the full code:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var mapArray = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
var StyleSheet = function(image, width, height) {
this.image = image;
this.width = width;
this.height = height;
this.draw = function(image, sx, sy, swidth, sheight, x, y, width, height) {
context.drawImage(image, sx, sy, swidth, sheight,x, y, width, height);
}
this.drawimage = function(image, x, y, width, height) {
context.drawImage(image, x, y, width, height);
}
}
/* Initial Sprite Position */
var boatPosX = 230;
var boatPosY = 200;
var Loader = function(src) {
this.image = new Image();
this.image.src = src;
this.image.onload = function() {
var sprite = new StyleSheet(background, 36, 36);
var ship = new StyleSheet(boat, 90, 100);
for (let i = 0; i < mapArray.length; i++) {
for (let j = 0; j < mapArray[i].length; j++) {
if (mapArray[i][j] == 0) {
sprite.draw(background, 190, 230, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
if (mapArray[i][j] == 1) {
sprite.draw(background, 30, 30, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
if (mapArray[i][j] == 2) {
sprite.draw(background, 200, 20, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
}
}
ship.drawimage(boat, boatPosX, boatPosY, 50, 50);
}
return this.image;
}
function render() {
}
setInterval(render, 10);
/* Sprite controls */
function move(e) {
if (e.keyCode == 39) {
boatPosX += 2;
console.log("works");
}
if (e.keyCode == 37) {
boatPosX -= 2;
}
}
document.onkeydown = move;
var background = new Loader("ground.png");
var boat = new Loader("ship.png");
console.log(background);
UPDATE:
So following my old questions, I decided to do some changes to my code so that I would be allowed to call requestAnimationFrame for my onload function and draw the sprite on the canvas constantly. For that I separated Loader constructor and my method onload by putting onload into a new function and assigning that function to Loader prototype. Then I do var background = new Loader("ground.png"); and background.render(); but I get Uncaught TypeError: background.render is not a function error. Not sure what I'm doing wrong?
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var mapArray = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
var StyleSheet = function(image, width, height) {
this.image = image;
this.width = width;
this.height = height;
this.draw = function(image, sx, sy, swidth, sheight, x, y, width, height) {
context.drawImage(image, sx, sy, swidth, sheight,x, y, width, height);
}
this.drawimage = function(image, x, y, width, height) {
context.drawImage(image, x, y, width, height);
}
}
/* Initial Sprite Position */
var boatPosX = 230;
var boatPosY = 200;
var Loader = function(src) {
this.image = new Image();
this.image.src = src;
return this.image;
}
Loader.prototype.render = function() {
this.image.onload = function() {
var sprite = new StyleSheet(background, 36, 36);
var ship = new StyleSheet(boat, 90, 100);
for (let i = 0; i < mapArray.length; i++) {
for (let j = 0; j < mapArray[i].length; j++) {
if (mapArray[i][j] == 0) {
sprite.draw(background, 190, 230, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
if (mapArray[i][j] == 1) {
sprite.draw(background, 30, 30, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
if (mapArray[i][j] == 2) {
sprite.draw(background, 200, 20, 26, 26, i * sprite.width, j * sprite.height, sprite.width, sprite.height);
}
}
}
ship.drawimage(boat, boatPosX, boatPosY, 50, 50);
}
}
/* Sprite controls */
function move(e) {
if (e.keyCode == 39) {
boatPosX += 2;
console.log("works");
}
if (e.keyCode == 37) {
boatPosX -= 2;
}
}
document.onkeydown = move;
var background = new Loader("ground.png");
var boat = new Loader("ship.png");
background.render();
console.log(background);
Codepen example: https://codepen.io/Limpuls/pen/dejVpR
I made a fork of your Pen and refactored your code. Now you have a game loop using requestAnimationFrame: https://codepen.io/DonKarlssonSan/pen/rvrGvL/
Edit, adding what I changed:
I removed the Loader and instead just use plain Image and img.src = url which I then pass in as parameters to the constructor to StyleSheet.
I extracted the loop over mapArray into a render method which also functions as the main ("game") loop.
Code:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var mapArray = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
var StyleSheet = function(image, width, height) {
this.image = image;
this.width = width;
this.height = height;
this.draw = function(image, sx, sy, swidth, sheight, x, y, width, height) {
context.drawImage(image, sx, sy, swidth, sheight, x, y, width, height);
};
this.drawimage = function(image, x, y, width, height) {
context.drawImage(image, x, y, width, height);
};
};
/* Initial Sprite Position */
var boatPosX = 230;
var boatPosY = 200;
function render() {
requestAnimationFrame(render);
for (let i = 0; i < mapArray.length; i++) {
for (let j = 0; j < mapArray[i].length; j++) {
if (mapArray[i][j] == 0) {
this.sprite.draw(
background,
190,
230,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
if (mapArray[i][j] == 1) {
this.sprite.draw(
background,
30,
30,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
if (mapArray[i][j] == 2) {
this.sprite.draw(
background,
200,
20,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
}
}
this.ship.drawimage(boat, boatPosX, boatPosY, 50, 50);
};
function move(e) {
if (e.keyCode == 39) {
boatPosX += 2;
console.log("right");
}
if (e.keyCode == 37) {
boatPosX -= 2;
console.log("left");
}
}
document.onkeydown = move;
var background = new Image();
background.src = "http://i67.tinypic.com/35lx8y0.png";
var sprite = new StyleSheet(background, 36, 36);
var boat = new Image();
boat.src = "http://i66.tinypic.com/b7b9tc.png";
var ship = new StyleSheet(boat, 90, 100);
render();

Why fillText doesn't show up in canvas?

Tried changing the fillStyle color to many different ones, also different positions but nothing. No errors in console either. I already have tileset and sprites drawn on the canvas, does that has anything to do with it? I just need to print out a simple text on every character move on key press.
Here is the code:
function move(e) {
if (e.keyCode == 39) {
boatPosX += 5;
view.x -= 5
moveCount++;
context.fillStyle = "red";
context.fillText(theArray[0].question, 0, 0);
console.log(theArray[0].question);
}
The rest of the code:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var view = {x: 0, y: 0};
var questionsArray = [];
var moveCount = 0;
var mapArray = [
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
function isPositionWall(ptX, ptY) {
var gridX = Math.floor(ptX / 36)
var gridY = Math.floor(ptY / 36)
if(gridX < 0 || gridX >= mapArray[0].length)
return true;
if(gridY < 0 || gridY >= mapArray.length)
return true;
return mapArray[gridX][gridY];
}
var theArray = [];
var Question = function(question, answer1, answer2, correctAnswer) {
this.question = question;
this.answer1 = answer1;
this.answer2 = answer2;
this.correctAnswer = correctAnswer;
this.addToArray = function(){
theArray.push(this);
};
this.addToArray();
}
Question.prototype.checkAnswer = function() {
return answer1 || answer2 == correctAnswer;
}
var question1 = new Question("Taip ar ne?", "Taip", "Ne", "Taip");
var question2 = new Question("Jo ar ne?", "Ne", "Jo", "Jo");
var question3 = new Question("Aha ar ne?", "Aha", "Ne", "Ne");
var question4 = new Question("Ja ar ne?", "Taip", "Ne", "Taip");
var question5 = new Question("Jojo ar ne?", "Taip", "Ne", "Taip");
var question6 = new Question("Taip ar ne?", "Taip", "Ne", "Taip");
var question7 = new Question("Taip ar ne?", "Taip", "Ne", "Taip");
var StyleSheet = function(image, width, height, x, y) {
this.image = image;
this.width = width;
this.height = height;
this.x = x;
this.y = y
this.draw = function(image, sx, sy, swidth, sheight, x, y, width, height) {
context.drawImage(image, sx, sy, swidth, sheight, x, y, width, height);
};
this.drawimage = function(image, x, y, width, height) {
context.drawImage(image, x, y, width, height);
};
};
/* Initial Sprite Position */
var boatPosX = canvas.height/2 - 50;
var boatPosY = canvas.height/2 - 50;
function render(viewport) {
context.save();
context.translate(view.x, view.y);
requestAnimationFrame(render);
var oldPosX = boatPosX;
var oldPosY = boatPosY;
for (let i = 0; i < mapArray.length; i++) {
for (let j = 0; j < mapArray[i].length; j++) {
if (mapArray[i][j] == 0) {
this.sprite.draw(
background,
190,
230,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
if (mapArray[i][j] == 1) {
this.sprite.draw(
background,
30,
30,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
if (mapArray[i][j] == 2) {
this.sprite.draw(
background,
200,
20,
26,
26,
i * this.sprite.width,
j * this.sprite.height,
this.sprite.width,
this.sprite.height
);
}
}
}
this.ship.drawimage(boat, boatPosX, boatPosY, 50, 50);
//console.log(boatPosX + ship.width)
if(isPositionWall(boatPosX, boatPosY)) {
boatPosX = oldPosY;
console.log("collision");
}
context.restore();
};
function move(e) {
if (e.keyCode == 39) {
boatPosX += 5;
//canvas.width += 2;
view.x -= 5
moveCount++;
console.log(moveCount);
console.log("right");
context.fillStyle = "red";
context.fillText(theArray[0].question, 0, 0);
console.log(theArray[0].question);
}
if (e.keyCode == 37) {
boatPosX -= 5;
view.x += 5
moveCount++;
console.log(moveCount);
console.log("left");
}
if (e.keyCode == 38) {
boatPosY -= 5;
view.Y += 5
moveCount++;
console.log(moveCount);
console.log("up");
}
if (e.keyCode == 40) {
boatPosY += 5;
view.Y += 5
moveCount++;
console.log(moveCount);
console.log("down");
}
}
document.onkeydown = move;
var background = new Image();
background.src = "ground.png";
var sprite = new StyleSheet(background, 36, 36, 16, 16);
var boat = new Image();
boat.src = "ship.png";
var ship = new StyleSheet(boat, 90, 100, 16, 16);
console.log(Math.floor(boatPosX / 36));
console.log(mapArray[Math.floor(boatPosX / 36)]);
render();
Seems to be because you are trying to put the text at 0,0. By default the text will be drawn above the Y position of 0 (so off the top of the canvas which is why you don't see anything). If you made it 0,10 or 0,20 then you will probably see some text.
It is possible to change the text base line like this, so at 0,0 you will see something..
context.textBaseline = "top";

HTML5 Canvas - How to get adjacent pixels position from the linearized imagedata Uint8ClampedArray?

A 5 by 5 pixel image data is something like this in linearized imagedata array-
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So, the 3x3 pixel data is- 0 0 0 255. How can I get the adjacent pixel positions? Left and right adjacent ones are easy, just minus 4 and plus 4 respectively.
Accessing pixel data
The pixel data from .getImageData().data is a TypedArray of type Uint8ClampedArray. When reading the values they will be in the range 0-255 and in the order Red, Green, Blue, Alpha. If the value of alpha is zero then red, green, and blue will also be zero.
To get the index of a pixel
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
var index = (x + y * imageData.width) * 4;
const red = imageData.data[index];
const green = imageData.data[index + 1];
const blue = imageData.data[index + 2];
const alpha = imageData.data[index + 3];
To move down one pixel
index += imageData.width * 4;
To move up one
index -= imageData.width * 4;
To move left.
index -= 4;
To move right
index += 4;
If you are on the left or right edge and you move in the direction of the edge you will wrap around, on the line above and to the right if moving left and the line below and on the left if moving down.
When setting the image data the values will be floored and clamped to 0-255
imageData.data[index] = 29.5
console.log(imageData.data[index]); // 29
imageData.data[index] = -283
console.log(imageData.data[index]); // 0
imageData.data[index] = 283
console.log(imageData.data[index]); // 255
If you set an index that is outside the array size it will be ignored
imageData.data[-100] = 255;
console.log(imageData.data[-100]); // Undefined
imageData.data[imageData.data.length + 4] = 255;
console.log(imageData.data[imageData.data.length + 4]); // Undefined
You can speed up access and processing by using different array types. For example all of a pixel's channels as one value using Uint32Array
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
const pixels = new Uint32Array(imageData.data.buffer);
var index32 = x + y * imageData.width; // note there is no 4*;
const pixel = pixels[index32];
The channels are stored in bits 31-24 Alpha, 23-16 Blue, 15-8 Green, 7-0 Red.
You can set a pixel using a hex value
pixels[x + y * imageData.width] = 0xFF0000FF; // red
pixels[x + y * imageData.width] = 0xFF00FF00; // Green
pixels[x + y * imageData.width] = 0xFFFF0000; // Blue
pixels[x + y * imageData.width] = 0xFF000000; // Black
pixels[x + y * imageData.width] = 0xFFFFFFFF; // White
pixels[x + y * imageData.width] = 0; // Transparent
You can set all the pixels in a single call
pixels.fill(0xFF000000); // all pixels black
You can copy array data onto the array with
// set 3 pixels in a row at x,y Red, Yellow, White
pixels.set([0xFF0000FF,0xFF00FFFF,0xFFFFFFFF], x+y * imageData.width);
Warning
If the canvas has any pixel/s that are from an untrusted source it will be tainted and you will not be able to read the pixel data. Trusted sources are same domain or images served with the appropriate CORS header information. Images that are on the file system can not have their pixels accessed. Once a canvas is tainted it can not be cleaned.
A tainted canvas will throw an error when you call ctx.getImageData(0,0,1,1,) MDN does not list this exception for some reason. You will see "SecurityError" DOMException; in the DevTools console and there are plenty of answered question here in StackOverflow on the subject.
You could calculate the index with the width of the matrix and the length of one unit of 4.
The access is zero based.
function getPos(array, x, y, width) {
var p = 4 * (x + y * width);
return array.slice(p, p + 4);
}
var array = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
// element above
console.log(JSON.stringify(getPos(array, 2, 1, 5))); // [0, 0, 0, 240]
// actual element
console.log(JSON.stringify(getPos(array, 2, 2, 5))); // [0, 0, 0, 255]
// element below
console.log(JSON.stringify(getPos(array, 2, 3, 5))); // [0, 0, 0, 241]

requestanimationframe drawing with for loop issue

I'm working on a tetris game - still - and am trying to use requestAnimationFrame to draw my T piece on the black board.
This is the problem. the requestAnimationFrame draws the piece 2 times, then stops drawing even though the for loop is still running. That is, after two times, I only see the black background. When I comment out the black background the piece shows up/animates just fine.
I really am at a loss why this is happening.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
const T = [
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
]
var piece = T[0];
const player = {
position: {x: 5, y: -1},
piece: piece,
}
function colorPiece(piece, offset) {
for(y = 0; y < piece.length; y++) {
for(x = 0; x < piece.length; x++) {
if (piece[y][x] !== 0) {
ctx.fillStyle = "red";
ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
}
}
}
}
function drawCanvas() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.scale(20, 20);
colorPiece(player.piece, player.position);
}
function update() {
drawCanvas();
requestAnimationFrame(update);
}
update();
OK - working version, with a fiddle here. A number of changes. The biggest are:
Don't use canvas.scale(), since it's cumulative per this (see "More Examples"). Instead, use 20*x and 20*y for blocks 20x20.
Edit Based on a further test, it looks like this was the most significant change.
Rename so that piece is not used as all of a variable, a field name in player, and a parameter of colorPiece
Move the ctx creation into update() (now called doUpdate()) per this fiddle example. Pass ctx as a parameter to other functions.
Move the red fillStyle assignment out of the loop, since you only need to do it once, and then you can draw all the rectangles without changing it.
In the loops:
for(var y = 0; y < thePiece.length; ++y) {
for(var x = 0; x < thePiece[y].length; ++x) { ... } }
Keep x and y in the local scope, using var.
When you are ready to go across a row, that's thePiece[y].length, i.e., the length of the row. Using thePiece.length there would have broken for non-square elements of T.
Added a <p id="log"/> and a javascript framenum so that I could see that doUpdate() was indeed being called.
If you haven't already, make sure to open the console while you're testing so you can see error messages. If drawCanvas causes an error, it will prevent requestAnimationFrame from being called again. I think that's why the fiddle I linked above calls requestAnimationFrame at the beginning rather than the end of the frame-draw function.
Hope this helps!
Code
const T = [
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
],
[
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
],
]
const player = {
position: {x: 5, y: -1},
piece: T[0]
}
function colorPiece(ctx, thePiece, offset) {
ctx.fillStyle = "red";
for(var y = 0; y < thePiece.length; ++y) {
for(var x = 0; x < thePiece[y].length; ++x) {
if (thePiece[y][x] !== 0) {
ctx.fillRect(20*(x + offset.x), 20*(y + offset.y), 20, 20);
}
}
}
}
function drawCanvas(ctx) {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
colorPiece(ctx, player.piece, player.position);
}
var framenum=0;
function doUpdate(timestamp) {
document.getElementById("log").innerHTML = framenum.toString();
++framenum;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
drawCanvas(ctx);
window.requestAnimationFrame(doUpdate);
}
doUpdate();

HTML5 Canvas drawImage only draws after second refresh

OK, I have a HTML5 canvas... and it draws images from .png tiles (32x32). It works. Sort of. It only draws on the canvas after the second refresh. For example, if you were to load it up... al you would see is a red canvas (the background for #canvas is red) then if you were to refresh it... it would be successfully draw the images... why is that?
Here is the code. (All you need is two images. t0.png and t1.png in line_tiles folder) But I am sure you can spot the error right away that I can't :P
game.js
// HTML5 JS Tile Example
var canvas, context, board, imageObj, tiles;
var currentMap = 1;
var upMap = 0;
var rightMap = 0;
var leftMap = 0;
var downMap = 3;
var NUM_OF_TILES = 1; // starting from ZERO
// Set return 2D array of map
function loadMap(map) {
if (map == 1) {
return [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
}
}
// On load...
window.onload = function () {
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
imageObj = new Image();
tiles = [];
board = loadMap(1);
canvas.width = 512;
canvas.height = 352;
// 2. SET UP THE MAP TILES
for (x = 0; x <= NUM_OF_TILES; x++) {
imageObj = new Image(); // new instance for each image
imageObj.src = "line_tile/t" + x + ".png";
tiles.push(imageObj);
}
var theX;
var theY;
// 3. DRAW MAP BY ROWS AND COLS
for (x = 0; x <= 10; x++) {
for (y = 0; y <= 15; y++) {
theX = x * 32;
theY = y * 32;
context.drawImage(tiles[board[x][y]], theY, theX, 32, 32);
}
}
};
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<title>HTML5</title>
<script type="text/javascript" src="game.js"></script>
<style type="text/css">
<!--
#canvas {
background:red;
z-index:0;
position:relative;
}
.container {
width: 512px;
position: relative;
}
-->
</style>
</head>
<body>
<div class="container">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
You need to add onload hooks on your images (the tiles) and draw only when all images are loaded.
Here's a suggestion :
// HTML5 JS Tile Example
var canvas, context, board, imageObj, tiles;
var currentMap = 1;
var upMap = 0;
var rightMap = 0;
var leftMap = 0;
var downMap = 3;
var NUM_OF_TILES = 1; // starting from ZERO
// Set return 2D array of map
function loadMap(map) {
if (map == 1) {
return [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
}
}
// On load...
window.onload = function () {
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
imageObj = new Image();
tiles = [];
board = loadMap(1);
canvas.width = 512;
canvas.height = 352;
var draw = function() {
var theX;
var theY;
// 3. DRAW MAP BY ROWS AND COLS
for (x = 0; x <= 10; x++) {
for (y = 0; y <= 15; y++) {
theX = x * 32;
theY = y * 32;
context.drawImage(tiles[board[x][y]], theY, theX, 32, 32);
}
}
}
var loadedImagesCount = 0;
// 2. SET UP THE MAP TILES
for (x = 0; x <= NUM_OF_TILES; x++) {
var imageObj = new Image(); // new instance for each image
imageObj.src = "line_tile/t" + x + ".png";
imageObj.onload = function() {
loadedImagesCount++;
if (loadedImagesCount==NUM_OF_TILES) draw();
};
tiles.push(imageObj);
}
};
And be careful not to forget the var keyword (look at the loop).

Categories

Resources