HTML JS Canvas 游戏:瓷砖碰撞错误使玩家传送
HTML JS Canvas Game: Tile Collision Bug Makes Player Teleport Up
我是游戏开发的初学者,一直在努力解决瓷砖阵列和玩家矩形之间的碰撞问题。这个游戏的特点是跳跃和重力。首先,碰撞有效,但非常笨重。有时,当玩家最终到达方块顶部并稍微靠近边缘时,它会立即传送到右侧或左侧(取决于 edge/corner 的内容)并掉落。与瓷砖底部碰撞时也会发生这种情况;玩家会立即传送到一边并继续往上走。据我了解,瓷砖碰撞检测器混淆了与一侧或另一侧的碰撞,因为当玩家撞到瓷砖的边缘时,检测器会读取它,就好像它与两者发生碰撞一样,并决定根据最高坐标速度将玩家放置在其他地方(又名 speedX 和 speedY)。我通过每次碰到瓷砖顶部时设置 speedY = 0 来解决这个问题,这解决了问题,但另一个问题出现了。现在,如果玩家在一个方块的顶部然后掉落并很快回扫,它不会与方块的侧面发生碰撞,但它会很快再次回到方块上面。
我只需要一些关于如何解决这个问题的提示,因为我尝试的一切都会导致另一个问题。我听说这是开发基于 2D 图块的游戏中的常见错误。
这是一个带有实际代码的 jsfiddle:https://jsfiddle.net/8121u356/
下面是我的整个代码的展示:
function startGame() {
gameArea.start();
actor = new player(32, 32, "green", 32, 32);
}
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,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,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,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,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,0,0,0,0,0,1,1,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,0,0,0,1,1,1,0,0,0,1,1,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]
];
var levelRows = 20;
var levelCols = 20;
var gameArea = {
canvas : document.getElementById('canvas'),
start : function() {
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
requestAnimationFrame(updateGameArea);
window.addEventListener('keydown', function (e) {
gameArea.keys = (gameArea.keys || []);
gameArea.keys[e.keyCode] = true;
});
window.addEventListener('keyup', function (e) {
gameArea.keys = (gameArea.keys || []);
gameArea.keys[e.keyCode] = false;
})
},
clear : function(){
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
render : function() {
context = this.canvas.getContext("2d");
var tileSize = 32;
for(i=0;i<levelRows;i++){
for(j=0;j<levelCols;j++){
if(mapArray[i][j]==1){
context.fillStyle = "gray";
context.fillRect(j*tileSize,i*tileSize,tileSize,tileSize);
}
}
}
}
};
function TileCollisionManager(object) {
let tileSize = 32;
let baseCol = Math.floor(object.x / tileSize);
let baseRow = Math.floor(object.y / tileSize);
let colOverlap = object.x % tileSize;
let rowOverlap = Math.floor(object.y % tileSize);
if (object.speedX > 0) {
if ((mapArray[baseRow][baseCol + 1] && !mapArray[baseRow][baseCol]) ||
(mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow + 1][baseCol] && rowOverlap)) {
object.x = baseCol * tileSize;
}
}
if (object.speedX < 0) {
if ((!mapArray[baseRow][baseCol + 1] && mapArray[baseRow][baseCol]) ||
(!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow + 1][baseCol] && rowOverlap)) {
object.x = (baseCol + 1) * tileSize;
}
}
if (object.speedY > 0) {
if ((mapArray[baseRow + 1][baseCol] && !mapArray[baseRow][baseCol]) ||
(mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow][baseCol + 1] && colOverlap)) {
object.y = ((baseRow) * tileSize);
object.jumping = false;
object.speedY = 0;
}
}
if (object.speedY < 0) {
if ((!mapArray[baseRow + 1][baseCol] && mapArray[baseRow][baseCol]) ||
(!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow][baseCol + 1] && colOverlap)) {
object.y = (baseRow + 1) * tileSize;
object.speedY = 5;
}
}
}
function updateGameArea() {
gameArea.clear();
gameArea.render();
actor.update();
actor.newPos();
actor.speedX = 0;
actor.speedY += actor.gravity;
if (gameArea.keys && gameArea.keys[39]) {
actor.speedX = 4;
}
if (gameArea.keys && gameArea.keys[37]) {
actor.speedX = -4;
}
if (gameArea.keys && gameArea.keys[32]) {
if (!actor.jumping) {
actor.jumping = true;
actor.speedY = -actor.speed * 3;
}
}
TileCollisionManager(actor);
requestAnimationFrame(updateGameArea);
}
function player (width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX=0;
this.speedY=0;
this.gravity=0.3;
this.speed=3;
this.jumping=false;
this.color = color;
this.update = function () {
ctx = gameArea.context;
ctx.fillStyle = this.color;
ctx.fillRect(
this.x,
this.y,
this.width, this.height);
};
this.newPos = function () {
this.x += this.speedX;
this.y += this.speedY;
};
为您快速修复。
我已经第 3 次见到你 post 这个问题了。您没有得到答案,因为最好的解决方案是代码很多、复杂,并且需要对代码进行大量更改。
所以我所做的是创建一个非常快速和简单的解决方案。
以正确的顺序解决冲突。
我没有检查移动结束时的位置,而是更改了代码以检查移动的每个像素。这是必需的,因为当玩家从一个位置移动到下一个位置时,您必须以正确的顺序找到碰撞。如果您在顶部或底部之前撞到一侧的墙壁,或者相反,它会有所不同,这就是导致您遇到问题的原因。您先检查了 x,然后检查了 y,这在很多情况下都是错误的做法。
我还向演员添加了一个名为 canMove
的对象,它有 4 个属性,设置在每一帧的开始,用于防止玩家向被阻挡的方向移动。如果你让玩家朝一个被阻挡的方向移动,当你在那个方向按下钥匙时它会卡在墙上。
我侵入了你的代码
对不起,我弄得有点乱,但时间不够。
另外,为了帮助我编写更改,我做了一些其他模组,我缩放了该游戏以适应 window(缩放和调整大小都在 clear
函数中完成)。我更改了键盘界面以防止默认按下的键并设置箭头以跳转以及 space(我讨厌使用 space 跳转 :P)。还要更改地图以使用字符串,因为将更改输入为数组很麻烦。
我不确定你想让演员在头部被击中时如何反应。我这样做是为了让它以与向上移动相同的速度向下弹跳,但它确实使跳跃和滑入狭窄通道变得更加困难。
所以我想我已经完成了大部分,所以你可以继续你的游戏了。
如有疑问,请在评论中提问。
// NOTE var | 0 is the same as Math.floor(var)
var mapArray = [
"# #",
"# #",
"# ### #",
"# # #",
"# ## ##### #",
"# #",
"# #",
"# ## #",
"# ## #",
"# #",
"# ##### #",
"# #",
"# #",
"# ##### #",
"# #",
"# #",
"# # ## #",
"# ### #",
"# ##### ## #",
"####################",
].map(row => row.split("").map(cell=>cell==="#" ? 1 : 0));
var levelRows = 20;
var levelCols = 20;
var tileX = 32;
var tileY = 32;
var gameArea = {
canvas : document.getElementById('canvas'),
ctx : document.getElementById('canvas').getContext("2d"),
keys : { // set them here so that can block defaults
"37" : false,
"38" : false, // also jump
"39" : false,
"32" : false, // jump
},
start : function() {
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
requestAnimationFrame(updateGameArea);
function keyEvent(e) {
if(gameArea.keys["" + e.keyCode] !== undefined){
gameArea.keys["" + e.keyCode] = e.type === "keydown"
e.preventDefault();
}
}
addEventListener('keydown', keyEvent);
addEventListener('keyup', keyEvent);
focus();
},
clear(){
var minSize = Math.min(innerWidth,innerHeight);
if (this.ctx.canvas.width !== minSize|| this.ctx.canvas.height !== minSize) {
this.ctx.canvas.width = minSize;
this.ctx.canvas.height = minSize;
}
this.ctx.setTransform(1,0,0,1,0,0);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// the next line scales the canvas rendering to fit.
this.ctx.setTransform(
minSize / (levelCols * tileX),
0,
0,
minSize/ (levelRows * tileY),
0,0
);
},
render() {
var ctx = this.ctx;
for(i=0;i<levelRows;i++){
for(j=0;j<levelCols;j++){
if(mapArray[i][j]==1){
ctx.fillStyle = "gray";
ctx.fillRect(j*tileX,i*tileY,tileX,tileY);
}
}
}
}
};
function updateGameArea() {
gameArea.clear();
actor.canMove.check();
actor.speedX = 0;
if(actor.canMove.down){
actor.speedY += actor.gravity;
}
if (gameArea.keys[39] && actor.canMove.right) {
actor.speedX = 4;
}
if (gameArea.keys[37] && actor.canMove.left) {
actor.speedX = -4;
}
if (actor.canMove.up && (gameArea.keys[32] || gameArea.keys[38])) { //jump
if (!actor.jumping) {
actor.jumping = true;
actor.speedY = -actor.speed * 3;
}
}
actor.move(); // collision is done here
gameArea.render();
actor.draw();
requestAnimationFrame(updateGameArea);
}
function Player (width, height, color, x, y) { //player component
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX=0;
this.speedY=0;
this.gravity=0.3;
this.speed=3;
this.jumping=false;
this.color = color;
this.canMove = {
left : true,
right : true,
up : true,
down : true,
actor : this,
clear(){
this.left = true;
this.right = true;
this.up = true;
this.down = true;
},
check(){
this.clear();
var x = this.actor.x | 0;
var y = this.actor.y | 0;
var cx = x / tileX | 0;
var cy = y / tileY | 0;
if(x % tileX === 0){
if(getMap(cx-1,cy) === 1){
this.left = false;
if(y % tileY !== 0 && getMap(cx-1,cy +1) === 1){
this.left = false;
}
}
if(getMap(cx+1,cy) === 1){
this.right = false;
if(y % tileY !== 0 && getMap(cx+1,cy +1) === 1){
this.right = false;
}
}
}
if(y % tileY === 0){
if(getMap(cx,cy-1) === 1){
this.up = false;
if(x % tileX !== 0 && getMap(cx+1,cy -1) === 1){
this.up = false;
}
}
if(getMap(cx,cy+1) === 1){
this.down = false;
if(x % tileX !== 0 && getMap(cx+1,cy +1) === 1){
this.down = false;
}
}
}
}
};
this.draw = function () {
var ctx = gameArea.ctx;
ctx.fillStyle = this.color;
ctx.fillRect( this.x,this.y, this.width, this.height);
};
this.move = function() {
var x = this.x;
var y = this.y;
var sx = this.speedX;
var sy = this.speedY;
var speed = Math.sqrt(sx * sx + sy * sy);
if(speed > 0){
sx /= speed;
sy /= speed;
for(var i = 0; i < speed; i++){
var xx = (x + sx * i) | 0;
var yy = (y + sy * i) | 0;
var cx = xx / tileX | 0;
var cy = yy / tileY | 0;
if(sy > 0){
if(getMap(cx,cy+1) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy+1))){
this.y = y = cy * tileY;
this.speedY = sy = speed < 4 ? 0 : -3;
this.jumping = false;
}
}else if(sy < 0){
if(getMap(cx,cy) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy))){
cy += 1;
this.y = y = cy * tileY;
this.speedY = sy = -sy; // changing -sy to 0 will stick momentarily to the roof.
}
}
if(sx > 0){
if(getMap(cx+1,cy) === 1 || (yy % tileY !== 0 && getMap(cx + 1,cy+1))){
this.x = x = cx * tileX;
this.speedX = sx = 0;
}
}else if(sx < 0){
if(getMap(cx,cy) === 1 || (yy % tileY !== 0 && getMap(cx,cy+1))){
cx += 1;
this.x = x = cx * tileX;
this.speedX = sx = 0;
}
}
}
}
this.x += this.speedX;
this.y += this.speedY;
}
}
function getMap(x,y){
if(y < 0 || y >= levelRows || x < 0 || x >= levelCols){
return 1;
}
return mapArray[y][x];
}
gameArea.start();
actor = new Player(32, 32, "green", 32, 32);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id = "canvas" width="640" height="640"></canvas>
我是游戏开发的初学者,一直在努力解决瓷砖阵列和玩家矩形之间的碰撞问题。这个游戏的特点是跳跃和重力。首先,碰撞有效,但非常笨重。有时,当玩家最终到达方块顶部并稍微靠近边缘时,它会立即传送到右侧或左侧(取决于 edge/corner 的内容)并掉落。与瓷砖底部碰撞时也会发生这种情况;玩家会立即传送到一边并继续往上走。据我了解,瓷砖碰撞检测器混淆了与一侧或另一侧的碰撞,因为当玩家撞到瓷砖的边缘时,检测器会读取它,就好像它与两者发生碰撞一样,并决定根据最高坐标速度将玩家放置在其他地方(又名 speedX 和 speedY)。我通过每次碰到瓷砖顶部时设置 speedY = 0 来解决这个问题,这解决了问题,但另一个问题出现了。现在,如果玩家在一个方块的顶部然后掉落并很快回扫,它不会与方块的侧面发生碰撞,但它会很快再次回到方块上面。
我只需要一些关于如何解决这个问题的提示,因为我尝试的一切都会导致另一个问题。我听说这是开发基于 2D 图块的游戏中的常见错误。
这是一个带有实际代码的 jsfiddle:https://jsfiddle.net/8121u356/
下面是我的整个代码的展示:
function startGame() {
gameArea.start();
actor = new player(32, 32, "green", 32, 32);
}
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,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,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,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,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,0,0,0,0,0,1,1,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,0,0,0,1,1,1,0,0,0,1,1,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]
];
var levelRows = 20;
var levelCols = 20;
var gameArea = {
canvas : document.getElementById('canvas'),
start : function() {
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
requestAnimationFrame(updateGameArea);
window.addEventListener('keydown', function (e) {
gameArea.keys = (gameArea.keys || []);
gameArea.keys[e.keyCode] = true;
});
window.addEventListener('keyup', function (e) {
gameArea.keys = (gameArea.keys || []);
gameArea.keys[e.keyCode] = false;
})
},
clear : function(){
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
render : function() {
context = this.canvas.getContext("2d");
var tileSize = 32;
for(i=0;i<levelRows;i++){
for(j=0;j<levelCols;j++){
if(mapArray[i][j]==1){
context.fillStyle = "gray";
context.fillRect(j*tileSize,i*tileSize,tileSize,tileSize);
}
}
}
}
};
function TileCollisionManager(object) {
let tileSize = 32;
let baseCol = Math.floor(object.x / tileSize);
let baseRow = Math.floor(object.y / tileSize);
let colOverlap = object.x % tileSize;
let rowOverlap = Math.floor(object.y % tileSize);
if (object.speedX > 0) {
if ((mapArray[baseRow][baseCol + 1] && !mapArray[baseRow][baseCol]) ||
(mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow + 1][baseCol] && rowOverlap)) {
object.x = baseCol * tileSize;
}
}
if (object.speedX < 0) {
if ((!mapArray[baseRow][baseCol + 1] && mapArray[baseRow][baseCol]) ||
(!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow + 1][baseCol] && rowOverlap)) {
object.x = (baseCol + 1) * tileSize;
}
}
if (object.speedY > 0) {
if ((mapArray[baseRow + 1][baseCol] && !mapArray[baseRow][baseCol]) ||
(mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow][baseCol + 1] && colOverlap)) {
object.y = ((baseRow) * tileSize);
object.jumping = false;
object.speedY = 0;
}
}
if (object.speedY < 0) {
if ((!mapArray[baseRow + 1][baseCol] && mapArray[baseRow][baseCol]) ||
(!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow][baseCol + 1] && colOverlap)) {
object.y = (baseRow + 1) * tileSize;
object.speedY = 5;
}
}
}
function updateGameArea() {
gameArea.clear();
gameArea.render();
actor.update();
actor.newPos();
actor.speedX = 0;
actor.speedY += actor.gravity;
if (gameArea.keys && gameArea.keys[39]) {
actor.speedX = 4;
}
if (gameArea.keys && gameArea.keys[37]) {
actor.speedX = -4;
}
if (gameArea.keys && gameArea.keys[32]) {
if (!actor.jumping) {
actor.jumping = true;
actor.speedY = -actor.speed * 3;
}
}
TileCollisionManager(actor);
requestAnimationFrame(updateGameArea);
}
function player (width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX=0;
this.speedY=0;
this.gravity=0.3;
this.speed=3;
this.jumping=false;
this.color = color;
this.update = function () {
ctx = gameArea.context;
ctx.fillStyle = this.color;
ctx.fillRect(
this.x,
this.y,
this.width, this.height);
};
this.newPos = function () {
this.x += this.speedX;
this.y += this.speedY;
};
为您快速修复。
我已经第 3 次见到你 post 这个问题了。您没有得到答案,因为最好的解决方案是代码很多、复杂,并且需要对代码进行大量更改。
所以我所做的是创建一个非常快速和简单的解决方案。
以正确的顺序解决冲突。
我没有检查移动结束时的位置,而是更改了代码以检查移动的每个像素。这是必需的,因为当玩家从一个位置移动到下一个位置时,您必须以正确的顺序找到碰撞。如果您在顶部或底部之前撞到一侧的墙壁,或者相反,它会有所不同,这就是导致您遇到问题的原因。您先检查了 x,然后检查了 y,这在很多情况下都是错误的做法。
我还向演员添加了一个名为 canMove
的对象,它有 4 个属性,设置在每一帧的开始,用于防止玩家向被阻挡的方向移动。如果你让玩家朝一个被阻挡的方向移动,当你在那个方向按下钥匙时它会卡在墙上。
我侵入了你的代码
对不起,我弄得有点乱,但时间不够。
另外,为了帮助我编写更改,我做了一些其他模组,我缩放了该游戏以适应 window(缩放和调整大小都在 clear
函数中完成)。我更改了键盘界面以防止默认按下的键并设置箭头以跳转以及 space(我讨厌使用 space 跳转 :P)。还要更改地图以使用字符串,因为将更改输入为数组很麻烦。
我不确定你想让演员在头部被击中时如何反应。我这样做是为了让它以与向上移动相同的速度向下弹跳,但它确实使跳跃和滑入狭窄通道变得更加困难。
所以我想我已经完成了大部分,所以你可以继续你的游戏了。
如有疑问,请在评论中提问。
// NOTE var | 0 is the same as Math.floor(var)
var mapArray = [
"# #",
"# #",
"# ### #",
"# # #",
"# ## ##### #",
"# #",
"# #",
"# ## #",
"# ## #",
"# #",
"# ##### #",
"# #",
"# #",
"# ##### #",
"# #",
"# #",
"# # ## #",
"# ### #",
"# ##### ## #",
"####################",
].map(row => row.split("").map(cell=>cell==="#" ? 1 : 0));
var levelRows = 20;
var levelCols = 20;
var tileX = 32;
var tileY = 32;
var gameArea = {
canvas : document.getElementById('canvas'),
ctx : document.getElementById('canvas').getContext("2d"),
keys : { // set them here so that can block defaults
"37" : false,
"38" : false, // also jump
"39" : false,
"32" : false, // jump
},
start : function() {
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
requestAnimationFrame(updateGameArea);
function keyEvent(e) {
if(gameArea.keys["" + e.keyCode] !== undefined){
gameArea.keys["" + e.keyCode] = e.type === "keydown"
e.preventDefault();
}
}
addEventListener('keydown', keyEvent);
addEventListener('keyup', keyEvent);
focus();
},
clear(){
var minSize = Math.min(innerWidth,innerHeight);
if (this.ctx.canvas.width !== minSize|| this.ctx.canvas.height !== minSize) {
this.ctx.canvas.width = minSize;
this.ctx.canvas.height = minSize;
}
this.ctx.setTransform(1,0,0,1,0,0);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// the next line scales the canvas rendering to fit.
this.ctx.setTransform(
minSize / (levelCols * tileX),
0,
0,
minSize/ (levelRows * tileY),
0,0
);
},
render() {
var ctx = this.ctx;
for(i=0;i<levelRows;i++){
for(j=0;j<levelCols;j++){
if(mapArray[i][j]==1){
ctx.fillStyle = "gray";
ctx.fillRect(j*tileX,i*tileY,tileX,tileY);
}
}
}
}
};
function updateGameArea() {
gameArea.clear();
actor.canMove.check();
actor.speedX = 0;
if(actor.canMove.down){
actor.speedY += actor.gravity;
}
if (gameArea.keys[39] && actor.canMove.right) {
actor.speedX = 4;
}
if (gameArea.keys[37] && actor.canMove.left) {
actor.speedX = -4;
}
if (actor.canMove.up && (gameArea.keys[32] || gameArea.keys[38])) { //jump
if (!actor.jumping) {
actor.jumping = true;
actor.speedY = -actor.speed * 3;
}
}
actor.move(); // collision is done here
gameArea.render();
actor.draw();
requestAnimationFrame(updateGameArea);
}
function Player (width, height, color, x, y) { //player component
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX=0;
this.speedY=0;
this.gravity=0.3;
this.speed=3;
this.jumping=false;
this.color = color;
this.canMove = {
left : true,
right : true,
up : true,
down : true,
actor : this,
clear(){
this.left = true;
this.right = true;
this.up = true;
this.down = true;
},
check(){
this.clear();
var x = this.actor.x | 0;
var y = this.actor.y | 0;
var cx = x / tileX | 0;
var cy = y / tileY | 0;
if(x % tileX === 0){
if(getMap(cx-1,cy) === 1){
this.left = false;
if(y % tileY !== 0 && getMap(cx-1,cy +1) === 1){
this.left = false;
}
}
if(getMap(cx+1,cy) === 1){
this.right = false;
if(y % tileY !== 0 && getMap(cx+1,cy +1) === 1){
this.right = false;
}
}
}
if(y % tileY === 0){
if(getMap(cx,cy-1) === 1){
this.up = false;
if(x % tileX !== 0 && getMap(cx+1,cy -1) === 1){
this.up = false;
}
}
if(getMap(cx,cy+1) === 1){
this.down = false;
if(x % tileX !== 0 && getMap(cx+1,cy +1) === 1){
this.down = false;
}
}
}
}
};
this.draw = function () {
var ctx = gameArea.ctx;
ctx.fillStyle = this.color;
ctx.fillRect( this.x,this.y, this.width, this.height);
};
this.move = function() {
var x = this.x;
var y = this.y;
var sx = this.speedX;
var sy = this.speedY;
var speed = Math.sqrt(sx * sx + sy * sy);
if(speed > 0){
sx /= speed;
sy /= speed;
for(var i = 0; i < speed; i++){
var xx = (x + sx * i) | 0;
var yy = (y + sy * i) | 0;
var cx = xx / tileX | 0;
var cy = yy / tileY | 0;
if(sy > 0){
if(getMap(cx,cy+1) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy+1))){
this.y = y = cy * tileY;
this.speedY = sy = speed < 4 ? 0 : -3;
this.jumping = false;
}
}else if(sy < 0){
if(getMap(cx,cy) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy))){
cy += 1;
this.y = y = cy * tileY;
this.speedY = sy = -sy; // changing -sy to 0 will stick momentarily to the roof.
}
}
if(sx > 0){
if(getMap(cx+1,cy) === 1 || (yy % tileY !== 0 && getMap(cx + 1,cy+1))){
this.x = x = cx * tileX;
this.speedX = sx = 0;
}
}else if(sx < 0){
if(getMap(cx,cy) === 1 || (yy % tileY !== 0 && getMap(cx,cy+1))){
cx += 1;
this.x = x = cx * tileX;
this.speedX = sx = 0;
}
}
}
}
this.x += this.speedX;
this.y += this.speedY;
}
}
function getMap(x,y){
if(y < 0 || y >= levelRows || x < 0 || x >= levelCols){
return 1;
}
return mapArray[y][x];
}
gameArea.start();
actor = new Player(32, 32, "green", 32, 32);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id = "canvas" width="640" height="640"></canvas>