I've been looking for some time now how to detect collisions on a tilemap between my player and the box specified in my table, but all I found are advanced tutorials, I'm trying to do this as simply as possible so that I can understand how it works too.
In my table, I therefore seek to detect a collision only if the player walks on a box of value 1 (this would be a wall for example). Then the player will not be able to move on this place of my map.
My code:
// Initi
ctx = null;
var ctx = document.getElementById("canvas").getContext("2d");
// Map
var gameMap = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
var tileW = 40,
tileH = 40;
var mapW = 10,
mapH = 10;
window.onload = function() {
requestAnimationFrame(drawGame);
ctx.font = "bold 10pt sans-serif";
};
// Player
var x = 100;
var y = 100;
var radius = 10;
var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false;
var speed = 1;
function drawPlayer() {
ctx.fillStyle = "green";
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2)
ctx.fill();
}
// Inputs
function inputs() {
if (upPressed) {
y = y - speed;
}
if (downPressed) {
y = y + speed;
}
if (leftPressed) {
x = x - speed;
}
if (rightPressed) {
x = x + speed;
}
}
document.body.addEventListener("keydown", keyDown)
document.body.addEventListener("keyup", keyUp)
function keyDown(event) {
if (event.keyCode == 38) {
upPressed = true;
}
if (event.keyCode == 40) {
downPressed = true;
}
if (event.keyCode == 37) {
leftPressed = true;
}
if (event.keyCode == 39) {
rightPressed = true;
}
if (event.keyCode == 65) {
speedCodePressed = true;
speed = 20;
}
if (event.keyCode == 32) {
shootPressed = true;
}
}
function keyUp(event) {
if (event.keyCode == 38) {
upPressed = false;
}
if (event.keyCode == 40) {
downPressed = false;
}
if (event.keyCode == 37) {
leftPressed = false;
}
if (event.keyCode == 39) {
rightPressed = false;
}
if (event.keyCode == 32) {
shootPressed = false;
}
}
// game map draw function
function drawMap() {
if (ctx == null) {
return;
}
for (var y = 0; y < mapH; ++y) {
for (var x = 0; x < mapW; ++x) {
switch (gameMap[((y * mapW) + x)]) {
case 0:
ctx.fillStyle = "#685b48";
break;
default:
ctx.fillStyle = "#5aa457";
}
ctx.fillRect(x * tileW, y * tileH, tileW, tileH);
}
}
}
// clear screen
function clearScreen() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// game loop
function drawGame() {
requestAnimationFrame(drawGame);
clearScreen();
drawMap();
drawPlayer();
inputs();
}
<canvas id="canvas"></canvas>
I won't go into too much detail, as I think it's pretty straightforward, but I'm a beginner and really have no idea.
See the changes below...
I added canvas.height = tileH * mapH same for width to match the real size of the game.
Created a new object var player = { x: 100, y: 100 , radius: 10, speed: 1 } you should keep everything related to the player in that object
I'm using Path2D to create the structure that we draw (walls) and a path that we use for the collisions
The collisions are detected with isPointInPath read more here: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
I also changed the gameMap to a 2 dimensional array its makes everything easier now that we are using the Path2D, not really required but I like it better that way.
var canvas = document.getElementById("canvas")
var tileW = 40
var tileH = 40
var mapW = 10
var mapH = 10
var ctx = canvas.getContext("2d");
var upPressed = false;
var downPressed = false;
var leftPressed = false;
var rightPressed = false
var player = { x: 100, y: 100, radius: 10, speed: 1 }
var gameMap = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
var path = new Path2D()
var walls = new Path2D()
window.onload = function() {
canvas.height = tileH * mapH
canvas.width = tileW * mapW
for (var y = 0; y < mapH; ++y) {
for (var x = 0; x < mapW; ++x) {
if (gameMap[y][x]) {
path.rect(x * tileW- player.radius, y * tileH- player.radius, tileW + player.radius*2, tileH + player.radius*2)
walls.rect(x * tileW, y * tileH, tileW, tileH)
}
}
}
requestAnimationFrame(drawGame);
};
function drawPlayer() {
ctx.fillStyle = "green";
ctx.beginPath();
ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2)
ctx.fill();
}
function inputs() {
var newx = player.x
var newy = player.y
if (upPressed) newy = player.y - player.speed;
if (downPressed) newy = player.y + player.speed;
if (leftPressed) newx = player.x - player.speed;
if (rightPressed) newx = player.x + player.speed;
if (!ctx.isPointInPath(path, newx, newy)) {
player.x = newx;
player.y = newy;
}
}
document.body.addEventListener("keydown", keyDown)
document.body.addEventListener("keyup", keyUp)
function keyDown(event) {
if (event.keyCode == 38) upPressed = true;
if (event.keyCode == 40) downPressed = true;
if (event.keyCode == 37) leftPressed = true;
if (event.keyCode == 39) rightPressed = true;
}
function keyUp(event) {
if (event.keyCode == 38) upPressed = false;
if (event.keyCode == 40) downPressed = false;
if (event.keyCode == 37) leftPressed = false;
if (event.keyCode == 39) rightPressed = false;
}
function drawGame() {
ctx.fillStyle = "#685b48"
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#5aa457"
ctx.fill(walls)
//ctx.stroke(path);
drawPlayer();
inputs();
requestAnimationFrame(drawGame);
}
<canvas id="canvas"></canvas>
Solution:
Check if the new position is not 1 in the game map.
If it's 1 do nothing.
If it's not 1 assign position
Calculating position:
Math.floor(y / tileH) // y
Math.floor(x / tileW) // x
Actual code:
function inputs() {
let newX = x
let newY = y
if(upPressed) {
newY -= speed
}
if(downPressed) {
newY += speed
}
if(leftPressed) {
newX -= speed
}
if(rightPressed) {
newX += speed
}
if (gameMap[Math.floor(newY / tileH)][Math.floor(newX / tileW)] !== 1) {
x = newX
y = newY
}
}
One way to solve this is by changing your game map from a 1 dimensional array to a 2 dimensional array.
So instead of:
var gameMap = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
Make it:
let gameMap = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
Or however you want to structure your game's map.
Then once you have this 2D array, keep track of the row and column index of where your player is currently located.
let player_index_x = 3;
let player_index_y = 5;
Update this index whenever the player changes locations; e.g if the player moves up 1, then you subtract 1 from the y index.
If the player moves right 1, add one to the x index.
Then collision detection becomes a lot more straightforward because before moving left, right, up, or down, you can check something like:
if(left_pressed)
{
// make sure that it is indeed possible to move left
if(player_index_x > 1)
{
if(gameMap[player_index_x - 1][player_index_y] == 1)
{
// collision detected, do not move, return if in function
}
else
{
// move player
player_index_x -= 1;
}
}
}
My Recommendations:
Be sure to check first whether or not the move is a valid one, so the player does not fall off the map
If a collision occurs, what should happen? If a collision doesn't occur, what should happen? I recommend writing down a list of your assumptions while coding and checking them as you go. Especially in collision detection, it can be very easy to have unintended bugs from unchecked assumptions.
Resources for Learning to do this:
How can I create a two dimensional array in JavaScript?
Related
I am creating a 2D platformer in JavaScript. I am trying to create a tilemap that does not spawn in blocks where there is supposed to be air (labeled 0 in tilemap). I do not get any errors. However, no blocks are spawned in the canvas. The game also crashes unless i remove the collision detection.
Main js
//drawing variables
var canvas;
var context;
//game variables
var gameLoop;
var player;
var borders = [];
var mapHeight = 12;
var mapWidth = 22;
var tilesize = 50;
//input variables
var upKey;
var rightKey;
var downKey;
var leftKey;
//runs once pace has been loaded
window.onload = function(){
//canvas and context variable setup
canvas = document.getElementById("gameCanvas")
context = canvas.getContext("2d")
//key listeners
setupInputs();
//create player
player = new Player(400, 400);
//create border
let tilemap = [
[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, 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, 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, 0, 0, 0, 0, 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, 1, 1, 1, 1],
]
//create a box (border) based on tilemap position. Let 0's be air
for (let row = 0; row < mapHeight; row++){
for(let col = 0; col < mapWidth; col++){
if(tilemap[row][col] !== 0){
borders.push(new Border(tilemap[row]*tilesize, tilemap[col]*tilesize, tilesize, tilesize, tilemap[row][col]));
}
}
}
//initialize main game loop. Framerate = 30/sec
gameLoop = setInterval(step, 1000/30);
//draw canvas
context.fillStyle = "#f4f4f4f4"
context.fillRect(0, 0, mapWidth*tilesize, mapHeight*tilesize);
}
function step(){
player.step();
//draw after updates
draw();
}
function draw(){
//Clear previous canvas
context.fillStyle = "#f4f4f4f4"
context.fillRect(0, 0, mapWidth*tilesize, mapHeight*tilesize);
//draw the player
player.draw();
//draw borders
for(let i = 0; i < borders.length; i++){
borders[i].draw();
}
}
//keyboard inputs
function setupInputs(){
document.addEventListener("keydown", function(event){
if(event.key === "w"){
upKey = true;
} else if(event.key === "a"){
leftKey = true;
} else if(event.key === "s"){
downKey = true;
} else if(event.key === "d"){
rightKey = true;
}
});
document.addEventListener("keyup", function(event){
if(event.key === "w"){
upKey = false;
} else if(event.key === "a"){
leftKey = false;
} else if(event.key === "s"){
downKey = false;
} else if(event.key === "d"){
rightKey = false;
}
});
}
//Checking to see if player and border intersect
function checkIntersection(r1, r2){
if (r1.x >= r2.x + r2.width){
return false;
} else if (r1.x + r1.width <= r2.x){
return false;
} else if (r1.y >= r2.y + r2.height){
return false;
} else if (r1.y + r1.height <= r2.y){
return false;
} else {
return true;
}
}
Player js
function Player(x, y){
//Player variables
this.x = x;
this.y = y;
this.xvel = 0;
this.yvel = 0;
this.friction = 0.6;
this.maxVel = 12;
this.width = tilesize;
this.height = tilesize;
this.active = true;
this.falling = false;
this.step = function(){
if(this.active){
if(!leftKey && !rightKey || leftKey && rightKey){
this.xvel *= this.friction;
} else if (rightKey){
this.xvel++;
} else if (leftKey){
this.xvel--;
}
if(upKey){
//check if standing on ground
if (this.yvel === 0 && this.falling === false){
this.yvel -= 70;
this.falling = true;
} else{
this.yvel += 0;
}
}
this.yvel += 1;
//not allowing velocity to surpass maximum velocity
if (this.xvel > this.maxVel){
this.xvel = this.maxVel;
} else if(this.xvel < -this.maxVel){
this.xvel = -this.maxVel;
}
if (this.yvel > this.maxVel){
this.yvel = this.maxVel;
} else if(this.yvel < -this.maxVel){
this.yvel = -this.maxVel;
}
if (this.xvel > 0){
this.xvel = Math.floor(this.xvel);
} else {
this.xvel = Math.ceil(this.xvel);
}
if (this.yvel > 0){
this.yvel = Math.floor(this.yvel);
} else {
this.yvel = Math.ceil(this.yvel);
}
//collision rectangles
let horizontalRect = {
x: this.x + this.xvel,
y: this.y,
width: this.width,
height: this.height
}
let verticalRect = {
x: this.x,
y: this.y + this.yvel,
width: this.width,
height: this.height
}
//Collision deteQction
for(let i = 0; i<borders.length; i++){
let borderRect = {
x: borders[i].x,
y: borders[i].y,
width: borders[i].width,
height: borders[i].height
}
if (checkIntersection(horizontalRect, borderRect)){
while (checkIntersection(horizontalRect, borderRect)){
horizontalRect.x -= Math.sign(this.xvel);
}
this.x = horizontalRect.x;
this.xvel = 0;
}
if (checkIntersection(verticalRect, borderRect)){
while(checkIntersection(verticalRect, borderRect)){
verticalRect.y -= Math.sign(this.yvel);
}
this.y = verticalRect.y;
this.yvel = 0;
this.falling = false;
}
}
if (this.x + this.xvel > mapWidth*tilesize - tileisze){
this.xvel = 0;
}
if (this.x + this.xvel < 0){
this.xvel = 0;
}
this.x += this.xvel;
this.y += this.yvel;
}
}
this.draw = function(){
context.fillStyle = "orange";
context.fillRect(this.x, this.y, this.width, this.height);
}
}
Border js
function Border(x, y, width, height, type){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.type = type;
this.draw = function(){
if (this.type === 1){
context.fillStyle = "#2C2C34";
} else if (this.type === 2){
context.fillStyle = "#39393A";
}
context.fillRect(this.x, this.y, this.width, this.height);
}
}
There seems to be several errors in your code and it may take a while to find them all. You hex colors have 8 characters "#f4f4f4f4". You have a map width of 22 but only have 21 columns in the 2D array. It also seems off that when you are creating your borders array you have rows in the x spot and col in the y spot. I seems like it should be reversed. Anyway if you're not opposed to using a 1D array here is a simplified version of you code without the player stuff.
Border
function Border(type, x, y){
this.x = x;
this.y = y;
this.width = TILE_SIZE;
this.height = TILE_SIZE;
this.type = type;
this.draw = function(){
if (this.type === 1){
context.fillStyle = "purple";
context.fillRect(this.x, this.y, this.width, this.height);
} else if (this.type === 2){
context.fillStyle = "orange";
context.fillRect(this.x, this.y, this.width, this.height);
}
}
}
and the main file
const canvas = document.getElementById("canvas")
const context = canvas.getContext("2d")
const TILE_SIZE = 25; //50 was too big for my screen based on how many rows/columns you wanted
canvas.width = 525; //TILE_SIZE * columns
canvas.height = 300; //TILE_SIZE * rows
var borders = [];
let tileMap = {
mapHeight: 12,
mapWidth: 21,
size: TILE_SIZE,
grid: [
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, 0, 0, 2, 0, 2, 2, 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 2, 0, 2, 2, 2, 0, 2, 0, 2, 0, 2, 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,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
]
}
//Uses 1 loop to create the grid
function createGrid() {
for (let i = 0; i < tileMap.grid.length; i++) {
let x = (i % tileMap.mapWidth) * tileMap.size;
let y = Math.floor((i / tileMap.mapWidth)) * tileMap.size;
let type = tileMap.grid[i];
borders.push(new Border(type, x, y));
}
};
createGrid(); //creates the grid when file is loaded
function animate() {
context.clearRect(0, 0, canvas.width, canvas.height);
for(let i = 0; i < borders.length; i++){
borders[i].draw();
}
requestAnimationFrame(animate);
}
animate()
I stuck the for loop in the animate function only for this example. Normally it would be its own function and that is where you can handle collision detection or drawing tiles or anything else you want to do with them.
If you must use a 2d array here is a function that will create it
function createGrid() {
for (let row = 0; row < tileMap.mapHeight; row++) {
for (let col = 0; col < tileMap.mapWidth; col++) {
let type = tileMap.grid[row][col];
let x = col * TILE_SIZE;
let y = row * TILE_SIZE;
borders.push(new Border(type, x, y))
}
}
}
createGrid()
Be sure to change the map back to 2d if you use the one I posted above. I also noticed in your player file you have tileisze at one point instead of tilesize. I would take some time to carefully scrub your files for errors as these may be the cause of some of your problems.
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();
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";
I set-up a simple canvas tile map with one player object that should move in keydown. So far it is working as I am clearing the canvas in the beginning on every gameLoop, however I noticed that the tile map is drawn only the first time and after the clearRect of the canvas, it just disappears. Can you please help?
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Tile Map</title>
<style>
#canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvas" height="400px" width="500px"></canvas>
<script>
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, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1],
[0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0]
];
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
// DRAW PLAYER
var player = new Object();
player.y = canvas.height / 2 - 40;
player.x = canvas.width / 2 - 40;
player.Width = 80;
player.Height = 80;
player_image = new Image();
player_image.src = 'https://sarahkerrigan.biz/wpmtest/1/images/horseright1.png';
function drawPlayer() { // drawing the player
context.beginPath();
context.drawImage(player_image, player.x, player.y, player.Width, player.Height);
context.closePath();
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//SOME VARS
var updateX = (player.x - 210); // Starting point of canvas X
var updateY = (player.y - 160); // Starting point of canvas Y
var posX = updateX;
var posY = updateY;
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//DRAW THE MAP
function drawMap() {
var posY = 0;
var grass = new Image();
var stone = new Image();
grass.src = 'https://sarahkerrigan.biz/wpmtest/1/images/tile/grass.jpeg';
stone.src = 'https://sarahkerrigan.biz/wpmtest/1/images/tile/sand.jpeg';
grass.onload = function() {
stone.onload = function() {
for (var i = 0; i < mapArray.length; i++) {
for (var j = 0; j < mapArray[i].length; j++) {
if (mapArray[i][j] == 0) {
context.drawImage(grass, posX, posY, 32, 32);
}
if (mapArray[i][j] == 1) {
context.drawImage(stone, posX, posY, 32, 32);
}
posX += 32;
}
posY += 32;
posX = updateX;
drawPlayer();
}
}
}
}
//-----------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------
// PLAYER MOVEMENT
document.onkeydown = function(e) {
var keyCode = e.keyCode;
if (keyCode == 39) {
player.x = player.x + 10; // right is pressed
}
if (keyCode == 37) {
player.x = player.x - 10; // left is pressed
}
if (keyCode == 38) {
player.y = player.y - 10; // up is pressed
}
if (keyCode == 40) {
player.y = player.y + 10; // down is pressed
}
}
//-----------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------
// THE GAME LOOP
function gameLoop() {
drawMap();
setInterval(gameLoop, 100); // 100 milisec to draw next frame
}
//-----------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------
gameLoop(); // start the gameLoop
</script>
</body>
</html>
I have 2 matrix, which are the result of several arrays of pixels from 2 images.
I need to detect when the canvas element of matrix 2 is inside or steps on the edge of element 1 (rectangle of matrix 1), at this moment it must notify the user "match detected!"
My code to create matrix 1 and 2:
var matrix = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
var matrix2 = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 1, 1, 1, 0, 0, 0],
[1, 1, 0, 1, 0, 0, 0, 1, 0, 0],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 1, 1, 1, 0],
[0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
var contFilas = matrix.length;
var contColumnas = matrix[0].length;
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var sz = 20;
var regions = [];
var regionCollection = [];
canvas.width = sz * contColumnas;
canvas.height = sz * contColumnas;
ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var y = 0; y < contFilas; y++) {
var regionline = [];
regions.push(regionline);
for (var x = 0; x < contColumnas; x++) {
var pixelRegion = 0;
regionline[x] = 0;
if (matrix[y][x] === 1) {
// check previous row
if (y) {
if (matrix[y - 1][x]) {
pixelRegion = regions[y - 1][x];
} else if (x && matrix[y - 1][x - 1]) {
pixelRegion = regions[y - 1][x - 1];
} else if (x + 1 < contColumnas && matrix[y - 1][x + 1]) {
pixelRegion = regions[y - 1][x + 1];
}
}
// check current row
if (x && matrix[y][x - 1]) {
pixelRegion = regions[y][x - 1];
}
// if not connected, start a new region
if (!pixelRegion) {
regionCollection.push([]);
pixelRegion = regionCollection.length;
}
// remember region
regionline[x] = pixelRegion;
regionCollection[pixelRegion - 1].push([x, y]);
// paint it
ctx.fillStyle = "black";
ctx.fillRect(x * sz + 1, y * sz + 1, sz - 2, sz - 2);
}
ctx.fillStyle = "white";
ctx.fillText(pixelRegion, x * sz + 8, y * sz + 13);
}
}
document.querySelector("#result").innerHTML = JSON.stringify(regionCollection);
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>getUserMedia</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
</head>
<body>
<canvas></canvas>
<div id="result"></div>
<div id="result2"></div>
</body>
</html>
How can I do this? detect when there is an element inside or touching rectangle 1 (matrix)?