Когда-то давно я нашёл, дескать, "самую сложную игру" ( http://caniplay.ru/flash/4565 ), и вот, спустя несколько лет, решил сделать что-то подобное, только не шибко хардкорное. Дело в том, что столкновения-то я обнаруживать научился, но вот как сделать объект твёрдым при столкновении - это уже посложнее. Я вообще не понимаю что в коде не так, вроде всё логично, но js так не считает.
Ещё с картой уровня проблемы: верхняя часть съезжает. Логикой это дело не возьмёшь. Помогите, пожалуйста.
P.s. только чёткий и ясный ответ, не псевдокод или тупо объяснение с хабра, а именно рабочий пример.
<!DOCTYPE html>
<html>
<head>
<title>.</title>
<meta charset="UTF-8">
</head>
<body style="margin: 0">
<canvas id="can" style="display: block"></canvas>
<script type="text/javascript">
function getId(o){return document.getElementById(o)};
class Block {
constructor(position, size, color, type) {
this.position = position,
this.size = size,
this.sys = {color: color}
}
};
let can = getId("can"), ctx = can.getContext('2d'), game = {
size: 35
},
player = {
position: {
x: innerWidth / 2 - 25,
y: innerHeight - 25 - 300
},
size: {
width: 50,
height: 50
},
sys: {
color: "#AA00FF",
velocity: {
x: 0,
y: 0,
friction: .9,
plus: 1
},
keys: {
w: false,
a: false,
s: false,
d: false
},
scroll: {
x: 0,
y: 0
}
}
}, blocks = [], createBlock = (x, y, w, h, color) => blocks.push( new Block({x: x, y: y}, {width: w, height: h}, color)),
collides = (pl1, pl2) => (pl1.position.x + pl1.size.width < pl2.position.x ||
pl2.position.x + pl2.size.width < pl1.position.x ||
pl1.position.y + pl1.size.height < pl2.position.y ||
pl2.position.y + pl2.size.height < pl1.position.y) ? false : true,
map = `
bbbbbbbbbbbbbbbbbbbbbbbbbbbb-b
b----------------------------b
b----------------------------b
b----------------------------b
b----------------------------b
b----------------------------b
b-------------P--------------b
b-------------b--------------b
b----------------------------b
b----------------------------b
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
`.trim().split('\n');
// Тут какая-то фигня: блоки вверху "слетают" почему-то.
for(let y = 0; y < map.length; y++) {
for(let x = 0; x < map[y].length; x++) {
let sym = map[y][x],
w = game.size,
h = game.size;
if(sym == "P") {
player.position.x = x * w;
player.position.y = y * h;
player.size.width = w;
player.size.height = h
} else if(sym == "b") {
createBlock(x * w, y * h, w, h, "#902")
}
}
};
onresize = () => {
can.width = innerWidth;
can.height = innerHeight
};
onresize();
document.onkeydown = e => {
let kc = e.keyCode;
if(kc == 'W'.charCodeAt()) player.sys.keys.w = true;
if(kc == 'A'.charCodeAt()) player.sys.keys.a = true;
if(kc == 'S'.charCodeAt()) player.sys.keys.s = true;
if(kc == 'D'.charCodeAt()) player.sys.keys.d = true;
if(kc == 'Z'.charCodeAt()) blocks.pop()
};
document.onkeyup = e => {
let kc = e.keyCode;
if(kc == 'W'.charCodeAt()) player.sys.keys.w = false;
if(kc == 'A'.charCodeAt()) player.sys.keys.a = false;
if(kc == 'S'.charCodeAt()) player.sys.keys.s = false;
if(kc == 'D'.charCodeAt()) player.sys.keys.d = false
};
(loop = () => {
ctx.fillStyle = "rgba(0, 0, 0, .24903)";
ctx.fillRect(-3e38, -3e38, 3e38 * 2, 3e38 * 2);
// Скроллинг
player.sys.scroll.x = -player.position.x + innerWidth / 2 - player.size.width / 2;
player.sys.scroll.y = -player.position.y + innerHeight / 2 - player.size.height / 2;
ctx.save();
ctx.translate(player.sys.scroll.x, player.sys.scroll.y);
if(player.sys.keys.w) player.sys.velocity.y -= player.sys.velocity.plus;
if(player.sys.keys.s) player.sys.velocity.y += player.sys.velocity.plus;
if(player.sys.keys.a) player.sys.velocity.x -= player.sys.velocity.plus;
if(player.sys.keys.d) player.sys.velocity.x += player.sys.velocity.plus;
player.sys.velocity.y *= player.sys.velocity.friction;
player.sys.velocity.x *= player.sys.velocity.friction;
player.position.y += player.sys.velocity.y;
player.position.x += player.sys.velocity.x;
blocks.forEach(block => {
// ВОТ ТУТ ВООБЩЕ НИЧЕГО НЕ ПОНЯТНО
if(collides(block, player)) {
if(player.position.y + player.size.height + player.sys.velocity.y > block.position.y) {
player.position.y = block.position.y - block.size.height
} else if(player.position.y - player.sys.velocity.y < block.position.y + block.size.height) {
player.position.y = block.position.y + block.size.height
}
};
ctx.fillStyle = block.sys.color;
ctx.fillRect(block.position.x, block.position.y, block.size.width, block.size.height)
});
ctx.fillStyle = player.sys.color;
ctx.fillRect(player.position.x, player.position.y, player.size.width, player.size.height);
ctx.restore();
requestAnimationFrame(loop)
})();
</script>
</body>
</html>
Вот, я думаю как-то так, даже кое где тверже чем хотелось бы =).
Баги с картой уровня - распечатайте содержимое строки, образующей уровень и увидите там \t
Добавил там Вам всякого рисования для отладки:
class Block {
constructor(position, size, color, type) {
this.position = position,
this.size = size,
this.sys = {
color: color
}
}
};
let debug = false;
let ctx = can.getContext('2d'),
game = {size: 35},
player = {
position: {x: innerWidth / 2 - 25,y: innerHeight - 25 - 300},
size: {width: 50, height: 50},
sys: {
color: "#AA00FF",
velocity: {x: 0, y: 0,friction: .9,plus: .99},
keys: {w: false,a: false,s: false, d: false},
scroll: {x: 0, y: 0}
}
},
blocks = [],
createBlock = (x, y, w, h, color) =>
blocks.push(new Block({x: x, y: y}, {width: w, height: h}, color)),
collides = (pl1, pl2) => !(
pl1.position.x + pl1.size.width < pl2.position.x ||
pl2.position.x + pl2.size.width < pl1.position.x ||
pl1.position.y + pl1.size.height < pl2.position.y ||
pl2.position.y + pl2.size.height < pl1.position.y),
map = `
bbbbbbbbbbbbbbbbbbbbbbbbb----b
b----------------------------b
b-------------------------bbbb
b----------------------------b
b----b-----------------------b
b----------------------------b
b-------------P--------------b
b-------------b--------------b
b-----------------------b----b
b----------------------------b
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
`.trim().split('\t').join('').split('\n');
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
let sym = map[y][x], w = game.size, h = game.size;
if (sym == "P") {
player.position.x = x * w;
player.position.y = y * h;
player.size.width = w;
player.size.height = h
} else if (sym == "b") {
createBlock(x * w, y * h, w, h, "#902")
}
}
};
onresize = () => {can.width = innerWidth;can.height = innerHeight};
onresize();
document.onkeydown = e => {
let kc = e.keyCode;
if (kc == 'W'.charCodeAt()) player.sys.keys.w = true;
if (kc == 'A'.charCodeAt()) player.sys.keys.a = true;
if (kc == 'S'.charCodeAt()) player.sys.keys.s = true;
if (kc == 'D'.charCodeAt()) player.sys.keys.d = true;
if (kc == 'Z'.charCodeAt()) blocks.pop()
};
document.onkeyup = e => {
let kc = e.keyCode;
if (kc == 'W'.charCodeAt()) player.sys.keys.w = false;
if (kc == 'A'.charCodeAt()) player.sys.keys.a = false;
if (kc == 'S'.charCodeAt()) player.sys.keys.s = false;
if (kc == 'D'.charCodeAt()) player.sys.keys.d = false
};
(loop = () => {
requestAnimationFrame(loop);
ctx.fillStyle = "rgba(0, 0, 0, .24903)";
ctx.fillRect(-3e38, -3e38, 3e38 * 2, 3e38 * 2);
// Скроллинг
player.sys.scroll.x = -player.position.x + innerWidth / 2 - player.size.width / 2;
player.sys.scroll.y = -player.position.y + innerHeight / 2 - player.size.height / 2;
ctx.save();
ctx.translate(player.sys.scroll.x, player.sys.scroll.y);
if (player.sys.keys.w) player.sys.velocity.y -= player.sys.velocity.plus;
if (player.sys.keys.s) player.sys.velocity.y += player.sys.velocity.plus;
if (player.sys.keys.a) player.sys.velocity.x -= player.sys.velocity.plus;
if (player.sys.keys.d) player.sys.velocity.x += player.sys.velocity.plus;
player.sys.velocity.y *= player.sys.velocity.friction;
player.sys.velocity.x *= player.sys.velocity.friction;
let top = null, bottom = null, left = null, right = null;
blocks.forEach(block => {
let a, collision = collides(block, player);
if (collision) {
// определяем с какой стороны стена и запоминаем ограничения
let dy = player.position.y - block.position.y;
let dx = player.position.x - block.position.x;
a = Math.atan2(dy, dx)/Math.PI*180;
if (a> 45 && a< 135) top = block.position.y+block.size.height;
if (a<-45 && a>-135) bottom = block.position.y-player.size.height;
if (a< 45 && a> -45) left = block.position.x+block.size.width;
if (a>135 || a<-135) right = block.position.x-player.size.width;
};
ctx.fillStyle = debug && collision ? 'wheat' : block.sys.color;
ctx.fillRect(block.position.x, block.position.y, block.size.width, block.size.height)
if (debug && collision){
let cx = block.position.x+block.size.width/2;
let cy = block.position.y+block.size.height/2;
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(cx+Math.cos(a/180*Math.PI)*15,cy+Math.sin(a/180*Math.PI)*15);
ctx.stroke();
}
});
let f = 0.66; // имитация трения об препятствия
let fritcion = (bottom?f:1)*(top?f:1)*(left?f:1)*(right?f:1);
player.position.y += player.sys.velocity.y * fritcion;
player.position.x += player.sys.velocity.x * fritcion;
if (bottom != null)
player.position.y = Math.min(player.position.y, bottom);
if (top != null)
player.position.y = Math.max(player.position.y, top);
if (left != null)
player.position.x = Math.max(player.position.x, left);
if (right != null)
player.position.x = Math.min(player.position.x, right);
ctx.fillStyle = player.sys.color;
ctx.fillRect(player.position.x, player.position.y, player.size.width, player.size.height);
if (!debug)
return ctx.restore();
ctx.fillStyle = 'green';
if (player.sys.velocity.y < 0 && top)
ctx.fillRect(player.position.x, player.position.y, player.size.width, 4);
if (player.sys.velocity.y > 0 && bottom)
ctx.fillRect(player.position.x, player.position.y+player.size.height-4, player.size.width, 4);
if (player.sys.velocity.x < 0 && left)
ctx.fillRect(player.position.x, player.position.y, 4, player.size.height);
if (player.sys.velocity.x > 0 && right)
ctx.fillRect(player.position.x+player.size.width-4, player.position.y, 4, player.size.height);
ctx.restore();
})();
body, canvas {margin: 0; display: block}
<canvas id="can"></canvas>
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском