Чтобы следовать коду в этой статье, вы должны быть знакомы с JavaScript, а также иметь представление об основах HTML5 Canvas.

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

ЧТО МЫ СТРОИМ?

Готовым продуктом будет простая 2D-игра с боковой прокруткой.

  • Простая концепция, ясная цель: игроку появляются «препятствия», когда он движется вперед, и он должен прыгать, чтобы избежать их.
  • Бег вперед, назад и прыжок контролируются соответствующими клавишами курсора (стрелками) на клавиатуре.
  • Мы сохраним элементы холста несложными (только квадраты и треугольники), чтобы мы могли лучше сосредоточиться на движении элемента и эффекте «бесконечного бегуна», которые на самом деле заставят вас почувствовать себя настоящей игрой.

НАСТРОЙКА ЭТАПА

Прежде чем мы посмотрим, что составляет фактический «игровой» код, нам нужно настроить наши файлы проекта и создать элемент холста, который мы собираемся использовать.

  1. Нам потребуются файлы index.html, game.js и game.css.

ПЕРВЫЙ:

Создайте index.html и включите отображаемый код.

  • Убедитесь, что вы связали свою таблицу стилей и файл javascript в index.html.
  • Мы создаем элемент холста в теле нашего HTML.
    Мы будем обрабатывать любые стили и размеры для него в другом месте.
  • Неважно, какое название или заголовок вы даете своей игре, просто включите те же инструкции, которые сообщают игроку, какие элементы управления они могут использовать для игры.

ДАЛЕЕ: добавьте любой стиль страницы, который вы предпочитаете, чтобы окружить область холста, которая будет содержать весь игровой контент.

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

И наконец: добавьте файл game.js и готовьтесь приступить к созданию игры!

Пропускаем подробные объяснения здесь, потому что к этому моменту в статье вы либо пришли со знанием Canvas, либо перешли по ссылке вверху страницы ранее и собрали основы.

Суммируя:

  • Первое, что нам нужно сделать в нашем JavaScript, - это определить контекст для нашего холста; в результате мы получаем доступ ко всем возвращаемым методам и многому другому, которые нам понадобятся при создании в 2D.
  • Следующим шагом является изменение размера элемента холста. Если мы не укажем новую ширину и высоту, по умолчанию для элемента будет установлено значение 300 пикселей W x 150 пикселей H.
  • Вы, конечно, можете выбрать свой собственный размер, чтобы лучше соответствовать игре, которую вы хотите создать, или странице, на которую будет добавлена ​​ваша игра, но помните, что в дальнейшем в нашем коде используются несколько значений, которые полностью рассчитываются на основе имеющихся у нас размеров. установлено в примере (1120 W x 400 H).
    Любые изменения, которые вы вносите в размер холста, должны быть отражены в значениях координат, которые вы задали для элементов впоследствии.

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

СОЗДАЙТЕ ЭЛЕМЕНТ ИГРОКА

Квадрат, который будет представлять игрока в игре, будет сохранен как объект. Мы будем использовать этот объект во всем коде, чтобы:

  • Обновите свойства координат, которые действуют как текущее местоположение квадрата.
  • Определите, находится ли квадрат в данный момент в воздухе или нет
  • Определите, движется ли квадрат вперед или назад
  • Сохраняйте высоту и ширину квадрата (установите размер)
const square = {
  height: 32,
  jumping: true,
  width: 32,
  x: 0,
  xVelocity: 0,
  y: 0,
  yVelocity: 0
};

СОЗДАЙТЕ КОНТРОЛЛЕР ИГРОКА

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

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

const controller = {
  left: false,
  right: false,
  up: false,
  keyListener: function (event) {
    let key_state = (event.type == "keydown") ? true : false;
    switch (event.keyCode) {
      case 37: // left arrow
        controller.left = key_state;
        break;
      case 38: // up arrow
        controller.up = key_state;
        break;
      case 39: // right arrow
        controller.right = key_state;
        break;
    }
  }
};

СОЗДАЙТЕ «ПЕТКУ», ОБЕСПЕЧИВАЮЩУЮ АНИМАЦИЮ.

Следующий шаг довольно содержательный.

Функция цикла будет там, где мы:

  • Постоянно проверяйте, что игрок делает со своим контроллером, и соответствующим образом анимируйте квадрат игрока (направление, скорость, бег или прыжки).
  • Примените эффекты «гравитации» и «трения», которые изменят скорость квадрата игрока и обеспечат реалистичность его движения.
  • Обработайте случаи, когда квадрат игрока выходит за пределы экрана влево или вправо, и установите режим бесконечной прокрутки.
  • Нарисуйте и залейте все фигуры холста, из которых состоит игра, изначально и по мере анимации каждого нового кадра.
  1. Итак, давайте начнем с настройки условных блоков, которые будут имитировать инициируемое игроком движение квадрата, регулируя значение скорости квадратного объекта или устанавливая флаг прыжка на true.
const loop = function () {
  if (controller.up && square.jumping == false) {
    square.yVelocity -= 20;
    square.jumping = true;
  }
  if (controller.left) {
    square.xVelocity -= 0.5;
  }
  if (controller.right) {
    square.xVelocity += 0.5;
  }

— — — — — — — — — — — — — — — — — — — — — — — — — —

2. Затем мы добавляем эффекты гравитации и трения. После добавления они не особо выделяются в действии, но без них анимация выглядит довольно резкой и неуклюжей.

  square.yVelocity += 1.5; // gravity
  square.x += square.xVelocity;
  square.y += square.yVelocity;
  square.xVelocity *= 0.9; // friction
  square.yVelocity *= 0.9; // friction

— — — — — — — — — — — — — — — — — — — — — — — — — —

3. После этого мы должны начать обрабатывать особенности игры, которые станут очевидными проблемами при просмотре того, как анимация работает на экране.

Во-первых, мы должны гарантировать, что квадрат никогда не «опускается» ниже области, которую, как мы определили, будет «землей», по которой он должен проходить.

  // if the square is falling below floor line, then:
  if (square.y > 386 - 16 - 32) {
    square.jumping = false;
    square.y = 386 - 16 - 32;
    square.yVelocity = 0;
  }

Здесь текущее значение координаты y квадрата никогда не должно быть больше координаты y, которая представляет «верх» линии земли (с вычтенной высотой прыжка и высотой квадрата).

Если это так, мы останавливаем прыжок квадрата, устанавливаем координату y квадрата равной значению, которое он не может превзойти, и не позволяем квадрату «падать» (снова устанавливаем скорость y на 0). .

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

  if (square.x < -20) {
    square.x = 1220;
  } else if (square.x > 1220) {  // if the square goes off the right
    square.x = -20;
  }

— — — — — — — — — — — — — — — — — — — — — — — — — —

4. Далее происходит рисование фигур, в том числе фона, слоя земли, квадрата игрока (и, далее в статье, препятствий). Это тоже входит в функцию цикла, так что она запускает и перерисовывает фигуры в каждом кадре анимации.

  // Creates the backdrop for each frame
  context.fillStyle = "#201A23";
  context.fillRect(0, 0, 1220, 400); // x, y, width, height

  // Creates and fills the cube for each frame
  context.fillStyle = "#8DAA9D"; // hex for cube color
  context.beginPath();
  context.rect(square.x, square.y, square.width, square.height);
  context.fill();

  // Creates the "ground" for each frame
  context.strokeStyle = "#2E2532";
  context.lineWidth = 30;
  context.beginPath();
  context.moveTo(0, 385);
  context.lineTo(1220, 385);
  context.stroke();

— — — — — — — — — — — — — — — — — — — — — — — — — —

5. Мы сигнализируем браузеру, что готовы выполнить анимацию, и что перед следующей перерисовкой браузер должен обновить анимацию с помощью функции цикла.

// Updates when called to tell the browser it is ready to draw again
window.requestAnimationFrame(loop);

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

СОЗДАТЬ СЛУШАТЕЛЕЙ СОБЫТИЙ

(И НАЧАТЬ ЦИКЛ АНИМАЦИИ)

Мы настраиваем прослушиватели событий keyup и keydown для необходимых ключей в объекте контроллера.

window.addEventListener("keydown", controller.keyListener)
window.addEventListener("keyup", controller.keyListener);

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

window.requestAnimationFrame(loop);

СЛЕДУЮЩИЙ ШАГ: ПРЕПЯТСТВИЯ

На этом этапе мы почти закончили. Вы увидите, следуете ли вы своей собственной версией кода до сих пор, что у вас есть управляемый игроком квадрат, который может двигаться и прыгать, и фон, который, по-видимому, продолжается вечно.

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

  1. Во-первых, нам нужно вернуться к самому началу кода.
    В пространстве под нашим определением контекста и над объявлением нашего квадрата нам нужно добавить следующее:
// Start the frame count at 1 (also level 1)
let frameCount = 1;
// Set the number of obstacles to match the current "level" number
let obCount = frameCount;
// Create a collection to hold the randomly generated x coordinates
const obXCoors = [];

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

— — — — — — — — — — — — — — — — — — — — — — — — — —

2. Существует простой способ узнать, когда пользователь завершил «уровень» и начинает новый, и это фактически уже установлено в существующем коде.

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

** В качестве примечания, чтобы объяснить, почему мы не можем просто рисовать объекты препятствий , когда пользователь покидает экран: T порядок рисования фигур выполняется материя.
Таким образом, мы можем создать наши случайные координаты заранее, но должны подождать, чтобы нарисовать сами препятствия, пока после не будет нарисован фон и другие формы. * *

// Create the obstacles for each frame
const nextFrame = () => {
  // increase the frame / "level" count
  frameCount++;
  for (let i = 0; i < obCount; i++) {
    // Randomly generate the x coordinate for the
       top corner start of each triangle
    obXCoor = Math.floor(Math.random() * (1165 - 140 + 1) + 140);
    obXCoors.push(obXCoor);
  }
}
  • Увеличьте frameCount (что означает, что был достигнут следующий уровень), что увеличивает количество препятствий, которые будут частью следующей сцены.
  • Начните итерацию, которая будет продолжаться до текущего количества препятствий.
  • Сгенерируйте это число (количество препятствий) в случайных координатах x
    «Случайно» между координатами x от 140 до 1165 - которые были выбраны, чтобы гарантировать, что никакие препятствия не появятся слишком близко к началу или прямо к концу , игрового окна (совершенно несправедливо по отношению к игроку и его невозможно обыграть).
  • Добавьте каждую координату x в коллекцию координат, чтобы формы препятствий можно было динамически рисовать позже в функции цикла.

— — — — — — — — — — — — — — — — — — — — — — — — — —

3. Очень небольшая корректировка, нам нужно добавить вызов nextFrame () к условному выражению, которое проверяет, что игрок выходит из правой части окна.

} else if (square.x > 1220) {// if square goes past right boundary
square.x = -20;
nextFrame();
}

(Остальная часть этого условного выражения уже присутствует в вашем коде, я показал это здесь только для того, чтобы вы могли видеть вызов метода nextFrame в контексте.)

— — — — — — — — — — — — — — — — — — — — — — — — — —

4. Фактическое последнее изменение заключается в добавлении секции рисования для форм препятствий в нужную область в теле функции цикла. Мы вставим это под рисунком для квадрата и над рисунком для земли.

  // Create the obstacles for each frame
  // Set the standard obstacle height
  const height = 200 * Math.cos(Math.PI / 6);
  context.fillStyle = "#FBF5F3"; // hex for triangle color
  obXCoors.forEach((obXCoor) => {
    context.beginPath();
    // (x = random, y = coor. on "ground")
    context.moveTo(obXCoor, 385);
    // (x = ^random + 20, y = coor. on "ground")
    context.lineTo(obXCoor + 20, 385);
    // (x = ^random + 10, y = peak of triangle)
    context.lineTo(obXCoor + 10, 510 - height);
    context.closePath();
    context.fill();
  });
  • Рассчитайте стандартную высоту, которой мы будем следовать для всех препятствий
  • Выполните итерации по набору случайно сгенерированных координат x, которые определят, где по ширине окна на земле появится препятствие.
  • Сместите каждое значение координаты x на согласованные значения (20 и 10), чтобы нарисовать равносторонний треугольник одинакового размера независимо от того, какой окажется координата x.

КОНЕЧНЫЙ РЕЗУЛЬТАТ

После всего, что мы рассмотрели, ваш файл game.js должен выглядеть так:

  • Игрок может двигаться вперед или назад и прыгать.
  • Действия и движения игрока будут выглядеть плавными и реалистичными.
    - Есть «гравитация», которая влияет на квадрат, когда он падает обратно после прыжка
    - Есть «трение», которое влияет на квадрат, когда он скользит вперед или назад
    - нажатие стрелки вперед или назад, удерживая другую, замедлит, а затем немедленно остановит движение квадрата, так как на объект одновременно действуют две конфликтующие силы.
  • Для простого и быстрого эффекта количество препятствий будет соответствовать текущему «уровню» (который, как считается, увеличивается каждый раз, когда игрок проходит через следующий кадр).

ЗАКЛЮЧЕНИЕ::

Вот и все, простой 2D-скроллер, созданный с помощью JavaScript и HTML5 Canvas!

Основная цель статьи - показать, насколько легко можно приступить к созданию какой-нибудь игры; когда часто может показаться, что достаточно много фундаментального обучения нужно сделать только для того, чтобы ворваться в мир разработчиков игр.

Некоторые предложения и следующие шаги по быстрому улучшению игры:

  • Добавьте индикатор на экран, который информирует игрока, на каком «уровне» он находится в данный момент.
  • Добавить форму обнаружение столкновений; таким образом, игрок теряет очки или напрямую терпит поражение, если сталкивается с одним или некоторым количеством препятствий.
  • Оптимизируйте создание новых препятствий:
    - Увеличивайте количество новых препятствий каждые пару уровней, а не постоянно, по одному на каждом новом уровне.
    - Улучшите рандомизацию расположения препятствий, чтобы ограничить время их прохождения группа вместе.
  • Добавьте музыку или другие эффекты, например освещение, чтобы улучшить впечатление от проигрывателя.

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

Спасибо за чтение!