Жизни - Разработка игр

Жизни
=====

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

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

Мы можем растянуть удовольствие от игры, добавив жизни. Это позволит игроку сделать несколько попыток, а не одну.

Новый переменные
----------------

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

```js
var lives = 3;
var livesText;
var lifeLostText;
```

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

Определяем новые надписи
------------------------

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

```js
livesText = game.add.text(game.world.width - 5, 5, "Lives: " + lives, {
  font: "18px Arial",
  fill: "#0095DD",
});
livesText.anchor.set(1, 0);
lifeLostText = game.add.text(
  game.world.width \* 0.5,
  game.world.height \* 0.5,
  "Life lost, click to continue",
  { font: "18px Arial", fill: "#0095DD" },
);
lifeLostText.anchor.set(0.5);
lifeLostText.visible = false;
```

Объекты `livesText` и `lifeLostText` очень похожи на `scoreText` — они определяют положение на экране, текст надписи и стилизацию шрифта. Чтобы всё выглядело должным образом, надпись с жизнями мы закрепляем в правом верхнем углу, а надпись о потере жизни, мы выводим в центре экрана. И всё это при помощи функции `anchor.set()`.

Надпись `lifeLostText` появится только при потере жизни, поэтому её видимость мы выставляем в `false`.

### Чистим код, стилизирующий надписи

Как вы могли заметить, мы используем одинаковые стили для всех надписей: `scoreText`, `livesText` и `lifeLostText`. Однако, налицо копирование кода и если мы, когда-либо, захотим изменить размер шрифта или цвет, то нам придётся делать это в нескольких местах. Чтобы избежать этого, мы вынесем стиль в отдельную переменную. Напишите следующую строку сразу после всех наших текущих определений переменных:

```js
var textStyle = { font: "18px Arial", fill: "#0095DD" };
```

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

```js
scoreText = game.add.text(5, 5, "Points: 0", textStyle);
livesText = game.add.text(
  game.world.width - 5,
  5,
  "Lives: " + lives,
  textStyle,
);
livesText.anchor.set(1, 0);
lifeLostText = game.add.text(
  game.world.width \* 0.5,
  game.world.height \* 0.5,
  "Life lost, click to continue",
  textStyle,
);
lifeLostText.anchor.set(0.5);
lifeLostText.visible = false;
```

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

Код обработки жизни
-------------------

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

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

Мы объявим новую функцию `ballLeaveScreen`; Удалим предыдущий обработчик (зачёркнутый код сверху) и заменим его следующей линией:

```js
ball.events.onOutOfBounds.add(ballLeaveScreen, this);
```

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

```js
function ballLeaveScreen() {
  lives--;
  if (lives) {
    livesText.setText("Lives: " + lives);
    lifeLostText.visible = true;
    ball.reset(game.world.width \* 0.5, game.world.height - 25);
    paddle.reset(game.world.width \* 0.5, game.world.height - 5);
    game.input.onDown.addOnce(function () {
      lifeLostText.visible = false;
      ball.body.velocity.set(150, -150);
    }, this);
  } else {
    alert("You lost, game over!");
    location.reload();
  }
}
```

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

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

События
-------

Скорее всего вы заметили вызов методов `add()` и `addOnce()` в двух блоках кода выше и хотите знать, чем они отличаются. Разница в том, что метод `add()` и привязанная к нему функция выполняется каждый раз, когда выполняется событие, тогда как метод `addOnce()` полезен, когда вы хотите, чтобы связанная с ним функция выполнилась единожды и не повторялась снова. В нашем случае при каждом событии `outOfBounds` будет выполняться `ballLeaveScreen`, но когда мяч покидает экран, сообщение с экрана удалится единожды.

Проверь свой код
----------------

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

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

Жизни делают игру более казуальной — даже если вы проиграете единожды, у вас будут ещё 2 жизни и вы сможете продолжить игру. Теперь мы можем поработать над внешним видом игры, сделать её более красивой, добавив анимацию и эффекты (en-US).

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


Движение мяча - Разработка игр

Движение мяча
=============

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

Это **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.

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


Платформа и управление - Разработка игр

Платформа и управление
======================

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

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

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

Рисуем платформу
----------------

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

### Создаём платформу

Сначала, добавим переменную `paddle`, сразу после переменной `ball`:

```js
var paddle;
```

После этого, в функции `preload`, загрузим изображение `paddle` при помощи ещё одного вызова функции `load.image()`:

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

### Добавляем графику для платформы

Чуть не забыли, на этом этапе нам надо скачать изображение платформы с Github в папку `/img`.

### Рисуем платформу с физикой

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

```js
paddle = game.add.sprite(
  game.world.width \* 0.5,
  game.world.height - 5,
  "paddle",
);
```

Мы можем использовать `world.width` и `world.height` для позиционирования платформы в том месте, где мы хотим: `game.world.width*0.5` расположит платформу прямо по середине экрана. В данном случае, нам повезло, что наш игровой мир совпадает с `<canvas>`, однако, в других играх мир может быть гораздо больше экрана.

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

```js
paddle.anchor.set(0.5, 1);
```

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

```js
game.physics.enable(paddle, Phaser.Physics.ARCADE);
```

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

```js
function update() {
  game.physics.arcade.collide(ball, paddle);
}
```

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

```js
paddle.body.immovable = true;
```

Вот! Теперь всё работает, как надо.

Управляем платформой
--------------------

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

```js
function update() {
  game.physics.arcade.collide(ball, paddle);
  paddle.x = game.input.x;
}
```

Теперь, каждый кадр координата `x` платформы будет соответствовать координате `x` курсора. Однако, при старте игры, положение нашей платформы не по центру экрана, из-за того, что положение курсора не определено. Чтобы это исправить, давайте добавим платформе координату `x` по умолчанию, на случай, если положение курсора не определено. Обновите предыдущую строку кода:

```js
paddle.x = game.input.x || game.world.width \* 0.5;
```

Если вы этого ещё не сделали, то обновите страницу `index.html` и попробуйте то, что у нас получилось!

Расположение мячика
-------------------

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

```js
ball = game.add.sprite(game.world.width \* 0.5, game.world.height - 25, "ball");
ball.anchor.set(0.5);
```

Скорость оставим такую же, но изменим направление по оси `y`, изменив второй параметр со 150 на -150, и теперь мяч будет двигаться вверх. Найдите строку `ball.body.velocity.set( ... )` и измените её, как показано ниже:

```js
ball.body.velocity.set(150, -150);
```

Теперь мячик появляется по середине платформы и двигается вверх.

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

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

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

Мы можем управлять платформой и сделали так, чтобы мячик отскакивал от неё. Но какой от этого толк, если мячик отскакивает и от нижней грани экрана? В следующей главе мы добавим логику проигрыша и экран "Game over".

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


Отскакивания - Разработка игр

Отскакивания
============

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

Это **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 - Разработка игр

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)


Отскок от стен - Разработка игр

Отскок от стен
==============

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

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

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

Простое обнаружение столкновений
--------------------------------

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

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

```js
var ballRadius = 10;
```

Теперь обновите строку, которая рисует шарик, внутри функции `drawBall()`:

```js
ctx.arc(x, y, ballRadius, 0, Math.PI \* 2);
```

### Отскакивание от верхней и нижней стены

Есть четыре стены, от которых мяч будет отскакивать — давайте сначала сосредоточимся на верхней. При каждом кадре нужно проверять, коснулся ли мяч верхней границы — если да, то будет обратное движение мяча, поэтому он начнёт двигаться в противоположном направлении и остановится в пределах видимой границы. Вспомнив, что система координат начинается с левого верхнего угла, мы можем придумать что-то вроде этого:

```js
if (y + dy < 0) {
  dy = -dy;
}
```

Если значение `y` положения шара ниже нуля, изменить направление движения по оси y установив его с тем же значением но с другим знаком. Если мяч движется вверх со скоростью 2 пикселя на кадр, теперь он будет двигаться "вверх" со скоростью -2 пикселя, что на самом деле означает движение вниз со скоростью 2 пикселя.

Приведённый выше код описывает отражение только от верхней границы, так что теперь давайте думать о нижнем крае:

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

Если положение мяча по оси `y` больше, чем высота полотна (помните, что мы рассчитываем значения `y` от верхнего левого, чтобы верхний край начинался с 0, а нижний край — 480 пикселей, высота нашего `<canvas>`), затем после отскока от нижней кромки обратное движение по оси `y`.

Мы можем объединить эти две конструкции в одну, чтобы уменьшить код:

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

Если одно из двух утверждений верно, тогда направление мяча меняется.

### Отскоки влево и вправо

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

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

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

На этом этапе вы должны вставить этот блок кода в функцию Draw (), непосредственно перед закрывающей фигурной скобкой.

### Мяч продолжает исчезать в стене!

Проверьте сейчас свой код, и вы будете впечатлёны — теперь мяч, отскакивает от всех четырёх краёв нашего `<canvas>`! Однако есть некоторая проблема - когда мяч попадает в любую стену, он немного заходит за границы `<canvas>` перед отскоком:

![](/ru/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls/ball-in-wall.png)

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

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

Когда расстояние между центром шара и краем стены равно радиусу шарика, шарик изменит направление движения. Вычитая радиус при отскоке от одной стены и добавляя при отскоке от другой, мы получили простое обнаружение столкновений. Шарик отскакивает от стен как надо.

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

Давайте ещё раз проверим готовый код для этой части, и код, что у вас есть, и играйте:

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

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

Теперь мы добрались до стадии, где наш мяч одновременно двигается и остаётся на игровом поле. В четвёртой главе мы рассмотрим реализацию управления — см. Paddle and keyboard controls (en-US).

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


Обнаружение столкновений - Разработка игр

Обнаружение столкновений
========================

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

Это 7 шаг из 10 в Gamedev Canvas tutorial (en-US). Вы можете найти исходный код, как он будет выглядеть после завершения этого урока, тут Gamedev-Canvas-workshop/lesson7.html.

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

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

Функция обнаружения столкновения
--------------------------------

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

```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      // calculations
    }
  }
}
```

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

* Координата X шара больше, чем координата X кирпича.
* Координата X шара меньше, чем X-координата кирпича плюс его ширина.
* Координата Y шара больше, чем Y-координата кирпича.
* Координата Y шара меньше, чем Y-координата кирпича плюс его высота.

Давайте напишем это в коде:

```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
        dy = -dy;
      }
    }
  }
}
```

Добавьте вышеприведённый блок к вашему коду под `keyUpHandler()` функцией .

Удаление кирпичей после их попадания
------------------------------------

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

```js
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
  bricks[c] = [];
  for (var r = 0; r < brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0, status: 1 };
  }
}
```

Теперь мы будем проверять значение свойства `status` каждого кирпича в функции `drawBricks()` перед его рисованием - если `status` равен `1` , нарисуем его, а если равен `0` , то значит в него попал мяч и он не должен больше отрисовываться. Отредактируйте `drawBricks()` следующим образом:

```js
function drawBricks() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      if (bricks[c][r].status == 1) {
        var brickX = c \* (brickWidth + brickPadding) + brickOffsetLeft;
        var brickY = r \* (brickHeight + brickPadding) + brickOffsetTop;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}
```

Отслеживание и обновление состояния в функции обнаружения столкновений
----------------------------------------------------------------------

Теперь нам нужно задействовать свойство `status` кирпича в функции `collisionDetection()` : если кирпич активен (его статус равен `1` ), мы проверяем, было ли столкновение; если да, мы устанавливаем статус данного кирпича равным `0`, чтобы он не был нарисован на экране. Отредактируйте функцию `collisionDetection()`, как показано ниже:

```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
        }
      }
    }
  }
}
```

Активация нашего обнаружения столкновений
-----------------------------------------

Последнее, что нужно сделать, это добавить вызов функции `collisionDetection()` в нашу основную функцию `draw()` . Добавьте следующую строку в функцию `draw()` , чуть ниже `drawPaddle()` :

```js
collisionDetection();
```

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

Обнаружение столкновения шара теперь выполняется на каждом кадре и для каждого кирпича. Теперь мы можем ломать кирпичи! : -

**Примечание:** измените цвет шара, когда он ударит по кирпичу.

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

Мы уверенно движемся вперёд! Поехали! В восьмой главе мы будем учиться отслеживать счёт и выигрывать (en-US).

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


Заключение - Разработка игр

Заключение
==========

* « Предыдущая статья

Это 10-й и заключительный шаг в Gamedev Canvas tutorial (en-US). Вы можете найти исходный код, как он должен выглядеть, после завершения этого урока в Gamedev-Canvas-workshop/lesson10.html.

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

Предоставление игроку нескольких жизней
---------------------------------------

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

```js
var lives = 3;
```

Отрисовка счётчика жизни выглядит почти так же, как и счётчика баллов - добавьте в код следующую функцию под функцией `drawScore()` :

```js
function drawLives() {
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}
```

Вместо того, чтобы немедленно закончить игру, мы уменьшим количество жизней, пока они больше не будут доступны. Мы также можем сбросить позиции мяча и биты, когда игрок начинает игру со следующей жизнью. Итак, в функции `draw()` замените следующие три строки:

```js
alert("GAME OVER");
document.location.reload();
clearInterval(interval); // Needed for Chrome to end game
```

Давайте добавим немного более сложную логику к ней, как показано ниже:

```js
lives--;
if (!lives) {
  alert("GAME OVER");
  document.location.reload();
} else {
  x = canvas.width / 2;
  y = canvas.height - 30;
  dx = 2;
  dy = -2;
  paddleX = (canvas.width - paddleWidth) / 2;
}
```

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

### Визуализация дисплея жизней

Теперь вам нужно добавить вызов `drawLives()` внутри функции `draw()` и добавить его под вызовом `drawScore()`.

```js
drawLives();
```

Улучшение рендеринга с requestAnimationFrame()
----------------------------------------------

Теперь давайте работать над чем-то, что не связано с игровой механикой, но с тем, как она рендерится. `requestAnimationFrame` поможет браузеру рендерить игру лучше, чем фиксированная частота кадров, которую в настоящее время мы реализовали, используя `setInterval()` (en-US). Замените следующую строку:

```js
setInterval(draw, 10);
```

на:

```js
draw();
```

и удалите каждый экземпляр:
```

clearInterval(interval); // Needed for Chrome to end game
```

Затем в самом низу функции `draw()` (непосредственно перед закрывающей фигурной скобкой) добавьте следующую строку, которая заставляет функцию `draw()` вызывать себя снова и снова:

```js
requestAnimationFrame(draw);
```

Функция `draw()` теперь выполняется снова и снова в цикле `requestAnimationFrame()`, но вместо фиксированной частоты кадров в 10 миллисекунд, мы возвращаем управление частотой кадров обратно в браузер. Соответственно он будет синхронизировать частоту кадров и отображать фигуры только при необходимости. Это обеспечивает более эффективный и плавный цикл анимации, чем более старый метод `setInterval().`

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

Вот и все-финальная версия игры готова!

**Примечание:** измените количество жизней и угол отскока мяча от биты.

Игра закончена - на данный момент!
----------------------------------

Вы закончили все уроки — поздравляем! К этому моменту вы должны знать основы манипулирования canvas и логику простых 2D-игр. Сейчас самое время изучить некоторые фреймворки и продолжить разработку игр. Вы можете проверить аналог этой серии, 2D breakout game using Phaser (en-US) или Cyber Orb built in Phaser (en-US) учебник. Вы также можете просмотреть раздел Games section on MDN для вдохновения и увеличения знаний.

Вы также можете вернуться на this tutorial series' index page (en-US) учебника. Получайте удовольствие от написания кода!

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


Создание Canvas и рисование на нём - Разработка игр

Создание Canvas и рисование на нём
==================================

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

Это **1й шаг** из 10 Gamedev Canvas tutorial (en-US). Вы можете найти исходный код для этого урока по ссылке Gamedev-Canvas-workshop/lesson1.html.

Прежде чем мы сможем начать писать функциональные возможности игры, нам необходимо создать базовую структуру для рендеринга игры внутри. Это можно сделать с помощью HTML и элемента `<canvas>`.

HTML игры
---------

Структура HTML документа довольно проста, так как игра будет полностью визуализироваться в `<canvas>` элементе. Используя ваш любимый текстовый редактор, создайте новый HTML документ, сохраните его как `index.html` в любом удобном месте, и скопируйте в него этот код:

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Gamedev Canvas Workshop</title>
    <style>
 \* {
 padding: 0;
 margin: 0;
 }
 canvas {
 background: #eee;
 display: block;
 margin: 0 auto;
 }
 </style>
  </head>
  <body>
    <canvas id="myCanvas" width="480" height="320"></canvas>

    <script>
 // JavaScript-код будем писать здесь
 </script>
  </body>
</html>
```

Мы определили `charset`, `<title>` и некий базовый CSS в заголовке. Тело документа содержит элементы `<canvas>` и `<script>` — мы будем визуализировать игру внутри первого и писать JavaScript-код, который управляет игрой, во втором. Элемент `<canvas>` имеет `id` равный `myCanvas`, который позволяет однозначно отыскать элемент. Так же этот элемент имеет ширину 480 пикселей и высоту 320 пикселей. Весь JavaScript-код мы будем писать между открывающим тегом `<script>` и закрывающим тегом `</script>`.

Основы Canvas
-------------

Чтобы иметь возможность визуализировать игру в `<canvas>` элементе, сначала мы должны сослаться на этот элемент в коде JavaScript. Добавьте следующий код после открывающего тега `<script>`.

```js
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
```

Таким образом, мы храним ссылку на `<canvas>` элемент в переменной `canvas`. Далее мы создаём переменную `ctx` для хранения 2D визуализации контекста — метод, который используется для отрисовки в Canvas.

Давайте рассмотрим пример отрисовки красного квадрата на canvas. Добавьте этот код ниже предыдущего кода на JavaScript и загрузите `index.html` в браузере, чтобы посмотреть результат.

```js
ctx.beginPath();
ctx.rect(20, 40, 50, 50);
ctx.fillStyle = "#FF0000";
ctx.fill();
ctx.closePath();
```

Все инструкции располагаются между методами `beginPath()` и `closePath()`. Мы определяем прямоугольник, используя `rect()`: первые два параметра определяют координаты верхнего левого угла прямоугольника на canvas, в то время, как два других параметра определяют ширину и высоту прямоугольника. В нашем случае прямоугольник нарисован на расстоянии 20 пикселей слева и 40 пикселей сверху, 50 пикселей в ширину и 50 пикселей в высоту, что делает его квадратом. Свойство `fillStyle` хранит цвет, который будет использован методом `fill()` для отрисовки нашего квадрата.

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

```js
ctx.beginPath();
ctx.arc(240, 160, 20, 0, Math.PI \* 2, false);
ctx.fillStyle = "green";
ctx.fill();
ctx.closePath();
```

Как видите, мы снова используем методы `beginPath()` и `closePath()`. Между ними наиболее важная часть кода - метод `arc()`. Он принимает шесть параметров:

* `x` и `y` координаты центра круга
* радиус
* начальный и конечный угол отрисовки круга (в радианах)
* направление отрисовки: (`false` - по часовой стрелке, по умолчанию, или `true` - против часовой стрелки). Последний параметр не обязательный.

Свойство `fillStyle` выглядит не так, как прежде. Это потому что, как и в CSS, цвет может быть задан в шестнадцатеричном формате, названием цвета, функцией `rgba()` , или же любым другим методом для цвета.

Вместо `fill()` и заполнения фигур цветом, можно использовать `stroke()`, чтобы окрасить только внешнюю обводку фигуры. Попробуйте добавить этот код к вашему коду JavaScript:

```js
ctx.beginPath();
ctx.rect(160, 10, 100, 40);
ctx.strokeStyle = "rgba(0, 0, 255, 0.5)";
ctx.stroke();
ctx.closePath();
```

Код выше отрисовывает пустой прямоугольник с синей обводкой. Благодаря функции альфа-канала `rgba()` , голубой цвет полупрозрачный.

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

Здесь полный исходный код для первого урока, посмотреть онлайн можно на JSFiddle:

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

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

Сейчас мы создали базовый HTML и немного узнали о canvas, давайте на следующем шаге изучим, как Двигать мяч в нашей игре (en-US).

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


Переместить мяч - Разработка игр

Переместить мяч
===============

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

Это **2й шаг** из 10 Gamedev Canvas tutorial (en-US). Вы можете найти исходный код для данного урока по ссылке Gamedev-Canvas-workshop/lesson2.html.

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

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

Чтобы canvas постоянно обновлялся, необходимо определить функцию отрисовки, которая будет циклически запускаться с разными параметрами, чтобы изменять позицию элемента. Функцию можно циклически запускать с помощью JavaScript временной функции, такой как `setInterval()` (en-US) или `requestAnimationFrame()`.

Удалите весь JavaScript-код, который сейчас хранился в вашем HTML файле, за исключением первых двух строк, и добавьте следующий код после них. Функция `draw()` будет исполняться внутри функции `setInterval` каждые 10 мс:

```js
function draw() {
  // код отрисовки
}
setInterval(draw, 10);
```

Благодаря бесконечности функции `setInterval` функция `draw()` будет выполняться каждые 10 мс бесконечно или же пока мы не остановим её. Теперь давайте нарисуем мяч - добавьте в свой код функцию `draw()`:

```js
ctx.beginPath();
ctx.arc(50, 50, 10, 0, Math.PI \* 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
```

Попробуйте сейчас ваш обновлённый код - каждый раз мяч будет перерисовываться.

Перемещение
-----------

Вы не можете определить, перерисовывается ли мяч в конкретный момент, так как его позиция не меняется. Давайте исправим это. Прежде всего, в позиции (50,50) определим стартовую точку в нижней центральной части Canvas в переменной `x` и `y`, и потом используем их для определения текущего положения мяча.

Для начала добавьте следующие две строки до функции `draw()`, чтобы определить переменные `x` и `y`:

```js
var x = canvas.width / 2;
var y = canvas.height - 30;
```

Далее обновите функцию `draw()`, чтобы использовать переменные x и y в методе `arc()`, как показано на подсвеченной линии:

```js
function draw() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}
```

Теперь важная часть: мы хотим добавлять небольшое значение к переменным `x` и `y` после каждой итерации, чтобы заставить мяч двигаться. Давайте определим эти небольшие значения в переменных `dx` и `dy` и установим их 2 и -2 соответственно. Добавьте следующий код после определения переменных x и y:

```js
var dx = 2;
var dy = -2;
```

И последнее, необходимо обновить переменные `x` и `y` на значение `dx` и `dy` каждую итерацию, чтобы мяч отрисовывался каждый раз на новой позиции. Добавьте ещё две строки кода в функции `draw()`:

```js
function draw() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
  x += dx;
  y += dy;
}
```

Сохраните ваш код и откройте страницу в браузере. Всё работает хорошо, вот только мяч оставляет позади след:

![](/ru/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball/ball-trail.png)

Очищение объекта после каждого кадра
------------------------------------

Мяч оставляет след, потому что мы рисуем круг на каждой итерации без удаления предыдущей отрисованной фигуры. Не переживайте, потому что существует метод для очистки canvas элемента: `clearRect()`. Этот метод принимает четыре параметра: x и y координаты верхнего левого угла прямоугольника, x и y координаты нижнего правого угла прямоугольника. Вся площадь, покрытая прямоугольником, будет очищена от любого содержимого, которое когда-либо было там отрисовано.

Добавьте следующую подсвеченную строку в функцию `draw()`:

```js
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
  x += dx;
  y += dy;
}
```

Сохраните ваш код и попробуйте снова, и на этот раз вы увидите, что мяч перемещается без следов. Каждые 10 мс canvas очищается, голубой круг (наш мяч) отрисовывается на заданной позиции, переменные `x` и `y` каждую итерацию обновляются.

Рефакторинг
-----------

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

Замените существующую функцию draw() следующими двумя функциями:

```js
function drawBall() {
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI \* 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBall();
  x += dx;
  y += dy;
}
```

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

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

Упражнение: попробуйте изменить скорость перемещения мяча, или направление перемещения.

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

Мы нарисовали мяч и заставили его двигаться, но он продолжает пропадать на краю canvas. В третьей части мы рассмотрим, как заставить его отскакивать от стен (en-US).

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


Ракетка и управление клавиатурой - Разработка игр

Ракетка и управление клавиатурой
================================

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

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

Мяч беспрепятственно отражается от стен, и вы можете смотреть на него бесконечно, но в настоящее время нет интерактивности. Это не игра, если вы не можете управлять мячом. Давайте добавим взаимодействие с игрой: управление ракеткой.

Определение ракетки, чтобы ударить по мячу
------------------------------------------

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

```js
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width - paddleWidth) / 2;
```

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

```js
function drawPaddle() {
  ctx.beginPath();
  ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();
}
```

Позволяем пользователю управлять ракеткой
-----------------------------------------

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

* Две переменные для хранения информации о том, левая или правая кнопка управления нажата.
* Два обработчика для событий `keydown` и `keyup` — мы хотим запустить некоторый код для обработки движения ракетки при нажатии кнопок.
* Две функции обработки события `keydown` и `keyup` код, который будет выполняться при нажатии кнопок.
* Возможность перемещения ракетки влево и вправо

Нажатые кнопки могут быть определены и инициализированы булевыми переменными. Добавьте эти строки рядом с остальными вашими переменными:

```js
var rightPressed = false;
var leftPressed = false;
```

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

```js
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
```

Когда `keydown` событие вызывается любой клавишей клавиатуры (нажатием клавиши), функция `keyDownHandler()` будет выполняться. Та же картина верна для второго обработчика: `keyup` событие запустит функцию `keyUpHandler ()` (когда будет отпущена). Добавьте следующий блок в ваш код ниже `addEventListener()`:

```js
function keyDownHandler(e) {
  if (e.keyCode == 39) {
    rightPressed = true;
  } else if (e.keyCode == 37) {
    leftPressed = true;
  }
}

