Создание игры-лабиринта на языке 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 комментариев