Управление
Два способа слушать клавиатуру
В прошлой теме DOM мы реагировали на keydown сразу: нажал стрелку — шарик прыгнул на клетку. Для
игры это плохо: при зажатой клавише keydown повторяется с задержкой (как буквы в текстовом поле),
и движение получается рывками. А две клавиши одновременно (вправо + прыжок) так вообще не поймать.
Поэтому в играх события клавиатуры только запоминают, какие клавиши сейчас зажаты, а игровой цикл в каждом кадре смотрит на это и двигает героя плавно.
Множество зажатых клавиш
Set — коллекция без повторов с быстрыми add, delete и has. Идеально подходит:
const keys = new Set();
document.addEventListener("keydown", (e) => keys.add(e.key));
document.addEventListener("keyup", (e) => keys.delete(e.key));
// в update:
if (keys.has("ArrowLeft")) player.x -= 200 * dt;
if (keys.has("ArrowRight")) player.x += 200 * dt;
Повторные keydown при удержании не мешают: добавить в Set то, что там уже есть, — ничего не меняет.
Стрелки и пробел по умолчанию прокручивают страницу. Чтобы этого не было во время игры:
document.addEventListener("keydown", (e) => {
keys.add(e.key);
if (e.key.startsWith("Arrow") || e.key === " ") e.preventDefault();
});
Ещё одна мелочь: если окно потеряло фокус с зажатой клавишей, keyup не придёт, и герой будет
бежать сам. Лечится так: window.addEventListener("blur", () => keys.clear());.
Направление и скорость
Удобно сначала посчитать направление −1, 0 или 1, а потом умножить на скорость:
let dx = 0;
if (keys.has("ArrowLeft")) dx -= 1;
if (keys.has("ArrowRight")) dx += 1; // обе зажаты → 0, стоим
player.x += dx * SPEED * dt;
Если двигаться по диагонали, получается быстрее (√2 ≈ 1.41 раза), потому что складываются два
движения. В аркадах это часто прощают, а в точных играх направление нормализуют — делят dx и dy
на длину вектора.
Не выйти за край
Чтобы герой не убежал с экрана, его координату зажимают между минимумом и максимумом:
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
player.x = clamp(player.x, 0, canvas.width - player.w);
player.y = clamp(player.y, 0, canvas.height - player.h);
Максимум — ширина холста − ширина героя, потому что x — это левый край, а правый край у героя на
w пикселей дальше.
Прыжок
Прыжок — это мгновенная скорость вверх (отрицательная vy, ведь y растёт вниз), которую дальше
гасит гравитация:
if (keys.has(" ") && player.onGround) {
player.vy = -400;
player.onGround = false;
}
player.vy += GRAVITY * dt;
player.y += player.vy * dt;
if (player.y >= GROUND) { // упал на землю
player.y = GROUND;
player.vy = 0;
player.onGround = true;
}
Флаг onGround запрещает прыгать в воздухе. Высота прыжка зависит от начальной скорости и гравитации:
больше гравитация — прыжок ниже и «тяжелее». Подбирать эти числа — часть геймдизайна.
Мышь
Мышь на холсте работает так же, через события. Только координаты мыши — относительно окна, а нам нужны координаты внутри холста:
canvas.addEventListener("click", (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
console.log("клик в точке холста", x, y);
});
Итог
- События клавиатуры только записывают состояние:
keys.add/keys.delete; решения — вupdate. preventDefault()для стрелок и пробела,keys.clear()при потере фокуса.- Направление −1/0/1 × скорость ×
dt; координаты зажимаемMath.max / Math.min. - Прыжок: отрицательная
vy+ гравитация + флагonGround.