function keyUpHandler(e) {
  if (e.keyCode == 39) {
    rightPressed = false;
  } else if (e.keyCode == 37) {
    leftPressed = false;
  }
}
```

Когда мы нажимаем клавишу, эта информация хранится в переменной. Соответствующая переменная в каждом конкретном случае устанавливается в `true`. Когда клавиша отпущена, переменная устанавливается обратно в `false`.

Обе функции принимают параметр, представленный переменной `e`. Из неё вы можете получить полезную информацию: `key` содержит информацию о нажатой клавише. Например, код 37 — это клавиша стрелка влево и 39 — стрелка вправо. Если стрелка влево нажата, то переменная `leftPressed` имеет значение `true`, когда кнопка отпущена, то переменная leftPressed имеет значение false. Та же схема со стрелкой вправо и переменной rightPressed.

### Логика перемещения ракетки

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

```js
if (rightPressed) {
  paddleX += 7;
} else if (leftPressed) {
  paddleX -= 7;
}
```

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

```js
if (rightPressed && paddleX < canvas.width - paddleWidth) {
  paddleX += 7;
} else if (leftPressed && paddleX > 0) {
  paddleX -= 7;
}
```

Позиция `paddleX` будет двигаться от 0 на левой стороне холста и `canvas.width-paddleWidth` на правой стороне. Это будет работать именно так, как нам нужно.

Добавьте вышеприведённый блок кода в функцию `draw()` в самый конец, чуть выше закрывающей фигурной скобки.

Единственное, что осталось сделать сейчас, это вызвать `drawPaddle()` функцию внутри функции `draw()`, чтобы нарисовать ракетку на экране. Добавьте следующую строку внутрь функции `draw()`, чуть ниже строки, которая вызывает `drawBall()`:

```js
drawPaddle();
```

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

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

**Примечание:** Сделайте скорость движения ракетки быстрее или медленнее, или измените её размер.

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

Теперь у нас есть что-то похожее на игру. Беда только в том, что пока вы можете лишь бесконечно бить мяч ракеткой. Это все изменится в пятой главе, Game over (en-US), когда мы начнём добавлять конечное состояние для нашей игры.

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


Создаём зону кирпичей - Разработка игр

Создаём зону кирпичей
=====================

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

Это 6 шаг из 10 в Gamedev Canvas tutorial (en-US). Вы можете найти исходный код, как он будет выглядеть после завершения этого урока, тут - Gamedev-Canvas-workshop/lesson6.html.

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

Настройка переменных кирпича
----------------------------

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

```js
var brickRowCount = 3;
var brickColumnCount = 5;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
```

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

Мы будем держать все наши кирпичи в двумерном массиве. Он будет содержать кирпичные столбцы (c), которые, в свою очередь, будут содержать кирпичные ряды (r), которые, в свою очередь, будут содержать объект, содержащий положение `x` и `y` чтобы рисовать каждый кирпич на экране. Добавьте следующие значения ниже ваших переменных:

```js
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
  bricks[c] = [];
  for (var r = 0; r < brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0 };
  }
}
```

Вышеприведённый код будет прокручивать строки и столбцы и создавать новые кирпичи. Обратите внимание, что кирпичные объекты также будут использоваться для целей обнаружения столкновений позже.

Механизм отрисовки кирпичей
---------------------------

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

```js
function drawBricks() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      bricks[c][r].x = 0;
      bricks[c][r].y = 0;
      ctx.beginPath();
      ctx.rect(0, 0, brickWidth, brickHeight);
      ctx.fillStyle = "#0095DD";
      ctx.fill();
      ctx.closePath();
    }
  }
}
```

Опять же, мы зацикливаем строки и столбцы, чтобы установить положение `x` и `y` каждого кирпича, и мы также `brickWidth` кирпич на кирпичной `brickWidth` Canvas - `brickWidth` - с каждой итерацией цикла. Проблема в том, что мы рисуем их все в одном месте, в координатах `(0,0)` . Нам нужно включить некоторые вычисления, которые будут определять положение `x` и `y` каждого кирпича для каждой итерации цикла:

```js
var brickX = c \* (brickWidth + brickPadding) + brickOffsetLeft;
var brickY = r \* (brickHeight + brickPadding) + brickOffsetTop;
```

Каждая позиция `brickX` разрабатывается как `brickWidth` + `brickPadding` , умноженная на номер столбца, `c` , плюс `brickOffsetLeft` ; логика для brickY идентична, за исключением того, что она использует значения для номера строки, `r`, `brickHeight` и `brickOffsetTop` . Теперь каждый отдельный кирпич может быть помещён в правильное место и столбец места, с отступом между каждым кирпичом, нарисованным на смещение от левого и верхнего краёв холста.

Окончательная версия функции `drawBricks()` после назначения `brickX` и `brickY` в качестве координат вместо `(0,0)` каждый раз будет выглядеть следующим образом: добавьте это в свой код ниже функции `drawPaddle()` :

```js
    for(var c=0; c<brickColumnCount; c++) {
        for(var r=0; r<brickRowCount; r++) {
            var brickX = (c\*(brickWidth+brickPadding))+brickOffsetLeft;
            var brickY = (r\*(brickHeight+brickPadding))+brickOffsetTop;
            bricks[c][r].x = brickX;
            bricks[c][r].y = brickY;
            ctx.beginPath();
            ctx.rect(brickX, brickY, brickWidth, brickHeight);
            ctx.fillStyle = "#0095DD";
            ctx.fill();
            ctx.closePath();
        }
    }
}
```

Сама отрисовка кирпичей
-----------------------

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

```js
drawBricks();
```

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

На этом этапе игра снова стала интереснее:

**Примечание:** попробуйте изменить количество кирпичей в строке или столбце или их позиции.

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

Итак, теперь у нас есть кирпичи! Но мяч вообще не взаимодействует с ними - мы изменим это, поскольку мы переходим в седьмую главу: Collision detection (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)


Счёт и выигрыш - Разработка игр

Счёт и выигрыш
==============

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

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

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

Подсчёт очков
-------------

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

```js
var score = 0;
```

Вам также нужна функция `drawScore()` для создания и обновления отображения оценки. Добавьте после функции `collisionDetection()` следующий код:

```js
function drawScore() {
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Score: " + score, 8, 20);
}
```

Рисование текста на канве аналогично рисованию фигур. Определение шрифта выглядит точно так же, как и в CSS - вы можете установить размер и тип шрифта в `font()` методе. Затем используйте `fillStyle()` для установки цвета шрифта и `fillText()` для установки фактического текста, который будет помещён на канву, и где он будет размещён. Первым параметром является сам текст - приведённый выше код показывает текущее количество точек, а два последних параметра - это координаты, в которых текст будет помещён на канву.

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

```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
          score++;
        }
      }
    }
  }
}
```

Вызов `drawScore()` из функции `draw()` позволяет обновить счёт с каждым новым фреймом - добавьте следующую строку внутри `draw()`, чуть ниже вызова `drawPaddle()`:

```js
drawScore();
```

Отображение сообщения о победе, когда все кирпичи были уничтожены
-----------------------------------------------------------------

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

```js
function collisionDetection() {
  for (var c = 0; c < brickColumnCount; c++) {
    for (var r = 0; r < brickRowCount; r++) {
      var b = bricks[c][r];
      if (b.status == 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
        ) {
          dy = -dy;
          b.status = 0;
          score++;
          if (score == brickRowCount \* brickColumnCount) {
            alert("YOU WIN, CONGRATULATIONS!");
            document.location.reload();
          }
        }
      }
    }
  }
}
```

Благодаря этому, игроки могут выиграть игру, когда они уничтожают все кирпичи, что очень важно, когда дело доходит до игр. Функция `document.location.reload()` перезагружает страницу и снова запускает игру после нажатия кнопки оповещения.

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

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

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

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

На данный момент игра выглядит довольно хорошо. В следующем уроке вы расширите привлекательность игры, добавив Mouse controls (en-US).

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


Асинхронные скрипты для asm.js - Разработка игр

Асинхронные скрипты для asm.js
==============================

Каждая средняя или большая игра должна компилировать код asm.js как часть асинхронного скрипта, чтобы дать браузеру максимальную гибкость для оптимизации процесса компиляции. В Gecko асинхронная компиляция позволяет движку JavaScript компилировать asm.js из основного потока когда игра загружается, и кешировать сгенерированный машинный код, так что игру не нужно компилировать при последующих загрузках (начиная с Firefox 28). Чтобы увидеть разницу, переключите `javascript.options.parallel_parsing` в `about:config`.

Написание асинхронного скрипта
------------------------------

Получить асинхронную компиляцию легко: при написании JavaScript просто используйте `async` атрибут следующим образом:
```

<script async src="file.js"></script>
```

или, чтобы сделать то же самое через скрипт:
```

var script = document.createElement('script');
script.src = "file.js";
document.body.appendChild(script);
```

(скрипты создаются из скрипта по умолчанию `async`.) Стандартная оболочка HTML, которую генерирует Emscripten, создаёт последнее.

Когда асинхронный не асинхронный?
---------------------------------

Две распространённые ситуации, в которых сценарий не асинхронен (как определено спецификацией HTML ):

```html
<script async src="file.js"></script>
```

а также

```js
const script = document.createElement("script");
script.src = "file.js";
document.body.appendChild(script);
```

Оба считаются «встроенными» скриптами, компилируются и запускаются сразу

Что если ваш код находится в строке JS? Вместо использования `eval` или `innerHTML`, оба из которых запускают синхронную компиляцию, вы должны использовать BLOB-объект с URL объекта:
```

var blob = new Blob([codeString]);
var script = document.createElement('script');
var url = URL.createObjectURL(blob);
script.onload = script.onerror = function() { URL.revokeObjectURL(url); };
script.src = url;
document.body.appendChild(script);
```

Настройка `src` вместо настройки `innerHTML` делает этот скрипт асинхронным.


Реализация элементов управления с помощью API Gamepad - Разработка игр

Реализация элементов управления с помощью API Gamepad
=====================================================

В этой статье рассматривается реализация эффективной кросс-браузерной системы управления веб-играми с использованием API Gamepad, позволяющей управлять веб-играми с помощью консольных игровых контроллеров. В нем есть тематическое исследование игры "Голодный холодильник", созданный компанией Enclave Games.

Элементы управления для web игр
-------------------------------

Исторически игра на консоли, подключённой к телевизору, всегда была совершенно другим опытом, чем игра на ПК, в основном из-за уникальных элементов управления. В конце концов, дополнительные драйверы и плагины позволили нам использовать консольные геймпады с настольными играми - либо нативными играми, либо теми, которые работают в браузере. Теперь, в эпоху HTML5, у нас наконец есть API Gamepad, который даёт нам возможность играть в браузерные игры с использованием геймпад-контроллеры без каких-либо плагинов. API Gamepad достигает этого, предоставляя интерфейс, демонстрирующий нажатия кнопок и изменения оси, которые могут быть использованы внутри кода JavaScript для обработки входных данных. Это хорошие времена для браузерных игр.

![gamepad-controls](http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img01.png)

API статус и поддержка браузера
-------------------------------

Gamepad API все ещё находится на стадии рабочего проекта в процессе W3C, что означает, что его реализация все ещё может измениться, но говорит о том, что поддержка браузера уже довольно хороша. Firefox 29+ и Chrome 35+ поддерживают его сразу после установки. Opera поддерживает API в версии 22+ (неудивительно, учитывая, что теперь они используют движок Blink Chrome.) И Microsoft недавно реализовала поддержку API в Edge,что означает, что четыре основных браузера теперь поддерживают API Gamepad.

Какие геймпады лучше всего?
---------------------------

![gamepad devices](http://end3r.com/tmp/gamepad/devices.jpg)

Наиболее популярные геймпады в наше время - геймпады от Xbox 360, Xbox One, PS3 и PS4 — они прошли все испытания и хорошо работают с Gamepad API, реализованном в браузерах на Windows и Mac OS X.

Существует также ряд других устройств с различными макетами кнопок, которые более или менее работают в разных реализациях браузера. Код, описанный в этой статье, был протестирован с помощью нескольких геймпадов, но любимая конфигурация автора-это беспроводной контроллер Xbox 360 и браузер Firefox на Mac OS X.

Пример исследования: Голодный холодильник
-----------------------------------------

The competition ran in November 2013 and decided to take part in it. The theme for the competition was "change", so they submitted a game where you have to feed the Hungry Fridge by tapping the healthy food (apples, carrots, lettuces) and avoid the "bad" food (beer, burgers, pizza.) A countdown changes the type of food the Fridge wants to eat every few seconds, so you have to be careful and act quickly. You can try Hungry Fridge here.

Конкурс GitHub Game Off II состоялся в ноябре 2013 года, и Enclave Games решила принять в нем участие. Тема для конкурса была "переменна", поэтому они представили игру, в которой вы должны накормить голодный холодильник, нажав на здоровую пищу (яблоки, морковь, салат) и избежать "плохой" пищи (пиво, гамбургеры, пицца.) Обратный отсчёт меняет тип пищи, которую холодильник хочет съесть каждые несколько секунд, поэтому вы должны быть осторожны и действовать быстро. Вы можете попробовать Голодный холодильник здесь.

![hungryfridge-mainmenu](http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img02.png)

Вторая, скрытая реализация "изменения" - это возможность превратить статичный холодильник в полноценную движущуюся, стреляющую и едящую машину. Когда вы подключаете контроллер, игра значительно меняется (голодный холодильник превращается в супер турбо голодный холодильник), и вы можете управлять бронированным холодильником с помощью API Gamepad. Вы должны сбивать еду, но вы все ещё должны искать тип пищи, которую холодильник хочет съесть в каждой точке, иначе вы потеряете энергию.

![hungryfridge-howtoplay-gamepad](http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img03.png)

Игра инкапсулирует два совершенно разных типа "изменений" - хорошая еда против плохой еды и мобильная против настольной.

![hungryfridge-gameplay-gamepad](http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img04.png)

Демо-версия
-----------

Сначала была построена полная версия игры Hungry Fridge, а затем, чтобы для демонстрации API Gamepad в действии и показа исходного кода JavaScript, была создана простая демо-версия. Это часть набора контента Gamepad API, доступного на GitHub, где вы можете глубоко погрузиться в код и изучить, как именно он работает.

![Hungry Fridge demo using Gamepad API](http://end3r.com/tmp/gamepad/super-turbo.png)

Код, описанный ниже, взят из полной версии игры Hungry Fridge, но он почти идентичен демо-версии — единственная разница заключается в том, что полная версия использует переменную turbo, чтобы решить, будет ли игра запущена с использованием режима Super Turbo. Он работает независимо, поэтому его можно включить, даже если геймпад не подключён.

**Примечание:** время пасхальных яиц: есть скрытая опция для запуска Super Turbo Hungry Fridge на рабочем столе без подключения геймпада — нажмите на значок геймпада в правом верхнем углу экрана. Он запустит игру в режиме Super Turbo, и вы сможете управлять холодильником с помощью клавиатуры: A и D для поворота башни влево и вправо, W для стрельбы и клавиш со стрелками для перемещения.

Реализация
----------

Есть два важных события, которые можно использовать вместе с API Gamepad - `gamepadconnected` и `gamepaddisconnected`. Первый срабатывает, когда браузер обнаруживает подключение нового геймпада, а второй - когда геймпад отключён (либо физически пользователем, либо из-за бездействия).) В демо-версии объект `gamepadAPI` используется для хранения всего, что связано с API:

```js
var gamepadAPI = {
  controller: {},
  turbo: false,
  connect: function () {},
  disconnect: function () {},
  update: function () {},
  buttonPressed: function () {},
  buttons: [],
  buttonsCache: [],
  buttonsStatus: [],
  axesStatus: [],
};
```

Массив `кнопок` содержит клавиатуру Xbox 360 :

```js
buttons: [
  'DPad-Up','DPad-Down','DPad-Left','DPad-Right',
  'Start','Back','Axis-Left','Axis-Right',
  'LB','RB','Power','A','B','X','Y',
],
```

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

```js
window.addEventListener("gamepadconnected", gamepadAPI.connect);
window.addEventListener("gamepaddisconnected", gamepadAPI.disconnect);
```

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

Обе функции довольно просты:

```js
connect: function(evt) {
  gamepadAPI.controller = evt.gamepad;
  gamepadAPI.turbo = true;
  console.log('Gamepad connected.');
},
```

The `connect()` function takes the event as a parameter and assigns the `gamepad` object to the `gamepadAPI.controller` variable. We are using only one gamepad for this game, so it's a single object instead of an array of gamepads. We then set the `turbo` property to `true`. (We could use the `gamepad.connected` boolean for this purpose, but we wanted to have a separate variable for turning on Turbo mode without needing to have a gamepad connected, for reasons explained above.)

Функция `connect()` принимает событие в качестве параметра и назначает объект `gamepad` объекту `gamepadAPI.controller`. Мы используем только один геймпад для этой игры, так что это один объект вместо массива геймпадов. Затем мы устанавливаем свойство turbo в true. (Мы могли бы использовать `gamepad.connected`, т.к для этой цели подключается логическое значение, но мы хотели иметь отдельную переменную для включения турбо-режима без необходимости подключения геймпада, по причинам, описанным выше.)

```js
disconnect: function(evt) {
  gamepadAPI.turbo = false;
  delete gamepadAPI.controller;
  console.log('Gamepad disconnected.');
},
```

Функция `disconnect` устанавливает `gamepad.turbo property` на `false` и удаляет переменную, содержащую объект gamepad.

### Объект геймпада

В объекте `gamepad` содержится много полезной информации, причём наиболее важными являются состояния кнопок и осей:

* `id`: Строка, содержащая информацию о контроллере.
* `index`: Уникальный идентификатор для подключённого устройства.
* `connected`: Логическая переменная. Возвращает `true` при подключении.
* `mapping`: Тип компоновки кнопок; Стандартный - единственный доступный вариант на данный момент.
* `axes`: Состояние каждой оси, представленное массивом значений с плавающей запятой.
* `buttons` : Состояние каждой кнопки, представленное массивом объектов GamepadButton, содержащих `pressed` и `value` свойства.

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

### Запрос объекта геймпада

Помимо `connect()` и `disconnect()`, в объекте `gamepadAPI` есть ещё два метода: `update()` и `buttonPressed()`. `update()` выполняется на каждом кадре внутри игрового цикла, чтобы регулярно обновлять фактическое состояние объекта геймпада:

```js
update: function() {
  // clear the buttons cache
  gamepadAPI.buttonsCache = [];
  // move the buttons status from the previous frame to the cache
  for(var k=0; k<gamepadAPI.buttonsStatus.length; k++) {
    gamepadAPI.buttonsCache[k] = gamepadAPI.buttonsStatus[k];
  }
  // clear the buttons status
  gamepadAPI.buttonsStatus = [];
  // get the gamepad object
  var c = gamepadAPI.controller || {};

  // loop through buttons and push the pressed ones to the array
  var pressed = [];
  if(c.buttons) {
    for(var b=0,t=c.buttons.length; b<t; b++) {
      if(c.buttons[b].pressed) {
        pressed.push(gamepadAPI.buttons[b]);
      }
    }
  }
  // loop through axes and push their values to the array
  var axes = [];
  if(c.axes) {
    for(var a=0,x=c.axes.length; a<x; a++) {
      axes.push(c.axes[a].toFixed(2));
    }
  }
  // assign received values
  gamepadAPI.axesStatus = axes;
  gamepadAPI.buttonsStatus = pressed;
  // return buttons for debugging purposes
  return pressed;
},
```

На каждом кадре, `update()` сохраняет кнопки, нажатые во время предыдущего кадра, в массиве `buttonsCache` и берёт новые из объекта `gamepadAPI.controller`. Затем он проходит по кнопкам и осям, чтобы получить их фактические состояния и значения.

### Обнаружение нажатия кнопок

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

```js
buttonPressed: function(button, hold) {
  var newPress = false;
  // loop through pressed buttons
  for(var i=0,s=gamepadAPI.buttonsStatus.length; i<s; i++) {
    // if we found the button we're looking for...
    if(gamepadAPI.buttonsStatus[i] == button) {
      // set the boolean variable to true
      newPress = true;
      // if we want to check the single press
      if(!hold) {
        // loop through the cached states from the previous frame
        for(var j=0,p=gamepadAPI.buttonsCache.length; j<p; j++) {
          // if the button was already pressed, ignore new press
          if(gamepadAPI.buttonsCache[j] == button) {
            newPress = false;
          }
        }
      }
    }
  }
  return newPress;
},
```

Существует два типа действий, которые следует учитывать для кнопки: одно нажатие и удержание. Логическая переменная `newPress` будет указывать, есть ли новое нажатие кнопки или нет. Затем мы проходим через массив нажатых кнопок — если данная кнопка совпадает с той, которую мы ищем, переменная `newPress` устанавливается в `true`. Чтобы проверить, является ли нажатие новым, так как игрок не держит клавишу, мы перебираем кешированные состояния кнопок из предыдущего кадра игрового цикла. Если мы находим его там, это означает, что кнопка удерживается, поэтому нового нажатия нет. В конце концов возвращается переменная `newPress`. Функция `buttonPressed` используется в цикле обновления игры следующим образом:

```js
if (gamepadAPI.turbo) {
  if (gamepadAPI.buttonPressed("A", "hold")) {
    this.turbo\_fire();
  }
  if (gamepadAPI.buttonPressed("B")) {
    this.managePause();
  }
}
```

Если `gamepadAPI.turbo` возвращает `true`, при нажатии (или удержании) данных кнопок мы выполняем соответствующие функции, возложенные на них. В этом случае нажатие или удержание "A" приведёт к выстрелу пули, а нажатие "B" поставит игру на паузу.

### Порог оси

Кнопки имеют только два состояния: 0 или 1, но аналоговые стики могут иметь много различных значений — они имеют плавающий диапазон между -1 и 1 вдоль обеих осей X и Y.

![axis threshold](http://end3r.com/tmp/gamepad/axis-threshold.png)

Геймпады могут запылиться от лежания в бездействии, что означает, что проверка точных значений -1 или 1 может быть проблемой. По этой причине может быть полезно установить пороговое значение для того, чтобы значение оси вступило в силу. Например, бак холодильника поворачивается вправо только тогда, когда значение X больше 0,5:
```

if(gamepadAPI.axesStatus[0].x > 0.5) {
  this.player.angle += 3;
  this.turret.angle += 3;
}
```

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

Обновление спецификаций
-----------------------

После более чем годичной стабильности, в апреле 2015 года была обновлена спецификация API W3C Gamepad (см. последнюю версию). Он не сильно изменился, но хорошо знать, что происходит — обновления заключаются в следующем...

### Получение геймпадов

Метод `Navigator.getGamepads ()` был обновлён с более длинным объяснением и примером кода. Теперь длина массива геймпадов должна быть `n+1`, где `n`-количество подключённых устройств — когда подключено одно устройство и оно имеет индекс 1, длина массива равна 2, и он будет выглядеть следующим образом: `[null, [object Gamepad]]`. Если устройство отключено или недоступно, то для него устанавливается значение `null`.

### Стандартное отображение

Тип отображения теперь является перечисляемым объектом вместо строки:
```

enum GamepadMappingType {
    "",
    "standard"
};
```

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

### События

В спецификации было доступно больше событий, чем просто `gamepadconnected` и `gamepaddisconnected`, но они были удалены из спецификации, поскольку считались не очень полезными. До сих пор продолжается дискуссия о том, следует ли их возвращать и в какой форме.

Подведение итогов
-----------------

API геймпада очень прост в разработке. Теперь это проще, чем когда-либо, вы можете использовать браузер как консоль без необходимости каких-либо плагинов. Вы можете играть в полную версию игры Hungry Fridge непосредственно в вашем браузере, установить её из Firefox Marketplace или проверить исходный код демо-версии вместе со всеми другими ресурсами в комплекте контента Gamepad API.


Создание простой сцены с помощью Three.js - Разработка игр

Создание простой сцены с помощью Three.js
=========================================

В 3D сцене любой игры, даже самой простой, есть стандартные сущности, такие как поверхности, расположенные в координатной системе, камера, для того, чтобы их видеть, света и материалы, для того, чтобы они лучше выглядели, анимации, чтобы их оживить и тд. **Three.js**, как и любая другая 3D библиотека, предоставляет встроенные функции, которые помогут вам в реализации 3D. В этой статье мы покажем вам основы использования Three, включая настройку среды, структуру необходимого HTML, фундаментальные объекты Three и как создать простейшую сцену.

**Примечание:** Мы выбрали Three, потому что это одна из самых популярных WebGL библиотек и она достаточно проста в освоении. Это не значит, что она самая лучшая, вы можете выбрать любую другую, например CopperLicht, GLGE, O3D и тд.

Настройка среды
---------------

Чтобы начать разработку с Three.js, нужно:

* Удостовериться, что вы используете современную версию браузера с поддержкой WebGL, например, Firefox или Chrome.
* Создать папку для экспериментов.
* Сохранить копию последней версии библиотеки Three.js в вашей папке.
* Открыть документацию Three.js в отдельной вкладке.
HTML структура
--------------

Здесь находится HTML структура, которую мы будем использовать:

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>MDN Games: Three.js demo</title>
    <style>
 body {
 margin: 0;
 padding: 0;
 }
 canvas {
 width: 100%;
 height: 100%;
 }
 </style>
  </head>
  <body>
    <script src="three.min.js"></script>
    <script>
 var WIDTH = window.innerWidth;
 var HEIGHT = window.innerHeight;
 /\* весь наш JavaScript-код будет после этого комментария \*/
 </script>
  </body>
</html>
```

Она содержит основную информацию: `<title>`, CSS, чтобы сделать `ширину` и `высоту` `<canvas>` элемента равным странице. В первом элементе `<script>` мы подключаем Three.js, во втором будем писать наш код, в котором уже создали 2 переменные для хранения `width` и `height`.

Прежде чем продолжить, скопируйте этот код в новый файл и сохраните в вашей рабочей директории под именем `index.html`.

Renderer
--------

Renderer это инструмент для отрисовки сцены в браузере. Есть 2 вида таких инструментов: WebGL по умолчанию, другие - Canvas, SVG, CSS, и DOM. Они различаются по тому как все рендерится. Несмотря на различия в них, для пользователя все будет выглядеть одинаково. Поэтому, вы можете выбрать запасной вариант на случай, если браузер пользователя не поддерживает выбранную вами технологию.

```js
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0xdddddd, 1);
document.body.appendChild(renderer.domElement);
```

Создаём новый WebGL renderer, устанавливаем размер так, чтобы он занимал все пространство страницы, прикрепляем DOM-элемент к странице. Вы могли заметить параметр `antialias` в первой строке — если он установлен в `true`, то границы объектов сглаживаются. Метод `setClearColor()` устанавливает цвет фона (мы установили в 0xDDDDDD, светло-серый, значение по умолчанию - чёрный).

Добавьте этот код в ваш элемент `<script>`.

Сцена
-----

Сцена (scene) это место, где все происходит. Когда создаются новые объекты, они добавляются в сцену, чтобы их можно было увидеть. В three.js роль сцены выполняет объект `Scene`. Давайте создадим его, добавив следующих код:

```js
var scene = new THREE.Scene();
```

Позже, мы будем использовать метод `.add()` для добавления объектов в сцену.

Камера
------

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

```js
var camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT);
camera.position.z = 50;
scene.add(camera);
```

Добавьте следующий код в программу.

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

* Мы установили поле зрения в 70, с этим можно поэкспериментировать: чем больше число, тем больше угол обзора, тем шире мы видим. Представьте обычную камеру и камеру с эффектом fish eye, Которая позволяет увидеть больше. Значение по умолчанию 50.
* Соотношение сторон установлено в соотношение сторон окна браузера. Можно установить, например, в 16 ⁄ 9. Значение по умолчанию 1.
* Координата `z`, равная 50, это дистанция от камеры до начала координат сцены вдоль оси `z`. Мы установили такое значение, чтобы увидеть объекты в сцене. Значения `x` и `y` по умолчанию равны 0.

Стоит поэкспериментировать с этими значениями, чтобы лучше разобраться в том, как они влияют на отрисовку сцены.

**Примечание:** значения позиции (например координата z) безразмерны, устанавливайте их так, чтобы вам было удобно с ними работать.

Отрисовка сцены
---------------

Всё уже готово, но мы пока что ничего не видим. Хотя мы настроили рендерер, нам всё равно нужно запустить рендеринг. Функция `render()` выполнит эту работу с небольшой помощью `requestAnimationFrame()`, которая заставляет сцену постоянно перерисовываться в каждом кадре:

```js
function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
render();
```

На каждом новом кадре вызывается функция `render()`, а `renderer` рендерит `scene` и `camera`. Сразу после объявления функции мы в первый раз вызываем её, чтобы запустить цикл, после чего он будет использоваться бесконечно.

Ещё раз, добавьте этот новый код ниже ваших предыдущих дополнений. Попробуйте сохранить файл и открыть его в браузере. Теперь вы должны увидеть серое окно. Поздравляем!

Geometry
--------

Now our scene is properly rendering, we can start adding 3D shapes. To speed up development, Three.js provides a bunch of predefined primitives, which you can use to create shapes instantly in a single line of code. There's cubes, spheres, cylinders, and more complicated shapes available. Detail like drawing required vertices and faces, for a given shape, is handled by the Three framework, so we can focus on higher level coding. Let's start, by defining the geometry for a cube shape, adding the following just above the `render()` function:

```js
var boxGeometry = new THREE.BoxGeometry(10, 10, 10);
```

In this case, we define a simple cube that is 10 x 10 x 10 units. The geometry itself is not enough though, we also need a material that will be used for our shape.

Material
--------

A material is what covers an object, the colors, or textures on its surface. In our case, we will choose a simple blue color to paint our box. There are a number of predefined materials which can be used: Basic, Phong, Lambert. Let's play with the last two later, but for now, the Basic one should be enough:

```js
var basicMaterial = new THREE.MeshBasicMaterial({ color: 0x0095dd });
```

Add this line below the previously added.

Our material is now ready, what next?

Mesh
----

To apply the material to a geometry, a mesh is used. This takes on a shape, and adds the specified material to every face:

```js
var cube = new THREE.Mesh(boxGeometry, basicMaterial);
```

Again, add this line below the one you previously added.

Добавление куба в сцену
-----------------------

Сейчас мы создали куб, используя 'geometry' и 'material'. Последнее, что мы должны сделать - добавить куб на сцену. Добавьте в код эту строку:

```js
scene.add(cube);
```

Если вы сохраните код и обновите вкладку браузера, вы увидите квадрат, а не куб, потому, что он стоит ровно напротив камеры и мы видим только одну сторону. У объектов есть полезное свойство - мы можем изменять их как хотим. Например, вы можете вращать его и масштабировать, сколько угодно. Что давайте немного повернём его, чтобы видеть больше сторон. Добавить в конец кода эту строку:

```js
cube.rotation.set(0.4, 0.2, 0);
```

Поздравляю, вы создали объект в 3D-среде! This might have proven easier than you first thought? Here's how it should look:

![Blue cube on a gray background rendered with Three.js.](/ru/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js/cube.png)

Весь код который мы создали:

Вы также можете просмотреть на GitHub.

More shapes and materials
-------------------------

Now we will add more shapes to the scene, and explore other shapes, materials, lighting, and more. Let's move the cube to the left, to make space for some friends. Adding the following line just below the previous one:

```js
cube.position.x = -25;
```

Now onto more shapes and materials. What might you input to add a torus, wrapped in the Phong material? Try adding the following lines, just below the lines defining the cube.

```js
var torusGeometry = new THREE.TorusGeometry(7, 1, 6, 12);
var phongMaterial = new THREE.MeshPhongMaterial({ color: 0xff9500 });
var torus = new THREE.Mesh(torusGeometry, phongMaterial);
scene.add(torus);
```

These lines will add a torus geometry; the `TorusGeometry()` method's parameters define, and the parameters are `radius`, `tube diameter`, `radial segment count`, and `tubular segment count`. The Phong material should look more glossy than the box's simple Basic material, though right now our torus will just look black.

We can choose more fun predefined shapes. Let's play some more. Add the following lines, below those defining the torus:

```js
var dodecahedronGeometry = new THREE.DodecahedronGeometry(7);
var lambertMaterial = new THREE.MeshLambertMaterial({ color: 0xeaeff2 });
var dodecahedron = new THREE.Mesh(dodecahedronGeometry, lambertMaterial);
dodecahedron.position.x = 25;
scene.add(dodecahedron);
```

This time, we are creating a dodecahedron, a shape containing twelve flat faces. The parameter, `DodecahedronGeometry(),` defines the size of the object. We're using a Lambert material, similar to Phong, but should be less glossy. Again it's black, for now. We're moving the object to the right, so it's not in the same position as the box, or torus.

As mentioned above, the new objects currently just look black. To have both, the Phong and Lambert materials properly visible, we need to introduce a source of light.

Lights
------

There are various types of light sources available in Three.js. The most basic is `PointLight`, which works like a flashlight, shining a spotlight in a defined direction. Add the following lines, below your shape definitions:

```js
var light = new THREE.PointLight(0xffffff);
light.position.set(-10, 15, 50);
scene.add(light);
```

We define a white point of light, set its position a little away from the center of the scene, so it can light up some parts of the shapes, finally adding it to the scene. Now everything works as it should, all three shapes are visible. You should check the documentation for other types of lights, like Ambient, Directional, Hemisphere, or Spot. Experiment placing them on our scene, to see how they affect it.

![Shapes: blue cube, dark yellow torus and dark gray dodecahedron on a gray background rendered with Three.js.](/ru/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js/shapes.png)

This looks a little boring though. In a game, something is usually happening. We might see animations and such. So let's try breathing a little life into these shapes, by animating them!

Анимация
--------

Мы можем использовать 'rotation', чтобы поменять положение куба. Также мы можем масштабировать фигуры или изменять их положение. Чтобы создать анимацию нужно внесённые изменения внести в цикл рендеринга, чтобы изменения происходили в каждом кадре.

### Вращение

Вращать фигуры просто. Вы просто добавляете или отнимаете значение по оси вращения. Добавьте эту строку кода сразу после: `requestAnimationFrame()` invocation inside the `render` function:

```js
cube.rotation.y += 0.01;
```

This rotates the cube on every frame, by a tiny bit, so the animation looks smooth.

### Scaling

We can also scale an object. Applying a constant value, we would make it grow, or shrink just once. Let's make things more interesting. First, we implement a helper variable, called `t,` for counting elapsed time. Add it right before the `render()` function:

```js
var t = 0;
```

Now let's increase the value by a given constant value, on each frame of the animation. Add the following lines, just below the `requestAnimationFrame()` invocation:

```js
t += 0.01;
torus.scale.y = Math.abs(Math.sin(t));
```

We use `Math.sin`, ending up with quite an interesting result. This scales the torus, repeating the process, as `sin` is a periodic function. We're wrapping the scale value in `Math.abs`, to pass the absolute values, greater or equal to 0. As sin is between -1 and 1, negative values might render out torus in unexpected way. In this case it looks black half the time.

Now, onto movement.

### Moving

Aside from rotation, and scaling, we can additionally move objects around the scene. Add the following, again just below our `requestAnimationFrame()` invocation:

```js
dodecahedron.position.y = -7 \* Math.sin(t \* 2);
```

This will move the dodecahedron up and down, by applying the `sin()` value to the y axis on each frame, and a little adjustment to make it look cooler. Try changing these values, to see how it affects the animations.

Conclusion
----------

Here's the final code:

You can also see it on GitHub and fork the repository, if you want to play with it locally. Now you understand the basics of Three.js, you can jump back to the parent page, 3D on the Web (en-US).

You could also try learning raw WebGL, to gain a better understanding of what's going on underneath. See our WebGL documentation.