Герой уже нарисован. Он смотрит на тебя с canvas и ничего не делает.

Это нормальный первый результат. Но в какой-то момент хочется нажать стрелку и увидеть, как он поехал. Так начинается движение персонажа на JavaScript canvas. Не с физики и не с игрового движка. С четырёх флагов, скорости и одного цикла.

Сначала запомни состояние клавиш

Самая простая ошибка — двигать героя прямо внутри keydown. Нажал клавишу — прибавил десять к x. Работает, пока не попробуешь удерживать кнопку. Дальше скорость зависит от того, как браузер повторяет событие.

Лучше разделить две вещи. Обработчик говорит: «стрелка зажата». Игровой цикл решает: «тогда каждый кадр двигаем героя».

const keys = {
  left: false,
  right: false,
  up: false,
  down: false
};

document.addEventListener('keydown', (event) => {
  if (event.key === 'ArrowLeft') keys.left = true;
  if (event.key === 'ArrowRight') keys.right = true;
  if (event.key === 'ArrowUp') keys.up = true;
  if (event.key === 'ArrowDown') keys.down = true;
});

document.addEventListener('keyup', (event) => {
  if (event.key === 'ArrowLeft') keys.left = false;
  if (event.key === 'ArrowRight') keys.right = false;
  if (event.key === 'ArrowUp') keys.up = false;
  if (event.key === 'ArrowDown') keys.down = false;
});

Скорость — обычное число

Положи скорость в объект героя. Например, 3 пикселя за кадр. Если зажата левая стрелка, уменьшаем x. Если правая — увеличиваем. С y то же самое.

Не ставь сразу скорость 20. Сначала важно увидеть сам принцип: клавиша меняет состояние, состояние меняет координату, координата меняет рисунок.

Где всё это происходит

В игровом цикле. Он очищает canvas, обновляет состояние героя, проверяет границы и рисует его заново. Без очистки за персонажем останется след. Без повторного вызова он так и останется на старой позиции.

function update() {
  if (keys.left) hero.x -= hero.speed;
  if (keys.right) hero.x += hero.speed;
  if (keys.up) hero.y -= hero.speed;
  if (keys.down) hero.y += hero.speed;
}

function loop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  update();
  keepInside();
  drawHero();
  requestAnimationFrame(loop);
}

loop();

Если хочешь отдельно разобраться, почему браузер вызывает кадры снова и снова, посмотри статью про игровой цикл.

Почему герой уезжает за край

Потому что число не знает, где заканчивается canvas. Для него можно спокойно продолжать движение в минус, пока на экране не останется пустота.

После обновления позиции прижми героя к границам. Радиус нужен затем, чтобы за край не уехала половина головы.

function keepInside() {
  const r = hero.radius;

  if (hero.x < r) hero.x = r;
  if (hero.x > canvas.width - r) hero.x = canvas.width - r;
  if (hero.y < r) hero.y = r;
  if (hero.y > canvas.height - r) hero.y = canvas.height - r;
}

Что должно получиться

Стрелки двигают котика. Он не исчезает за краем. Ввод, движение и рисунок лежат в разных функциях. Это ещё не большая игра. Но уже не картинка. У тебя появился мир, который реагирует на действие.

Если героя пока нет, начни с урока «рисуем героя». Двигать понятнее то, что ты сначала собрал сам.

Обложка книги JavaScript с нуля: учимся программировать, создавая игры

Книга по JavaScript для начинающих

«JavaScript с нуля: учимся программировать, создавая игры»

Пошаговый формат с маленькими практическими задачами: от базового синтаксиса до первых игровых механик без перегруза теорией.

Читать книгу на ЛитРес →