Создание игры-лабиринта на языке JavaScript с помощью HTML5 Canvas.

Для этой игры мы не будем генерировать лабиринт, а возьмем готовый рисунок. Столкновения со стенками будем определять, анализируя цвет пикселей.

Разметка

Создадим холст и картинку-маркер, которая обозначает положение игрока:

<canvas id="canvas"></canvas>
<img id="face" src="face.png">
<img id="maze" src="maze.png">

Также выводим на страницу картинку лабиринта.

img { display:none; }
canvas { border:5px double black; }

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

Инициализация лабиринта


window.onload = function() {
  let canvas = document.getElementById("canvas");
  let context = canvas.getContext("2d");

  let mazeImg = document.getElementById("maze");
  let faceImg = document.getElementById("face");

  // начальная позиция
  let x = 0;
  let y = 0;

  let timer;
}

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

Также сохраняем ссылки на изображения и создаем переменные x и y для хранения положения маркера и timer для перерисовки холста.

Отрисовка лабиринта

Запускаем функцию drawMaze, которая должна отрисовать лабиринт. Передаем ей изображение лабиринта и начальные координаты маркера:

// отрисовать фон лабиринта
drawMaze(268, 5);

// отрисовка фона
function drawMaze(startingX, startingY) {
  canvas.width = mazeImg.width;
  canvas.height = mazeImg.height;

  // Рисуем лабиринт
  context.drawImage(mazeImg, 0,0);

  // Рисуем значок
  x = startingX;
  y = startingY;
   
  context.drawImage(faceImg, x, y);
  context.stroke();
}

Эта функция просто отрисовывает на холсте два изображения и обновляет начальную позицию маркера.

Перемещение маркера

Для перемещения маркера игрока введем еще две переменные, которые будут сохранять направление смещения маркера по обеим осям:

// смещение
let dx = 0;
let dy = 0;

При нажатии на клавиши-стрелки, мы будем изменять смещение маркера.

// обработка нажатия кнопок
window.onkeydown = processKey;

function processKey(e) {
  e.preventDefault();

  // Если значок находится в движении, 
  // останавливаем его
  dx = 0;
  dy = 0;

  // Если нажата стрелка вверх, 
  // начинаем двигаться вверх
  if (e.keyCode == 38) {
    dy = -1;
  }

  // Если нажата стрелка вниз, 
  // начинаем двигаться вниз
  if (e.keyCode == 40) {
    dy = 1;
  }

  // Если нажата стрелка влево, 
  // начинаем двигаться влево
  if (e.keyCode == 37) {
    dx = -1;
  }

  // Если нажата стрелка вправо, 
  // начинаем двигаться вправо
  if (e.keyCode == 39) {
    dx = 1;
  }
}

Обновление кадра

Так как маркер игрока перемещается по лабиринту, нам нужно постоянно перерисовывать кадр, отображая это перемещение. Для этого мы будем использовать метод window.requestAnimationFrame, который выполнит функцию перерисовки в самый оптимальный момент.

function drawMaze(startingX, startingY) {
  // ...

  // Рисуем следующий кадр
  window.requestAnimationFrame(drawFrame);
}

// Отрисовка кадра
function drawFrame() {
  // Обновляем кадр только если значок движется
  if (dx != 0 || dy != 0) {
    // Закрашиваем перемещение значка желтым цветом
    context.beginPath();
    context.fillStyle = "rgb(254,244,207)";
    context.rect(x, y, 15, 15);
    context.fill()

    // Обновляем координаты значка, создавая перемещение
    x += dx;
    y += dy;

    // Перерисовываем значок
    context.drawImage(faceImg, x, y);
  }

  // Рисуем следующий кадр
  window.requestAnimationFrame(drawFrame);
}

Теперь, если вы нажмете на стрелки, маркер начнет движение. Браузер постоянно запускает функцию drawFrame, она проверяет смещение маркера и при необходимости перерисовывает его в новом месте.

Путь маркера закрашивается желтым цветом.

Проверка столкновений

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

// Отрисовка кадра
function drawFrame() {
  // Обновляем кадр только если значок движется
  if (dx != 0 || dy != 0) {
    // ...

    // Обновляем координаты значка, создавая перемещение
    x += dx;
    y += dy;


     // Проверка столкновения со стенками лабиринта
    if (checkForCollision()) {
      x -= dx;
      y -= dy;
      dx = 0;
      dy = 0;
    }

    // Перерисовываем значок
    context.drawImage(faceImg, x, y);
  }

  // Рисуем следующий кадр
  window.requestAnimationFrame(drawFrame);
}

