recursal/MDN huggingface dataset:
2D игра на чистом JavaScript
============================

* Следующая статья »

В этом пошаговом руководстве мы создадим простую игру **MDN Breakout,** написанную на чистом JavaScript и отрендеренную на HTML5 `<canvas>`.

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

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

![Gameplay screen from the game MDN Breakout where you can use your paddle to bounce the ball and destroy the brick field, with keeping the score and lives.](/ru/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/mdn-breakout-gameplay.png)

Детали к урокам
---------------

Все уроки и версии игры MDN Breakout доступны в GitHub:

1. Создание Canvas и рисование на нем (en-US)
2. Движение мяча (en-US)
3. Реакция при столкновении со стеной (en-US)
4. Управление (en-US)
5. Конец игры (en-US)
6. Построение поля кирпичей (en-US)
7. Реакция при столкновении (en-US)
8. Счёт и выигрыш (en-US)
9. Контроль мышью (en-US)
10. Заключение (en-US)

Лучший способ получить надёжные знания в области разработки браузерных игр — это начать с чистого JavaScript. Затем можно выбрать любой фреймворк для использования в своих проектах. Фреймворки — это инструменты, созданные на языке JavaScript; поэтому, даже если вы планируете работать с ними, нелишним будет сначала изучить сам язык, чтобы понимать, что именно происходит внутри. Фреймворки ускоряют разработку и помогают справиться со скучными частями игры, но если что-то работает не так, как ожидалось, всегда можно попытаться отладить код или написать собственное решение на чистом JavaScript.

**Примечание:** Если вам интересно узнать о разработке двухмерных игр с помощью игровой библиотеки, ознакомьтесь с альтернативной серией статей 2D игра Breakout с использованием Phaser (en-US).

**Примечание:** Эту серию статей можно использовать как материал для практических занятий по разработке игр. Также можно воспользоваться набором инструментов Gamedev Canvas Content Kit, основанном на этом уроке, если нужно сделать доклад о разработке игр в целом.

Следующий шаг
-------------

Ладно, давайте начнём! Перейдите к первой главе — Создание Canvas и рисование на нем (en-US).

* Следующая статья » (en-US)
Масштабирование
===============

* « Предыдущая статья
* Следующая статья »

Это **2-й урок** из 16, которые входят в руководство 2D игра на Phaser. Вы можете найти исходный код этого урока на Gamedev-Phaser-Content-Kit/demos/lesson02.html.

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

Масштабирование
---------------

В Phaser есть специальный объект `scale`, которые имеет несколько полезных методов и свойств. Измените вашу функцию `preload()` так, как показано ниже:


```js
function preload() {
  game.scale.scaleMode = Phaser.ScaleManager.SHOW\_ALL;
  game.scale.pageAlignHorizontally = true;
  game.scale.pageAlignVertically = true;
}
```


`scaleMode` имеет несколько опций, которые определяют, как `<canvas>` будет масштабироваться:

* `NO_SCALE` — ничего не масштабировать.
* `EXACT_FIT` — масштабировать с полным заполнением пустого места вертикально и горизонтально, не соблюдая соотношение (aspect ratio).
* `SHOW_ALL` — масштабирует игру, но сохраняет соотношение так, что картинки не будут искажаться, как и при предыдущем значении. Возможно наличие чёрных полос по краям экрана, но мы можем жить с этим.
* `RESIZE` — создаёт `<canvas>` с доступными шириной и высотой так, что вы можете помещать объекты в игру динамически; это расширенный режим (advanced mode).
* `USER_SCALE` — позволяет вам делать динамическое масштабирование, подсчёт размеров, масштаб и соотношение самостоятельно; опять же это расширенный режим.

Две другие строчки кода в функции `preload()` отвечают за вертикальное и горизонтальное выравнивание элемента `<canvas>`, так что он всегда будет находиться по центру независимо от размера экрана.

Изменение цвета фона
--------------------

Мы также можем сделать фон нашего элемента `<canvas>` таким, каким захотим, чтобы он не оставался постоянно чёрным. Объект `stage` имеет свойство `backgroundColor` для этого. Мы можем изменить значение, используя синтаксис CSS для цветов. Добавьте эту строку после трёх, недавно добавленных:


```js
game.stage.backgroundColor = "#eee";
```

Сравните свой код
-----------------

Вы можете сравнить весь код из этого урока со своим и поиграть с ним, чтобы понять, как он работает:

Следующее
---------

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

* « Предыдущая статья
* Следующая статья »
Game over
=========

* « Предыдущая статья
* Следующая статья »

Это **8** из 16 уроков руководства разработки игры с помощью Phaser. Исходный код этого урока вы можете найти здесь: Gamedev-Phaser-Content-Kit/demos/lesson08.html.

Чтобы разнообразить игру, давайте добавим возможность проигрыша — если вы не отобьёте мячик и дадите ему упасть на пол, то game over.

Проигрыш
--------

Чтобы добавить возможность проигрыша, давай те отключим определение коллизии с нижней гранью экрана. Добавьте следующую строку кода в функцию `create()`, сразу после кода инициализации всех атрибутов мячика:


```js
game.physics.arcade.checkCollision.down = false;
```


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


```js
ball.checkWorldBounds = true;
ball.events.onOutOfBounds.add(function () {
  alert("Game over!");
  location.reload();
}, this);
```


Мы сделали так, чтобы мячик проверял границы игрового мира (в нашем случае границы `<canvas>`) и, в случае выхода за их пределы (событие `onOutOfBounds`), выполнял функцию, которую мы привязали к этому событию. После закрытия всплывающего окна с сообщением 'Game over!', происходит перезагрузка страницы, чтобы можно было сыграть снова.

Сравните свой код
-----------------

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

Следующий шаг
-------------

Базовый геймплей готов. Но какой арканоид без разбивания кирпичиков?

* « Предыдущая статья
* Следующая статья »
Обработка коллизий
==================

* « Предыдущая статья
* Следующая статья »

Это **10** из 16 уроков руководства разработки игры с помощью Phaser. Исходный код этого урока вы можете найти здесь: Gamedev-Phaser-Content-Kit/demos/lesson10.html.

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

Проверяем коллизии Мячик/Кирпич
-------------------------------

Физический движок облегчает очень многие задачи — нам понадобится, всего лишь, несколько простых кусочков кода. Для начала, добавим в функцию `update()` строчку проверки коллизий между мячиком и кирпичами, как показано ниже:


```js
function update() {
  game.physics.arcade.collide(ball, paddle);
  game.physics.arcade.collide(ball, bricks, ballHitBrick);
  paddle.x = game.input.x || game.world.width \* 0.5;
}
```


Теперь будет отслеживаться положение мячика, относительно всех кирпичей из набора `bricks`. Третьим (опциальным) параметром, мы передаём функцию, которая будет выполняться каждый раз, когда будет найдена коллизия — `ballHitBrick`. Давайте создадим эту функцию в самом конце нашего кода, прямо перед `</script>`:


```js
function ballHitBrick(ball, brick) {
  brick.kill();
}
```


Вот и всё! Перезагрузите страницу и вы увидите, что все коллизии обрабатывается, как следует.

Спасибо Phaser за то, что передал нам в функцию эти два параметра — мячик и тот кирпич, с которым у мячика произошла коллизия. А дальше мы просто удаляем кирпич с экрана, вызвав у него функцию `kill()`.

Вы думали, что нам придётся писать много кода для отслеживания коллизий, как мы это делали на чистом JavaScript (en-US)? В этом и прелесть фреймворков — рутину они сделают за нас, а мы, в это время, можем сосредоточиться на действительно интересных вещах.

Сравните свой код
-----------------

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

Следующий шаг
-------------

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

* « Предыдущая статья
* Следующая статья »
Победа
======

* « Предыдущая статья
* Следующая статья »

Это **12** из 16 уроков руководства разработки игры с помощью Phaser. Исходный код этого урока вы можете найти здесь: Gamedev-Phaser-Content-Kit/demos/lesson12.html.

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

Как победить?
-------------

Добавьте следующий код в функцию `ballHitBrick()`:


```js
function ballHitBrick(ball, brick) {
  brick.kill();
  score += 10;
  scoreText.setText("Points: " + score);

  var count_alive = 0;
  for (i = 0; i < bricks.children.length; i++) {
    if (bricks.children[i].alive == true) {
      count_alive++;
    }
  }
  if (count_alive == 0) {
    alert("You won the game, congratulations!");
    location.reload();
  }
}
```


Чтобы перебрать все кирпичи в наборе, необходимо обратиться к полю `bricks.children`. Найдём все неразрешённые кирпичи, проверяя поле `alive` у каждого кирпича и, если все кирпичи разрушены, выведем всплывающее окно с текстом о победе. После закрытия этого окна, страница перезагрузится.

Сравните свой код
-----------------

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

Следующий шаг
-------------

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

* « Предыдущая статья
* Следующая статья »
Загрузка ресурсов и их вывод
============================

* « Предыдущая статья
* Следующая статья »

Это 3-й урок из 16 в руководстве 2D игра на Phaser. Весь исходный код из этого урока вы можете найти на Gamedev-Phaser-Content-Kit/demos/lesson03.html.

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

Создание мяча
-------------

Давайте начнём с создания JavaScript переменной для нашего мячика. Добавьте следующий код между строкой инициализации (`var game...`) и функцией `preload()`:


```js
var ball;
```


> 
> \*\*Примечание:\*\*В этом уроке мы для удобства будем использовать глобальные переменные, потому что цель урока - научить пользоваться Phaser для создания игр, а не хорошим практикам разработки.
> 
> 
> 

Загрузка спрайта мяча
---------------------

Загрузка и вывод изображений в canvas намного проще с использованием Phaser, чем чистым JavaScript. Чтобы загрузить спрайт, мы будем использовать метод `load.image()` объекта `game`. Добавьте новую строку кода в функцию `preload()`:


```js
function preload() {
  // ...
  game.load.image("ball", "img/ball.png");
}
```


Первым параметром, которому мы хотим присвоить имя картинки, является имя, которое будет использоваться в нашем игровом коде - например, в нашем имени переменной `ball` - поэтому мы должны убедиться, что оно совпадает. Второй параметр - это относительный путь к самой картинке, мы загружаем спрайт для нашего мяча. (Заметьте, что имя файла (asset) не обязано совпадать с именем переменной, которой оно присваивается, однако так проще).

Конечно, чтобы загрузить картинку, она должна находиться в нашей рабочей директории. Скачайте изображение с GitHub и сохраните его в `/img` директории, которая находится там же, где и файл `index.html`.

Теперь, чтобы вывести наш мяч на экран, мы используем другой метод `add.sprite()`. Добавьте следующую строку кода в функцию `create()`:


```js
function create() {
  ball = game.add.sprite(50, 50, "ball");
}
```


Это метод добавит мяч в игру и выведет его на экран. Первые два параметра — это координаты `x` и `y` элемента canvas соответственно, и последний параметр — это имя картинки, которое мы добавили раннее. Вот и всё: загрузите `index.html`, и вы увидите, что картинка уже загружена и выведена в canvas!

**Предупреждение:** Если вы храните ваш JavaScript код отдельно от HTML и используете файл для этого, например, `game.js`, тогда вам нужно расположить директорию `img` и JavaScript файл в одной папке, иначе картинка не загрузится.

Сравните свой код
-----------------

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

Следующее
---------

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

* « Предыдущая статья
* Следующая статья »
Очки
====

* « Предыдущая статья
* Следующая статья »

Это **11** из 16 уроков руководства разработки игры с помощью Phaser. Исходный код этого урока вы можете найти здесь: Gamedev-Phaser-Content-Kit/demos/lesson11.html.

Очки сделают игру более интересной — вы можете пробовать побить собственный рекорд или рекорд друзей. В этой главе мы добавим систему очков в нашу игру.

Мы будем использовать отдельные переменные для хранения количества очков и текста, который Phaser выведет на экран при помощи функции `text()`.

Определим переменные
--------------------

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


```js
// ...
var scoreText;
var score = 0;
```

Выводим очки на экран
---------------------

А сейчас добавим строку кода в самый конец функции `create()`:


```js
scoreText = game.add.text(5, 5, "Points: 0", {
  font: "18px Arial",
  fill: "#0095DD",
});
```


Функция `text()` может принимать четыре параметра:

* Координаты x и y текста.
* Сам текст.
* Шрифт и стиль текста.

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

Обновляем очки при разрушении кирпича
-------------------------------------

Мы будем увеличивать количество очков каждый раз, когда мячик разобьёт кирпич и обновлять текст `scoreText`, который отображает на экране текущие очки. Текст обновляется вызовом функции `setText()`. Добавьте эти две строчки кода в функцию `ballHitBrick()`:


```js
function ballHitBrick(ball, brick) {
  brick.kill();
  score += 10;
  scoreText.setText("Points: " + score);
}
```


Вот и всё — обновите страницу `index.html` и проверьте, как очки изменяются, при разрушении кирпича.

Сравните свой код
-----------------

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

Следующий шаг
-------------

Теперь мы имеем систему очков, но какой смысл в этом, если мы не можем выиграть? Давайте добавим логику выигрыша.

* « Предыдущая статья
* Следующая статья »
Движение мяча
=============

* « Предыдущая статья
* Следующая статья »

Это **4-й урок** из 16, которые входят в руководство 2D игра на Phaser. Вы можете найти исходный код этого урока на Gamedev-Phaser-Content-Kit/demos/lesson04.html.

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

Обновление позиции мяча на каждом кадре
---------------------------------------

Помните функцию `update()` и её назначение? Код внутри этой функции выполняется на каждом кадре, так что это идеальное место для кода, который будет обновлять положение нашего мяча. Добавьте следующие строки внутри `update()`:


```js
function update() {
  ball.x += 1;
  ball.y += 1;
}
```


Код выше добавляет `1` к свойствам `x` и `y`, которые отвечают за координаты мяча в canvas на каждом кадре. Перезагрузив `index.html`, вы должны увидеть, как наш мячик движется.

Сравните свой код
-----------------

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

Следующее
---------

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

В любом случае перед тем, как сделать это, мы рассмотрим физический движок Phaser.

* « Предыдущая статья
* Следующая статья »
Отскакивания
============

* « Предыдущая статья
* Следующая статья »

Это **6 урок** из 16 Руководства разработки игры с помощью Phaser. Вы можете найти исходный код того как должен выглядеть код после завершения урока здесь: Gamedev-Phaser-Content-Kit/demos/lesson06.html.

Теперь, когда мы познакомились с физикой, мы можем начать реализовывать определение столкновений в игре — сначала посмотрим на "стены".

Отскакивание от границ представления
------------------------------------

Самый простой способ заставить мячик от стен это сообщить фреймворку что мы хотим рассматривать границы элемента `<canvas>` как стены и не позволять мячу проходить через них. В Phaser это может быть просто реализовано с помощью свойства `collideWorldsBound`. Добавьте этот код сразу после существующего вызова метода `game.physics.enable()`:


```js
ball.body.collideWorldBounds = true;
```


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


```js
ball.body.bounce.set(1);
```


Попробуйте перезагрузить index.html опять — теперь мяч отскакивает от стен и движется внутри холста canvas.

Сравните ваш код
----------------

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

Следующие шаги
--------------

Это начинает больше походить на игру, но мы никак не можем её контролировать — самое время добавить рычаги управления для игрока.

* « Предыдущая статья
* Следующая статья »
Game over
=========

* « Предыдущая статья
* Следующая статья »

Это - 5-й шаг из 10 из Gamedev Canvas tutorial (en-US). Вы можете найти исходный код к этому уроку в Gamedev-Canvas-workshop/lesson5.html.

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

Реализация окончания игры
-------------------------

Давайте попытаемся реализовать "Конец Игры" в нашей игре. Вот часть кода от третьего урока, где мы заставили шар отталкиваться от стен:


```js
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
  dx = -dx;
}

if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
  dy = -dy;
}
```


Вместо того, чтобы позволить шару отталкиваться от всех четырёх стен, давайте позволим отталкиваться только от трёх стен — левую, верхнюю и правую. Удар об нижнюю стенку закончит игру. Мы отредактируем второй блок if, таким образом, что if else вызовет "Конец Игры" , когда шар столкнётся с нижней стенкой холста. Сохраните документ, взгляните на выводящееся сообщение перезагрузите игру путём перезагрузки страницы.

Во-первых, измените код, где вы изначально вызывали `setInterval()`
```

setInterval(draw, 10);
```


на следующий:
```

var interval = setInterval(draw, 10);
```


Затем замените второй оператор if следующим:


```js
if (y + dy < ballRadius) {
  dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
  alert("GAME OVER");
  document.location.reload();
  clearInterval(interval); // Needed for Chrome to end game
}
```

Отбить шар
----------

Последняя вещь, которую нужно сделать на этом уроке состоит в том, чтобы создать некоторое обнаружение столкновений между шаром и битой, таким образом, шар оттолкнётся от биты и возвратиться в область игры. Самая простая вещь состоит в том, чтобы проверить, является ли центр шара между левыми и правыми краями биты. Обновите последний бит кода, который вы изменили:


```js
if (y + dy < ballRadius) {
  dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
  if (x > paddleX && x < paddleX + paddleWidth) {
    dy = -dy;
  } else {
    alert("GAME OVER");
    document.location.reload();
  }
}
```


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

Сравните свой код
-----------------

Вот рабочий код для вас, чтобы сравнить ваш с:

**Примечание:** заставьте мяч двигаться быстрее, когда он попадает в биту.

Следующие шаги
--------------

Все идёт хорошо и наша игра начинает чувствовать себя намного более достойно. Вы уже можете проиграть! Но все ещё не хватает чего-то. Перейдём к шестой главе — Build the brick field (en-US) — и создадим некоторые кирпичи для шара, чтобы их уничтожить.

* « Предыдущая статья (en-US)
* Следующая статья » (en-US)
Управление мышью
================

* « Предыдущая статья
* Следующая статья »

Это 9-й шаг из 10 в Gamedev Canvas tutorial (en-US). Вы можете найти исходный код к этому уроку в Gamedev-Canvas-workshop/lesson9.html.

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

Отслеживание движений мыши
--------------------------

Отслеживание движений мыши ещё проще, чем обработка нажатий клавиш. Все, что нам нужно, это следить за событиями `mousemove (en-US)`. Добавьте следующую строку в том же месте, как и для других событий, чуть ниже `keyup event`:


```js
document.addEventListener("mousemove", mouseMoveHandler, false);
```

Привязка движения ракетки к движению мыши
-----------------------------------------

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


```js
function mouseMoveHandler(e) {
  var relativeX = e.clientX - canvas.offsetLeft;
  if (relativeX > 0 && relativeX < canvas.width) {
    paddleX = relativeX - paddleWidth / 2;
  }
}
```


В этой функции мы сначала разрабатываем значение `relativeX`, которое равно горизонтальному положению мыши в окне браузера (`e.clientX`) минус расстояние между левым краем canvas и левым краем окна браузера (`canvas.offsetLeft`) - фактически это равно расстоянию между левым краем canvas и указателем мыши. Если относительный указатель позиции X больше нуля и меньше, чем ширина Canvas, указатель находится в пределах границы Canvas, и `paddleX` установки (крепится на левый край ракетки) - устанавливается на `relativeX` значение минус половина ширины ракетки, так что движение будет по отношению к середине ракетки.

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

Сравните свой код
-----------------

Это последнее состояние кода для сравнения:

Упражнение: отрегулируйте границы движения ракетки так, что вся ракетка будет видна на обоих краях Canvas, а не только на его половину.

Следующий шаг
-------------

Теперь у нас есть полная игра, мы закончим нашу серию уроков с ещё несколькими небольшими хитростями — Finishing up (en-US).

* « Предыдущая статья (en-US)
* Следующая статья » (en-US)