aboutsummaryrefslogtreecommitdiff
path: root/files/ru/games/techniques/controls_gamepad_api/index.html
blob: 512f3143d481c1d500123ec2cd44ff389361bf09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
---
title: Реализация элементов управления с помощью API Gamepad
slug: Games/Techniques/Controls_Gamepad_API
tags:
  - Gamepad API
  - JavaScript
  - Игры
  - геймпады
  - контроллеры
translation_of: Games/Techniques/Controls_Gamepad_API
---
<div>{{GamesSidebar}}</div>

<div></div>

<p class="summary">В этой статье рассматривается реализация эффективной кросс-браузерной системы управления веб-играми с использованием API Gamepad, позволяющей управлять веб-играми с помощью консольных игровых контроллеров. В нем есть тематическое исследование игры "Голодный холодильник", созданный компанией <a href="http://enclavegames.com/">Enclave Games</a>.</p>

<h2 id="Элементы_управления_для_web_игр">Элементы управления для web игр</h2>

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

<p><img alt="gamepad-controls" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img01.png" style="display: block; height: 400px; margin: 0px auto; width: 600px;"></p>

<h2 id="API_статус_и_поддержка_браузера">API статус и поддержка браузера</h2>

<p><a href="http://www.w3.org/TR/gamepad/">Gamepad API</a> все еще находится на стадии рабочего проекта в процессе W3C, что означает, что его реализация все еще может измениться, но говорит о том, что поддержка браузера уже довольно хороша. Firefox 29+ и Chrome 35+ поддерживают его сразу после установки. Opera поддерживает API в версии 22+ (неудивительно, учитывая, что теперь они используют движок Blink Chrome.) И Microsoft недавно реализовала поддержку API в Edge,что означает, что четыре основных браузера теперь поддерживают API Gamepad.</p>

<h2 id="Какие_геймпады_лучше_всего">Какие геймпады лучше всего?</h2>

<p><img alt="gamepad devices" src="http://end3r.com/tmp/gamepad/devices.jpg" style="display: block; height: 450px; margin: 0px auto; width: 600px;"></p>

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

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

<h2 id="Пример_исследования_Голодный_холодильник">Пример исследования: Голодный холодильник</h2>

<p>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 <a href="http://enclavegames.com/games/hungry-fridge/">try Hungry Fridge here</a>.</p>

<p>Конкурс <a href="https://github.com/blog/1674-github-game-off-ii">GitHub Game Off II</a> состоялся в ноябре 2013 года, и <a href="http://enclavegames.com/">Enclave Games</a> решила принять в нем участие. Тема для конкурса была "переменна", поэтому они представили игру, в которой Вы должны накормить голодный холодильник, нажав на здоровую пищу (яблоки, морковь, салат) и избежать "плохой" пищи (пиво, гамбургеры, пицца.) Обратный отсчет меняет тип пищи, которую холодильник хочет съесть каждые несколько секунд, поэтому вы должны быть осторожны и действовать быстро. Вы можете попробовать Голодный холодильник <a href="http://enclavegames.com/games/hungry-fridge/">здесь</a>.</p>

<p><img alt="hungryfridge-mainmenu" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img02.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p>

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

<p><img alt="hungryfridge-howtoplay-gamepad" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img03.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p>

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

<p><img alt="hungryfridge-gameplay-gamepad" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img04.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p>

<h2 id="Демо-версия">Демо-версия</h2>

<p>Сначала была построена полная версия игры Hungry Fridge, а затем, чтобы для демонстрации API Gamepad в действии и показа исходного кода JavaScript, была создана <a href="https://end3r.github.io/Gamepad-API-Content-Kit/demo/demo.html">простая демо-версия</a>. Это часть <a href="https://end3r.github.io/Gamepad-API-Content-Kit/">набора контента Gamepad API</a>, доступного на GitHub, где вы можете глубоко погрузиться в код и изучить, как именно он работает.</p>

<p><img alt="Hungry Fridge demo using Gamepad API" src="http://end3r.com/tmp/gamepad/super-turbo.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p>

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

<div class="note">
<p><strong>ПРИМЕЧАНИЕ: </strong>время пасхальных яиц: есть скрытая опция для запуска Super Turbo Hungry Fridge на рабочем столе без подключения геймпада — нажмите на значок геймпада в правом верхнем углу экрана. Он запустит игру в режиме Super Turbo, и Вы сможете управлять холодильником с помощью клавиатуры: A и D для поворота башни влево и вправо, W для стрельбы и клавиш со стрелками для перемещения.</p>
</div>

<h2 id="Реализация">Реализация</h2>

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

<pre class="brush: js"><code>var gamepadAPI = {
  controller: {},
  turbo: false,
  connect: function() {},
  disconnect: function() {},
  update: function() {},
  buttonPressed: function() {},
  buttons: [],
  buttonsCache: [],
  buttonsStatus: [],
  axesStatus: []
};</code></pre>

<p>Массив <code>кнопок</code> содержит клавиатуру Xbox 360 :</p>

<pre class="brush: js"><code>buttons: [
  'DPad-Up','DPad-Down','DPad-Left','DPad-Right',
  'Start','Back','Axis-Left','Axis-Right',
  'LB','RB','Power','A','B','X','Y',
],</code>
</pre>

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

<pre class="brush: js"><code>window.addEventListener("gamepadconnected", gamepadAPI.connect);
window.addEventListener("gamepaddisconnected", gamepadAPI.disconnect);</code>
</pre>

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

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

<pre class="brush: js"><code>connect: function(evt) {
  gamepadAPI.controller = evt.gamepad;
  gamepadAPI.turbo = true;
  console.log('Gamepad connected.');
},</code></pre>

<p>The <code>connect()</code> function takes the event as a parameter and assigns the <code>gamepad</code> object to the <code>gamepadAPI.controller</code> 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 <code>turbo</code> property to <code>true</code>. (We could use the <code>gamepad.connected</code> 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.)</p>

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

<pre class="brush: js"><code>disconnect: function(evt) {
  gamepadAPI.turbo = false;
  delete gamepadAPI.controller;
  console.log('Gamepad disconnected.');
},</code>
</pre>

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

<h3 id="Объект_геймпада">Объект геймпада</h3>

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

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

<p>Переменная <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">index</span></font> полезна, если мы подключаем более одного контроллера и хотим идентифицировать их, чтобы действовать соответственно — например, когда у нас есть игра для двух игроков, требующая подключения двух устройств.</p>

<h3 id="Запрос_объекта_геймпада">Запрос объекта геймпада</h3>

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

<pre class="brush: js"><code>update: function() {
  // clear the buttons cache
  gamepadAPI.buttonsCache = [];
  // move the buttons status from the previous frame to the cache
  for(var k=0; k&lt;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&lt;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&lt;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;
},</code>
</pre>

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

<h3 id="Обнаружение_нажатия_кнопок">Обнаружение нажатия кнопок</h3>

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

<pre class="brush: js"><code>buttonPressed: function(button, hold) {
  var newPress = false;
  // loop through pressed buttons
  for(var i=0,s=gamepadAPI.buttonsStatus.length; i&lt;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&lt;p; j++) {
          // if the button was already pressed, ignore new press
          if(gamepadAPI.buttonsCache[j] == button) {
            newPress = false;
          }
        }
      }
    }
  }
  return newPress;
},</code>
</pre>

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

<pre class="brush: js"><code>if(gamepadAPI.turbo) {
  if(gamepadAPI.buttonPressed('A','hold')) {
    this.turbo_fire();
  }
  if(gamepadAPI.buttonPressed('B')) {
    this.managePause();
  }
}</code>
</pre>

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

<h3 id="Порог_оси">Порог оси</h3>

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

<p><img alt="axis threshold" src="http://end3r.com/tmp/gamepad/axis-threshold.png" style="display: block; height: 300px; margin: 0px auto; width: 400px;"></p>

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

<pre><code>if(gamepadAPI.axesStatus[0].x &gt; 0.5) {
  this.player.angle += 3;
  this.turret.angle += 3;
}</code>
</pre>

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

<h2 id="Обновление_спецификаций">Обновление спецификаций</h2>

<p>После более чем годичной стабильности, в апреле 2015 года была обновлена спецификация API W3C Gamepad (<a href="https://w3c.github.io/gamepad/">см. последнюю версию</a>). Он не сильно изменился, но хорошо знать, что происходит — обновления заключаются в следующем...</p>

<h3 id="Получение_геймпадов">Получение геймпадов</h3>

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

<h3 id="Стандартное_отображение">Стандартное отображение</h3>

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

<pre>enum GamepadMappingType {
    "",
    "standard"
};
</pre>

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

<h3 id="События">События</h3>

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

<h2 id="Подведение_итогов">Подведение итогов</h2>

<p>API геймпада очень прост в разработке. Теперь это проще, чем когда-либо, вы можете использовать браузер как консоль без необходимости каких-либо плагинов. Вы можете играть в полную версию игры Hungry Fridge непосредственно в вашем браузере, установить ее из <a href="https://marketplace.firefox.com/app/hungry-fridge">Firefox Marketplace</a> или проверить исходный код демо-версии вместе со всеми другими ресурсами в <a href="https://github.com/EnclaveGames/Hungry-Fridge">комплекте контента Gamepad API</a>.</p>