function checkForCollision() {
  // Перебираем все пиксели лабиринта и инвертируем их цвет
  let imgData = context.getImageData(x-1, y-1, 15+2, 15+2);
  let pixels = imgData.data;

  // Получаем данные для одного пикселя
  for (let i = 0; n = pixels.length, i < n; i += 4) {
    let red = pixels[i];
    let green = pixels[i+1];
    let blue = pixels[i+2];
    let alpha = pixels[i+3];

    // Смотрим на наличие черного цвета стены, 
    // что указывает на столкновение
    if (red == 0 && green == 0 && blue == 0) {
      return true;
    }
      
    // Смотрим на наличие серого цвета краев, 
    // что указывает на столкновение
    if (red == 169 && green == 169 && blue == 169) {
      return true;
    }
  }
    
  // Столкновения не было
  return false;
}

Функция checkForCollision просто берет все пиксели холста, на котором нарисован лабиринт, в том месте, где находится маркер, и проверяет цвет каждого пикселя.

Проверка прохождения

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

// Отрисовка кадра
function drawFrame() {
  // Обновляем кадр только если значок движется
  if (dx != 0 || dy != 0) {
    // ...

    // Проверяем дошел ли пользователь до финиша
    if (y > (canvas.height - 17)) {
      alert("Ты победил!");
      return; 
    }
  }

  // Рисуем следующий кадр
  window.requestAnimationFrame(drawFrame);
}

Если выход найден, то останавливаем игру и не планируем следующую перерисовку кадра.

Код игры

Полный код файла script.js:

window.onload = function() {
  let canvas = document.getElementById("canvas");
  let context = canvas.getContext("2d");

  let mazeImg = document.getElementById("maze");
  let faceImg = document.getElementById("face");

  // обработка нажатия кнопок
  window.onkeydown = processKey;

  // начальная позиция
  let x = 0;
  let y = 0;

  // смещение
  let dx = 0;
  let dy = 0;

  let timer;

  // отрисовать фон лабиринта
  drawMaze(268, 5);

  // отрисовка фона
  function drawMaze(startingX, startingY) {
    canvas.width = mazeImg.width;
    canvas.height = mazeImg.height;

    // Рисуем лабиринт
    context.drawImage(mazeImg, 0,0);

    // Рисуем значок
    x = startingX;
    y = startingY;
    context.drawImage(faceImg, x, y);
    context.stroke();

    // Рисуем следующий кадр
    window.requestAnimationFrame(drawFrame);
  }

  // Обработка нажатия кнопок
  function processKey(e) {
    e.preventDefault();

    // Если значок находится в движении, 
    // останавливаем его
    dx = 0;
    dy = 0;

    // Если нажата стрелка вверх, 
    // начинаем двигаться вверх
    if (e.keyCode == 38) {
      dy = -1;
    }

    // Если нажата стрелка вниз, 
    // начинаем двигаться вниз
    if (e.keyCode == 40) {
      dy = 1;
    }

    // Если нажата стрелка влево, 
    // начинаем двигаться влево
    if (e.keyCode == 37) {
      dx = -1;
    }

    // Если нажата стрелка вправо, 
    // начинаем двигаться вправо
    if (e.keyCode == 39) {
      dx = 1;
    }
  }

  // Отрисовка кадра
  function drawFrame() {
    // Обновляем кадр только если значок движется
    if (dx != 0 || dy != 0) {
      //Закрашиваем перемещение значка желтым цветом
      context.beginPath();
      context.fillStyle = "rgb(254,244,207)";
      context.rect(x, y, 15, 15);
      context.fill();

      // Обновляем координаты значка, создавая перемещение
      x += dx;
      y += dy;

      // Проверка столкновения со стенками лабиринта
      if (checkForCollision()) {
        x -= dx;
        y -= dy;
        dx = 0;
        dy = 0;
      }

      // Перерисовываем значок
      context.drawImage(faceImg, x, y);

      // Проверяем дошел ли пользователь до финиша
      if (y > (canvas.height - 17)) {
        alert("Ты победил!");
        return; 
      }
    }

    // Рисуем следующий кадр
    window.requestAnimationFrame(drawFrame);
  }

  function checkForCollision() {
    // Перебираем все пиксели лабиринта и инвертируем их цвет
    let imgData = context.getImageData(x-1, y-1, 15+2, 15+2);
    let pixels = imgData.data;

    // Получаем данные для одного пикселя
    for (let i = 0; n = pixels.length, i < n; i += 4) {
      let red = pixels[i];
      let green = pixels[i+1];
      let blue = pixels[i+2];
      let alpha = pixels[i+3];

      // Смотрим на наличие черного цвета стены, 
      // что указывает на столкновение
      if (red == 0 && green == 0 && blue == 0) {
        return true;
      }
      
      // Смотрим на наличие серого цвета краев, 
      // что указывает на столкновение
      if (red == 169 && green == 169 && blue == 169) {
        return true;
      }
    }
    
    // Столкновения не было
    return false;
  }
}

See the Pen maze by FurryCat (@mohnatus-the-lessful) on CodePen.

0 комментариев

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован