Введение
В июне 2010 года мы узнали, что местное издательство Boing Boing проводит конкурс на разработку игр . Мы увидели в этом отличный повод создать быструю и простую игру на JavaScript и <canvas>
, поэтому приступили к работе. После конкурса у нас еще было много идей и хотелось закончить начатое. Вот пример результата: маленькая игра под названием Onslaught! Арена .
Пиксельный ретро-стиль
Было важно, чтобы наша игра выглядела и ощущалась как ретро-игра Nintendo Entertainment System , учитывая предпосылку конкурса на разработку игры на основе чиптюна . В большинстве игр этого требования нет, но это по-прежнему распространенный художественный стиль (особенно среди инди-разработчиков) из-за простоты создания ресурсов и естественной привлекательности для ностальгирующих геймеров.

Учитывая, насколько малы эти спрайты, мы решили удвоить количество пикселей, а это означает, что спрайт 16x16 теперь будет иметь размер 32x32 пикселя и так далее. С самого начала мы удваивали работу по созданию ресурсов вместо того, чтобы заставлять браузер выполнять тяжелую работу. Это было просто проще реализовать, но при этом имело некоторые определенные преимущества во внешнем виде.
Вот сценарий, который мы рассмотрели:
<style>
canvas {
width: 640px;
height: 320px;
}
</style>
<canvas width="320" height="240">
Sorry, your browser is not supported.
</canvas>
Этот метод будет состоять из спрайтов 1x1 вместо их удвоения на стороне создания ресурса. После этого CSS возьмет на себя управление и изменит размер самого холста. Наши тесты показали, что этот метод может быть примерно в два раза быстрее, чем рендеринг более крупных (удвоенных) изображений, но, к сожалению, изменение размера CSS включает в себя сглаживание, и мы не смогли найти способ предотвратить это.

Это стало решающим фактором для нашей игры, поскольку отдельные пиксели очень важны, но если вам нужно изменить размер холста и сглаживание подходит для вашего проекта, вы можете рассмотреть этот подход из соображений производительности.
Забавные трюки с холстом
Мы все знаем, что <canvas>
— это новая мода, но иногда разработчики все же рекомендуют использовать DOM . Если вы сомневаетесь, что использовать, вот пример того, как <canvas>
сэкономил нам много времени и энергии.
Когда враг получает удар в режиме «Натиск!» Arena , он мигает красным и на короткое время отображает анимацию «боли». Чтобы ограничить количество графики, которую нам пришлось создать, мы показываем врагов, страдающих от боли, только в направлении вниз. В игре это выглядит приемлемо и позволяет сэкономить кучу времени на создании спрайтов. Однако монстров-боссов было неприятно видеть, как большой спрайт (размером 64x64 пикселей или больше) внезапно переключался с направления влево или вверх на внезапное положение лицом вниз для кадра боли.
Очевидным решением было бы нарисовать болевые рамки для каждого босса в каждом из восьми направлений, но это заняло бы очень много времени. Благодаря <canvas>
мы смогли решить эту проблему в коде:

Сначала мы рисуем монстра в скрытом «буфере» <canvas>
, накладываем на него красный цвет, а затем отображаем результат обратно на экран. Код выглядит примерно так:
// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");
// Draw your image on the buffer
buffer.drawImage(image, 0, 0);
// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();
// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);
Игровой цикл
Разработка игр имеет некоторые заметные отличия от веб-разработки. В веб-стеке принято реагировать на происходящие события через прослушиватели событий. Таким образом, код инициализации не может делать ничего, кроме прослушивания входных событий. Логика игры другая, поскольку необходимо постоянно обновляться. Если, например, игрок не пошевелился, это не должно помешать гоблинам схватить его!
Вот пример игрового цикла:
function main () {
handleInput();
update();
render();
};
setInterval(main, 1);
Первое важное отличие состоит в том, что функция handleInput
на самом деле ничего не делает сразу. Если пользователь нажимает клавишу в типичном веб-приложении, имеет смысл немедленно выполнить желаемое действие. Но в игре все должно происходить в хронологическом порядке, чтобы идти правильно.
window.addEventListener("mousedown", function(e) {
// A mouse click means the players wants to attack.
// We don't actually do that yet, but instead tell the rest
// of the program about the request.
buttonStates[e.button] = true;
}, false);
function handleInput() {
// Here is where we respond to the click
if (buttonStates[LEFT_BUTTON]) {
player.attacking = true;
delete buttonStates[LEFT_BUTTON];
}
};
Теперь мы знаем о входных данных и можем учитывать их в функции update
, зная, что они будут соответствовать остальным правилам игры.
function update() {
// Check for collisions, states, whatever else is needed
// If after that the player can still attack, do it!
if (player.attacking && player.canAttack()) {
player.attack();
}
};
Наконец, когда все вычислено, пришло время перерисовать экран! В DOM-стране эту тяжелую работу берет на себя браузер. Но при использовании <canvas>
необходимо вручную перерисовывать каждый раз, когда что-то происходит (обычно это каждый отдельный кадр!).
function render() {
// First erase everything, something like:
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw the player (and whatever else you need)
context.drawImage(
player.getImage(),
player.x, player.y
);
};
Временное моделирование
Моделирование на основе времени — это концепция перемещения спрайтов на основе количества времени, прошедшего с момента последнего обновления кадра. Этот метод позволяет вашей игре работать как можно быстрее, обеспечивая при этом движение спрайтов с постоянной скоростью.
Чтобы использовать моделирование на основе времени, нам нужно зафиксировать время, прошедшее с момента отрисовки последнего кадра. Нам нужно будет дополнить функцию update()
нашего игрового цикла, чтобы отслеживать это.
function update() {
// NOTE: You'll need to initially seed this.lastUpdate
// with the current time when your game loop starts
// this.lastUpdate = Date.now();
// Calculate elapsed time since last frame
var now = Date.now();
var elapsed = (now - this.lastUpdate);
this.lastUpdate = now;
// Do stuff with elapsed
};
Теперь, когда у нас есть прошедшее время, мы можем вычислить, насколько далеко данный спрайт должен перемещаться в каждом кадре. Во-первых, нам нужно отслеживать несколько вещей на объекте-спрайте: текущую позицию, скорость и направление.
var Sprite = function() {
// The sprite's position relative to the top left of the game world
this.position = {x: 0, y: 0};
// The sprite's direction. A positive x value indicates moving to the right
this.direction = {x: 1, y: 0};
// How many pixels the sprite moves per second
this.speed = 50;
};
Имея в виду эти переменные, вот как мы могли бы переместить экземпляр вышеуказанного класса спрайтов, используя моделирование на основе времени:
// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;
// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);
Обратите внимание, что значения direction.x
и direction.y
должны быть нормализованы , что означает, что они всегда должны находиться в диапазоне от -1
до 1
.
Элементы управления
Возможно, управление было самым большим камнем преткновения при разработке Onslaught! Арена . Самая первая демоверсия поддерживала только клавиатуру; игроки перемещали главного героя по экрану с помощью клавиш со стрелками и стреляли в том направлении, куда он смотрел, с помощью клавиши пробела. Хотя игра была несколько интуитивно понятной и простой для понимания, это делало игру практически неиграбельной на более сложных уровнях. Поскольку в любой момент времени в игрока летят десятки врагов и снарядов, крайне важно иметь возможность лавировать между плохими парнями , стреляя в любом направлении.
Для сравнения с аналогичными играми этого жанра мы добавили поддержку мыши для управления прицельной сеткой, которую персонаж будет использовать для нацеливания своих атак. Персонажа по-прежнему можно было перемещать с помощью клавиатуры, но после этого изменения он мог одновременно стрелять в любом направлении на 360 градусов. Заядлые игроки оценили эту функцию, но у нее был досадный побочный эффект: разочарование пользователей трекпада.

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

Нам также повезло, что у нас есть несколько европейских фанатов, но мы слышали от них разочарование по поводу того, что у них нет типичной QWERTY-клавиатуры и они не могут использовать клавиши WASD для перемещения по направлению. Аналогичные жалобы высказали и левши.
С этой сложной схемой управления, которую мы реализовали, также возникает проблема с игрой на мобильных устройствах. Действительно, одна из самых частых наших просьб — сделать «Натиск»! Арена доступна на Android, iPad и других сенсорных устройствах (где нет клавиатуры). Одной из сильных сторон HTML5 является его портативность, поэтому установка игры на эти устройства определенно осуществима, нам просто нужно решить множество проблем (в первую очередь, управление и производительность).
Чтобы решить эти многочисленные проблемы, мы начали использовать метод игрового процесса с одним вводом, который предполагает только взаимодействие с помощью мыши (или касания). Игроки нажимают или касаются экрана, и главный герой идет к выбранному месту, автоматически атакуя ближайшего злодея. Код выглядит примерно так:
// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
// Found one! Shoot in its direction
var shoot = hostile.boundingBox().center().subtract(
player.boundingBox().center()
).normalize();
}
// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).magnitude();
// Prevent jittering if the character is close enough
if (distance < 3) {
move.zero();
}
// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
player.setDirection(move);
}
Удаление дополнительного фактора, связанного с необходимостью целиться во врагов, может в некоторых ситуациях облегчить игру, но мы считаем, что упрощение игры для игрока имеет много преимуществ. Появляются и другие стратегии, такие как необходимость располагать персонажа рядом с опасными врагами, чтобы нацеливаться на них, а возможность поддержки сенсорных устройств неоценима.
Аудио
Среди элементов управления и производительности — одна из самых больших проблем при разработке Onslaught! Арена была тегом <audio>
HTML5. Вероятно, худшим аспектом является задержка: почти во всех браузерах существует задержка между вызовом .play()
и фактическим воспроизведением звука. Это может испортить впечатления геймера, особенно при игре в такую динамичную игру, как наша.
Другие проблемы включают в себя сбой события «прогресса» , что может привести к зависанию процесса загрузки игры на неопределенный срок. По этим причинам мы приняли так называемый метод «перехода вперед», при котором, если Flash не загружается, мы переключаемся на HTML5 Audio. Код выглядит примерно так:
/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/
// Default to sm2 (Flash)
var api = "sm2";
function initAudio (callback) {
switch (api) {
case "sm2":
soundManager.onerror = (function (init) {
return function () {
api = "html5";
init(callback);
};
}(arguments.callee));
break;
case "html5":
var audio = document.createElement("audio");
if (
audio
&& audio.canPlayType
&& audio.canPlayType("audio/mpeg;")
) {
callback();
} else {
// No audio support :(
}
break;
}
};
Также может быть важно, чтобы игра поддерживала браузеры, которые не воспроизводят файлы MP3 (например, Mozilla Firefox). Если это так, поддержку можно обнаружить и переключить на что-то вроде Ogg Vorbis с помощью такого кода:
/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/
var audio = document.createElement("audio");
if (audio && audio.canPlayType) {
if (!audio.canPlayType("audio/mpeg;")) {
// Here you know you CANNOT use .mp3 files
if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
// Here you know you CAN use .ogg files
}
}
}
Сохранение данных
Невозможно провести аркадную перестрелку без высоких результатов! Мы знали, что нам понадобится сохранение некоторых игровых данных, и, хотя мы могли бы использовать что-то старое, например файлы cookie, мы хотели изучить новые интересные технологии HTML5. Конечно, нет недостатка в опциях, включая локальное хранилище, хранилище сеансов и базы данных Web SQL.

Мы решили использовать localStorage
, поскольку он новый, замечательный и простой в использовании. Он поддерживает сохранение базовых пар ключ/значение, а это все, что нужно нашей простой игре. Вот простой пример того, как его использовать:
if (typeof localStorage == "object") {
localStorage.setItem("foo", "bar");
localStorage.getItem("foo"); // Value is "bar"
localStorage.removeItem("foo");
localStorage.getItem("foo"); // Value is now null
}
Есть некоторые «подводные камни», о которых следует знать. Независимо от того, что вы передаете, значения сохраняются в виде строк, что может привести к неожиданным результатам:
localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
// It's true!
}
// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)
// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}
Краткое содержание
С HTML5 приятно работать. Большинство реализаций обрабатывают все, что нужно разработчику игр, от графики до сохранения состояния игры. Хотя есть некоторые проблемы роста (например, проблемы с тегом <audio>
), разработчики браузеров движутся быстро, и, несмотря на то, что дела уже идут так хорошо, будущее выглядит светлым для игр, созданных на HTML5.

Введение
В июне 2010 года мы узнали, что местное издательство Boing Boing проводит конкурс на разработку игр . Мы увидели в этом отличный повод создать быструю и простую игру на JavaScript и <canvas>
, поэтому приступили к работе. После конкурса у нас еще было много идей и хотелось закончить начатое. Вот пример результата: маленькая игра под названием Onslaught! Арена .
Пиксельный ретро-стиль
Было важно, чтобы наша игра выглядела и ощущалась как ретро-игра Nintendo Entertainment System , учитывая предпосылку конкурса на разработку игры на основе чиптюна . В большинстве игр этого требования нет, но это по-прежнему распространенный художественный стиль (особенно среди инди-разработчиков) из-за простоты создания ресурсов и естественной привлекательности для ностальгирующих геймеров.

Учитывая, насколько малы эти спрайты, мы решили удвоить количество пикселей, а это означает, что спрайт 16x16 теперь будет иметь размер 32x32 пикселя и так далее. С самого начала мы удваивали работу по созданию ресурсов вместо того, чтобы заставлять браузер выполнять тяжелую работу. Это было просто проще реализовать, но при этом имело некоторые определенные преимущества во внешнем виде.
Вот сценарий, который мы рассмотрели:
<style>
canvas {
width: 640px;
height: 320px;
}
</style>
<canvas width="320" height="240">
Sorry, your browser is not supported.
</canvas>
Этот метод будет состоять из спрайтов 1x1 вместо их удвоения на стороне создания ресурса. После этого CSS возьмет на себя управление и изменит размер самого холста. Наши тесты показали, что этот метод может быть примерно в два раза быстрее, чем рендеринг более крупных (удвоенных) изображений, но, к сожалению, изменение размера CSS включает в себя сглаживание, и мы не смогли найти способ предотвратить это.

Это стало решающим фактором для нашей игры, поскольку отдельные пиксели очень важны, но если вам нужно изменить размер холста и сглаживание подходит для вашего проекта, вы можете рассмотреть этот подход из соображений производительности.
Забавные трюки с холстом
Мы все знаем, что <canvas>
— это новая мода, но иногда разработчики все же рекомендуют использовать DOM . Если вы сомневаетесь, что использовать, вот пример того, как <canvas>
сэкономил нам много времени и энергии.
Когда враг получает удар в режиме «Натиск!» Arena , он мигает красным и на короткое время отображает анимацию «боли». Чтобы ограничить количество графики, которую нам пришлось создать, мы показываем врагов, страдающих от боли, только в направлении вниз. В игре это выглядит приемлемо и позволяет сэкономить кучу времени на создании спрайтов. Однако монстров-боссов было неприятно видеть, как большой спрайт (размером 64x64 пикселей или больше) внезапно переключался с направления влево или вверх на внезапное положение лицом вниз для кадра боли.
Очевидным решением было бы нарисовать болевые рамки для каждого босса в каждом из восьми направлений, но это заняло бы очень много времени. Благодаря <canvas>
мы смогли решить эту проблему в коде:

Сначала мы рисуем монстра в скрытом «буфере» <canvas>
, накладываем на него красный цвет, а затем отображаем результат обратно на экран. Код выглядит примерно так:
// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");
// Draw your image on the buffer
buffer.drawImage(image, 0, 0);
// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();
// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);
Игровой цикл
Разработка игр имеет некоторые заметные отличия от веб-разработки. В веб-стеке принято реагировать на происходящие события через прослушиватели событий. Таким образом, код инициализации не может делать ничего, кроме прослушивания входных событий. Логика игры другая, поскольку необходимо постоянно обновляться. Если, например, игрок не пошевелился, это не должно помешать гоблинам схватить его!
Вот пример игрового цикла:
function main () {
handleInput();
update();
render();
};
setInterval(main, 1);
Первое важное отличие состоит в том, что функция handleInput
на самом деле ничего не делает сразу. Если пользователь нажимает клавишу в типичном веб-приложении, имеет смысл немедленно выполнить желаемое действие. Но в игре все должно происходить в хронологическом порядке, чтобы идти правильно.
window.addEventListener("mousedown", function(e) {
// A mouse click means the players wants to attack.
// We don't actually do that yet, but instead tell the rest
// of the program about the request.
buttonStates[e.button] = true;
}, false);
function handleInput() {
// Here is where we respond to the click
if (buttonStates[LEFT_BUTTON]) {
player.attacking = true;
delete buttonStates[LEFT_BUTTON];
}
};
Теперь мы знаем о входных данных и можем учитывать их в функции update
, зная, что они будут соответствовать остальным правилам игры.
function update() {
// Check for collisions, states, whatever else is needed
// If after that the player can still attack, do it!
if (player.attacking && player.canAttack()) {
player.attack();
}
};
Наконец, когда все вычислено, пришло время перерисовать экран! В DOM-стране эту тяжелую работу берет на себя браузер. Но при использовании <canvas>
необходимо вручную перерисовывать каждый раз, когда что-то происходит (обычно это каждый отдельный кадр!).
function render() {
// First erase everything, something like:
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw the player (and whatever else you need)
context.drawImage(
player.getImage(),
player.x, player.y
);
};
Временное моделирование
Моделирование на основе времени — это концепция перемещения спрайтов на основе количества времени, прошедшего с момента последнего обновления кадра. Этот метод позволяет вашей игре работать как можно быстрее, обеспечивая при этом движение спрайтов с постоянной скоростью.
Чтобы использовать моделирование на основе времени, нам нужно зафиксировать время, прошедшее с момента отрисовки последнего кадра. Нам нужно будет дополнить функцию update()
нашего игрового цикла, чтобы отслеживать это.
function update() {
// NOTE: You'll need to initially seed this.lastUpdate
// with the current time when your game loop starts
// this.lastUpdate = Date.now();
// Calculate elapsed time since last frame
var now = Date.now();
var elapsed = (now - this.lastUpdate);
this.lastUpdate = now;
// Do stuff with elapsed
};
Теперь, когда у нас есть прошедшее время, мы можем вычислить, насколько далеко данный спрайт должен перемещаться в каждом кадре. Во-первых, нам нужно отслеживать несколько вещей на объекте-спрайте: текущую позицию, скорость и направление.
var Sprite = function() {
// The sprite's position relative to the top left of the game world
this.position = {x: 0, y: 0};
// The sprite's direction. A positive x value indicates moving to the right
this.direction = {x: 1, y: 0};
// How many pixels the sprite moves per second
this.speed = 50;
};
Имея в виду эти переменные, вот как мы могли бы переместить экземпляр вышеуказанного класса спрайтов, используя моделирование на основе времени:
// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;
// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);
Обратите внимание, что значения direction.x
и direction.y
должны быть нормализованы , что означает, что они всегда должны находиться в диапазоне от -1
до 1
.
Элементы управления
Возможно, управление было самым большим камнем преткновения при разработке Onslaught! Арена . Самая первая демоверсия поддерживала только клавиатуру; игроки перемещали главного героя по экрану с помощью клавиш со стрелками и стреляли в том направлении, куда он смотрел, с помощью клавиши пробела. Хотя игра была несколько интуитивно понятной и простой для понимания, это делало игру практически неиграбельной на более сложных уровнях. Поскольку в любой момент времени в игрока летят десятки врагов и снарядов, крайне важно уметь лавировать между плохими парнями , стреляя в любом направлении.
Чтобы сравнить игру с аналогичными играми этого жанра, мы добавили поддержку мыши для управления прицельной сеткой, которую персонаж будет использовать для нацеливания своих атак. Персонажа по-прежнему можно было перемещать с помощью клавиатуры, но после этого изменения он мог одновременно стрелять в любом направлении на 360 градусов. Заядлые игроки оценили эту функцию, но у нее был досадный побочный эффект: разочарование пользователей трекпада.

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

Нам также повезло, что у нас есть несколько европейских фанатов, но мы слышали от них разочарование по поводу того, что у них нет типичной QWERTY-клавиатуры и они не могут использовать клавиши WASD для перемещения по направлению. Аналогичные жалобы высказали и левши.
С этой сложной схемой управления, которую мы реализовали, также возникает проблема с игрой на мобильных устройствах. Действительно, одна из самых частых наших просьб — сделать «Натиск»! Арена доступна на Android, iPad и других сенсорных устройствах (где нет клавиатуры). Одной из сильных сторон HTML5 является его портативность, поэтому установка игры на эти устройства определенно осуществима, нам просто нужно решить множество проблем (в первую очередь, управление и производительность).
Чтобы решить эти многочисленные проблемы, мы начали использовать метод игрового процесса с одним вводом, который предполагает только взаимодействие с помощью мыши (или касания). Игроки нажимают или касаются экрана, и главный герой идет к выбранному месту, автоматически атакуя ближайшего злодея. Код выглядит примерно так:
// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
// Found one! Shoot in its direction
var shoot = hostile.boundingBox().center().subtract(
player.boundingBox().center()
).normalize();
}
// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).magnitude();
// Prevent jittering if the character is close enough
if (distance < 3) {
move.zero();
}
// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
player.setDirection(move);
}
Удаление дополнительного фактора, связанного с необходимостью целиться во врагов, может в некоторых ситуациях облегчить игру, но мы считаем, что упрощение игры для игрока имеет много преимуществ. Появляются и другие стратегии, такие как необходимость располагать персонажа рядом с опасными врагами, чтобы нацеливаться на них, а возможность поддержки сенсорных устройств неоценима.
Аудио
Среди элементов управления и производительности — одна из самых больших проблем при разработке Onslaught! Арена была тегом <audio>
HTML5. Вероятно, худшим аспектом является задержка: почти во всех браузерах существует задержка между вызовом .play()
и фактическим воспроизведением звука. Это может испортить впечатления геймера, особенно при игре в такую динамичную игру, как наша.
Другие проблемы включают в себя сбой события «прогресса» , что может привести к зависанию процесса загрузки игры на неопределенный срок. По этим причинам мы приняли так называемый метод «перехода вперед», при котором, если Flash не загружается, мы переключаемся на HTML5 Audio. Код выглядит примерно так:
/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/
// Default to sm2 (Flash)
var api = "sm2";
function initAudio (callback) {
switch (api) {
case "sm2":
soundManager.onerror = (function (init) {
return function () {
api = "html5";
init(callback);
};
}(arguments.callee));
break;
case "html5":
var audio = document.createElement("audio");
if (
audio
&& audio.canPlayType
&& audio.canPlayType("audio/mpeg;")
) {
callback();
} else {
// No audio support :(
}
break;
}
};
Также может быть важно, чтобы игра поддерживала браузеры, которые не воспроизводят файлы MP3 (например, Mozilla Firefox). Если это так, поддержку можно обнаружить и переключить на что-то вроде Ogg Vorbis с помощью такого кода:
/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/
var audio = document.createElement("audio");
if (audio && audio.canPlayType) {
if (!audio.canPlayType("audio/mpeg;")) {
// Here you know you CANNOT use .mp3 files
if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
// Here you know you CAN use .ogg files
}
}
}
Сохранение данных
Невозможно провести аркадную перестрелку без высоких результатов! Мы знали, что нам понадобится сохранение некоторых игровых данных, и, хотя мы могли бы использовать что-то старое, например файлы cookie, мы хотели изучить новые интересные технологии HTML5. Конечно, нет недостатка в опциях, включая локальное хранилище, хранилище сеансов и базы данных Web SQL.

Мы решили использовать localStorage
, поскольку он новый, замечательный и простой в использовании. Он поддерживает сохранение базовых пар ключ/значение, а это все, что нужно нашей простой игре. Вот простой пример того, как его использовать:
if (typeof localStorage == "object") {
localStorage.setItem("foo", "bar");
localStorage.getItem("foo"); // Value is "bar"
localStorage.removeItem("foo");
localStorage.getItem("foo"); // Value is now null
}
Есть некоторые «подводные камни», о которых следует знать. Независимо от того, что вы передаете, значения сохраняются в виде строк, что может привести к неожиданным результатам:
localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
// It's true!
}
// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)
// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}
Краткое содержание
С HTML5 приятно работать. Большинство реализаций обрабатывают все, что нужно разработчику игр, от графики до сохранения состояния игры. Хотя есть некоторые проблемы роста (например, проблемы с тегом <audio>
), разработчики браузеров движутся быстро, и, несмотря на то, что дела уже идут так хорошо, будущее выглядит светлым для игр, созданных на HTML5.

Введение
В июне 2010 года мы узнали, что местное издательство Boing Boing проводит конкурс на разработку игр . Мы увидели в этом отличный повод создать быструю и простую игру на JavaScript и <canvas>
, поэтому приступили к работе. После конкурса у нас еще было много идей и хотелось закончить начатое. Вот пример результата: маленькая игра под названием Onslaught! Арена .
Пиксельный ретро-стиль
Было важно, чтобы наша игра выглядела и ощущалась как ретро-игра Nintendo Entertainment System , учитывая предпосылку конкурса на разработку игры на основе чиптюна . В большинстве игр этого требования нет, но это по-прежнему распространенный художественный стиль (особенно среди инди-разработчиков) из-за простоты создания ресурсов и естественной привлекательности для ностальгирующих геймеров.

Учитывая, насколько малы эти спрайты, мы решили удвоить количество пикселей, а это означает, что спрайт 16x16 теперь будет иметь размер 32x32 пикселя и так далее. С самого начала мы удваивали работу по созданию ресурсов вместо того, чтобы заставлять браузер выполнять тяжелую работу. Это было просто проще реализовать, но при этом имело некоторые определенные преимущества во внешнем виде.
Вот сценарий, который мы рассмотрели:
<style>
canvas {
width: 640px;
height: 320px;
}
</style>
<canvas width="320" height="240">
Sorry, your browser is not supported.
</canvas>
Этот метод будет состоять из спрайтов 1x1 вместо их удвоения на стороне создания ресурса. После этого CSS возьмет на себя управление и изменит размер самого холста. Наши тесты показали, что этот метод может быть примерно в два раза быстрее, чем рендеринг более крупных (удвоенных) изображений, но, к сожалению, изменение размера CSS включает в себя сглаживание, и мы не смогли найти способ предотвратить это.

Это стало решающим фактором для нашей игры, поскольку отдельные пиксели очень важны, но если вам нужно изменить размер холста и сглаживание подходит для вашего проекта, вы можете рассмотреть этот подход из соображений производительности.
Забавные трюки с холстом
Мы все знаем, что <canvas>
— это новая мода, но иногда разработчики все же рекомендуют использовать DOM . Если вы сомневаетесь, что использовать, вот пример того, как <canvas>
сэкономил нам много времени и энергии.
Когда враг получает удар в режиме «Натиск!» Arena , он мигает красным и на короткое время отображает анимацию «боли». Чтобы ограничить количество графики, которую нам пришлось создать, мы показываем врагов, страдающих от боли, только в направлении вниз. В игре это выглядит приемлемо и позволяет сэкономить кучу времени на создании спрайтов. Однако монстров-боссов было неприятно видеть, как большой спрайт (размером 64x64 пикселей или больше) резко переключается с направления влево или вверх на внезапное положение лицом вниз для кадра боли.
Очевидным решением было бы нарисовать болевые рамки для каждого босса в каждом из восьми направлений, но это заняло бы очень много времени. Благодаря <canvas>
мы смогли решить эту проблему в коде:

Сначала мы рисуем монстра в скрытом «буфере» <canvas>
, накладываем на него красный цвет, а затем отображаем результат обратно на экран. Код выглядит примерно так:
// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");
// Draw your image on the buffer
buffer.drawImage(image, 0, 0);
// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();
// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);
Игровой цикл
Разработка игр имеет некоторые заметные отличия от веб-разработки. В веб-стеке принято реагировать на происходящие события через прослушиватели событий. Таким образом, код инициализации не может делать ничего, кроме прослушивания входных событий. Логика игры другая, поскольку необходимо постоянно обновляться. Если, например, игрок не пошевелился, это не должно помешать гоблинам схватить его!
Вот пример игрового цикла:
function main () {
handleInput();
update();
render();
};
setInterval(main, 1);
Первое важное отличие состоит в том, что функция handleInput
на самом деле ничего не делает сразу. Если пользователь нажимает клавишу в типичном веб-приложении, имеет смысл немедленно выполнить желаемое действие. Но в игре все должно происходить в хронологическом порядке, чтобы идти правильно.
window.addEventListener("mousedown", function(e) {
// A mouse click means the players wants to attack.
// We don't actually do that yet, but instead tell the rest
// of the program about the request.
buttonStates[e.button] = true;
}, false);
function handleInput() {
// Here is where we respond to the click
if (buttonStates[LEFT_BUTTON]) {
player.attacking = true;
delete buttonStates[LEFT_BUTTON];
}
};
Теперь мы знаем о входных данных и можем учитывать их в функции update
, зная, что они будут соответствовать остальным правилам игры.
function update() {
// Check for collisions, states, whatever else is needed
// If after that the player can still attack, do it!
if (player.attacking && player.canAttack()) {
player.attack();
}
};
Наконец, когда все вычислено, пришло время перерисовать экран! В DOM-стране эту тяжелую работу берет на себя браузер. Но при использовании <canvas>
необходимо вручную перерисовывать каждый раз, когда что-то происходит (обычно это каждый отдельный кадр!).
function render() {
// First erase everything, something like:
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw the player (and whatever else you need)
context.drawImage(
player.getImage(),
player.x, player.y
);
};
Временное моделирование
Моделирование на основе времени — это концепция перемещения спрайтов на основе количества времени, прошедшего с момента последнего обновления кадра. Этот метод позволяет вашей игре работать как можно быстрее, обеспечивая при этом движение спрайтов с постоянной скоростью.
Чтобы использовать моделирование на основе времени, нам нужно зафиксировать время, прошедшее с момента отрисовки последнего кадра. Нам нужно будет дополнить функцию update()
нашего игрового цикла, чтобы отслеживать это.
function update() {
// NOTE: You'll need to initially seed this.lastUpdate
// with the current time when your game loop starts
// this.lastUpdate = Date.now();
// Calculate elapsed time since last frame
var now = Date.now();
var elapsed = (now - this.lastUpdate);
this.lastUpdate = now;
// Do stuff with elapsed
};
Теперь, когда у нас есть прошедшее время, мы можем вычислить, насколько далеко данный спрайт должен перемещаться в каждом кадре. Во-первых, нам нужно отслеживать несколько вещей на объекте-спрайте: текущую позицию, скорость и направление.
var Sprite = function() {
// The sprite's position relative to the top left of the game world
this.position = {x: 0, y: 0};
// The sprite's direction. A positive x value indicates moving to the right
this.direction = {x: 1, y: 0};
// How many pixels the sprite moves per second
this.speed = 50;
};
Имея в виду эти переменные, вот как мы могли бы переместить экземпляр вышеуказанного класса спрайтов, используя моделирование на основе времени:
// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;
// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);
Обратите внимание, что значения direction.x
и direction.y
должны быть нормализованы , что означает, что они всегда должны находиться в диапазоне от -1
до 1
.
Элементы управления
Возможно, управление было самым большим камнем преткновения при разработке Onslaught! Арена . Самая первая демоверсия поддерживала только клавиатуру; игроки перемещали главного героя по экрану с помощью клавиш со стрелками и стреляли в том направлении, куда он смотрел, с помощью клавиши пробела. Хотя игра была несколько интуитивно понятной и простой для понимания, это делало игру практически неиграбельной на более сложных уровнях. Поскольку в любой момент времени в игрока летят десятки врагов и снарядов, крайне важно уметь лавировать между плохими парнями , стреляя в любом направлении.
Чтобы сравнить игру с аналогичными играми этого жанра, мы добавили поддержку мыши для управления прицельной сеткой, которую персонаж будет использовать для нацеливания своих атак. Персонажа по-прежнему можно было перемещать с помощью клавиатуры, но после этого изменения он мог одновременно стрелять в любом направлении на 360 градусов. Заядлые игроки оценили эту функцию, но у нее был досадный побочный эффект: разочарование пользователей трекпада.

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

Нам также повезло, что у нас есть несколько европейских фанатов, но мы слышали от них разочарование по поводу того, что у них нет типичной QWERTY-клавиатуры и они не могут использовать клавиши WASD для перемещения по направлению. Аналогичные жалобы высказали и левши.
С этой сложной схемой управления, которую мы реализовали, также возникает проблема с игрой на мобильных устройствах. Действительно, одна из самых частых наших просьб — сделать «Натиск»! Арена доступна на Android, iPad и других сенсорных устройствах (где нет клавиатуры). Одной из сильных сторон HTML5 является его портативность, поэтому установка игры на эти устройства определенно осуществима, нам просто нужно решить множество проблем (в первую очередь, управление и производительность).
Чтобы решить эти многочисленные проблемы, мы начали использовать метод игрового процесса с одним вводом, который предполагает только взаимодействие с помощью мыши (или касания). Игроки нажимают или касаются экрана, и главный герой идет к выбранному месту, автоматически атакуя ближайшего злодея. Код выглядит примерно так:
// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
// Found one! Shoot in its direction
var shoot = hostile.boundingBox().center().subtract(
player.boundingBox().center()
).normalize();
}
// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).magnitude();
// Prevent jittering if the character is close enough
if (distance < 3) {
move.zero();
}
// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
player.setDirection(move);
}
Удаление дополнительного фактора, связанного с необходимостью целиться во врагов, может в некоторых ситуациях облегчить игру, но мы считаем, что упрощение игры для игрока имеет много преимуществ. Появляются и другие стратегии, такие как необходимость располагать персонажа рядом с опасными врагами, чтобы нацеливаться на них, а возможность поддержки сенсорных устройств неоценима.
Аудио
Среди элементов управления и производительности — одна из самых больших проблем при разработке Onslaught! Арена была тегом <audio>
HTML5. Вероятно, худшим аспектом является задержка: почти во всех браузерах существует задержка между вызовом .play()
и фактическим воспроизведением звука. Это может испортить впечатления геймера, особенно при игре в такую динамичную игру, как наша.
Другие проблемы включают в себя сбой события «прогресса» , что может привести к зависанию процесса загрузки игры на неопределенный срок. По этим причинам мы приняли так называемый метод «перехода вперед», при котором, если Flash не загружается, мы переключаемся на HTML5 Audio. Код выглядит примерно так:
/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/
// Default to sm2 (Flash)
var api = "sm2";
function initAudio (callback) {
switch (api) {
case "sm2":
soundManager.onerror = (function (init) {
return function () {
api = "html5";
init(callback);
};
}(arguments.callee));
break;
case "html5":
var audio = document.createElement("audio");
if (
audio
&& audio.canPlayType
&& audio.canPlayType("audio/mpeg;")
) {
callback();
} else {
// No audio support :(
}
break;
}
};
Также может быть важно, чтобы игра поддерживала браузеры, которые не воспроизводят файлы MP3 (например, Mozilla Firefox). Если это так, поддержку можно обнаружить и переключить на что-то вроде Ogg Vorbis с помощью такого кода:
/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/
var audio = document.createElement("audio");
if (audio && audio.canPlayType) {
if (!audio.canPlayType("audio/mpeg;")) {
// Here you know you CANNOT use .mp3 files
if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
// Here you know you CAN use .ogg files
}
}
}
Сохранение данных
Невозможно провести аркадную перестрелку без высоких результатов! Мы знали, что нам понадобится сохранение некоторых игровых данных, и, хотя мы могли бы использовать что-то старое, например файлы cookie, мы хотели изучить новые интересные технологии HTML5. Конечно, нет недостатка в опциях, включая локальное хранилище, хранилище сеансов и базы данных Web SQL.

Мы решили использовать localStorage
, поскольку он новый, замечательный и простой в использовании. Он поддерживает сохранение базовых пар ключ/значение, а это все, что нужно нашей простой игре. Вот простой пример того, как его использовать:
if (typeof localStorage == "object") {
localStorage.setItem("foo", "bar");
localStorage.getItem("foo"); // Value is "bar"
localStorage.removeItem("foo");
localStorage.getItem("foo"); // Value is now null
}
Есть некоторые «подводные камни», о которых следует знать. Независимо от того, что вы передаете, значения сохраняются в виде строк, что может привести к неожиданным результатам:
localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
// It's true!
}
// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)
// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}
Краткое содержание
С HTML5 приятно работать. Большинство реализаций обрабатывают все, что нужно разработчику игр, от графики до сохранения состояния игры. Хотя есть некоторые проблемы роста (например, проблемы с тегом <audio>
), разработчики браузеров движутся быстро, и, несмотря на то, что дела уже идут так хорошо, будущее выглядит светлым для игр, созданных на HTML5.

Введение
В июне 2010 года мы узнали, что местное издательство Boing Boing проводит конкурс на разработку игр . Мы увидели в этом отличный повод создать быструю и простую игру на JavaScript и <canvas>
, поэтому приступили к работе. После конкурса у нас еще было много идей и хотелось закончить начатое. Вот пример результата: маленькая игра под названием Onslaught! Арена .
Пиксельный ретро-стиль
Было важно, чтобы наша игра выглядела и чувствовала себя как игра в ретро Nintendo Entertainment System , учитывая предпосылку конкурса для разработки игры, основанной на Chiptune . В большинстве игр нет такого требования, но это все еще общий художественный стиль (особенно среди инди -разработчиков) из -за простоты создания активов и естественной привлекательности для ностальгических геймеров.

Учитывая, насколько маленькие эти спрайты мы решили удвоить наши пиксели, а это означает, что спрайт 16x16 теперь будет 32x32 пикселей и так далее. С самого начала мы удваивались на стороне создания активов вместо того, чтобы заставлять браузер делать тяжелую работу. Это было просто проще в реализации, но также имело некоторые определенные преимущества внешнего вида.
Вот сценарий, который мы рассмотрели:
<style>
canvas {
width: 640px;
height: 320px;
}
</style>
<canvas width="320" height="240">
Sorry, your browser is not supported.
</canvas>
Этот метод будет состоять из Spirits 1x1 вместо того, чтобы удвоить их на стороне создания активов. Оттуда CSS вступит во владение и изменяет размер сам холст. Наши критерии показали, что этот метод может быть примерно в два раза быстрее, чем делать более крупные (удвоенные) изображения, но, к сожалению, изменение размера CSS включает в себя анти-алиации, что мы не смогли найти способ предотвратить.

Это было нарушение сделки для нашей игры, поскольку отдельные пиксели так важны, но если вам нужно изменить размер своего холста, а анти-алиасы подходят для вашего проекта, вы можете рассмотреть этот подход по причинам производительности.
Веселые холст
Мы все знаем, что <canvas>
- это новое горячие, но иногда разработчики по -прежнему рекомендуют использовать DOM . Если вы находитесь на заборе, о котором можно использовать, вот пример того, как <canvas>
сэкономил нам много времени и энергии.
Когда враг попадает в натиск! Арена , она мигает красной и кратко отображает анимацию «боли». Чтобы ограничить количество графики, которую мы должны были создать, мы показываем врагов только в «боли» в направлении, направленном на нисходящее. Это выглядит приемлемым в игре и сэкономил тонны времени создания спрайта. Однако для босса монстров было резко увидеть большой спрайт (при 64x64 пикселях или более), снявшись с левой стороны или до внезапного обращения вниз для болевой рамки.
Очевидным решением было бы нарисовать боль для каждого босса в каждом из восьми направлений, но это было бы очень трудоемким. Благодаря <canvas>
мы смогли решить эту проблему в коде:

Сначала мы нарисуем монстра в скрытый «буфер» <canvas>
, накладываем его красным, а затем приведет к результату обратно на экран. Код выглядит примерно так:
// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");
// Draw your image on the buffer
buffer.drawImage(image, 0, 0);
// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();
// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);
Игровая петля
Разработка игры имеет некоторые заметные отличия от веб -разработки. В веб -стеке обычно реагируют на события, которые произошли через слушателей событий. Таким образом, код инициализации не может не делать ничего, кроме прослушивания событий ввода. Логика игры отличается, так как необходимо постоянно обновлять себя. Если, например, игрок не двигался, это не должно помешать гоблинам получить его!
Вот пример игровой петли:
function main () {
handleInput();
update();
render();
};
setInterval(main, 1);
Первое важное отличие состоит в том, что функция handleInput
на самом деле ничего не делает сразу. Если пользователь нажимает клавишу в типичном веб -приложении, имеет смысл немедленно выполнить желаемое действие. Но в игре все должно произойти в хронологическом порядке, чтобы правильно течь.
window.addEventListener("mousedown", function(e) {
// A mouse click means the players wants to attack.
// We don't actually do that yet, but instead tell the rest
// of the program about the request.
buttonStates[e.button] = true;
}, false);
function handleInput() {
// Here is where we respond to the click
if (buttonStates[LEFT_BUTTON]) {
player.attacking = true;
delete buttonStates[LEFT_BUTTON];
}
};
Теперь мы знаем о вводе и можем рассмотреть его в функции update
, зная, что он будет придерживаться остальных правил игры.
function update() {
// Check for collisions, states, whatever else is needed
// If after that the player can still attack, do it!
if (player.attacking && player.canAttack()) {
player.attack();
}
};
Наконец, как только все будет рассчитано, пришло время перерисовать экран! В Dom-Land браузер обрабатывает эту подъему вздымания. Но при использовании <canvas>
необходимо вручную перерисовать, когда что -то происходит (что обычно является каждым кадром!).
function render() {
// First erase everything, something like:
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw the player (and whatever else you need)
context.drawImage(
player.getImage(),
player.x, player.y
);
};
Основанное на времени моделирование
Основанное на времени моделирование-это концепция движущихся спрайтов в зависимости от количества прошедшего времени с момента последнего обновления кадров. Эта техника позволяет вашей игре работать как можно быстрее, обеспечивая, чтобы спрайты двигались с постоянными скоростями.
Чтобы использовать моделирование на основе времени, мы должны запечатлеть истеченное время с момента проведения последнего кадра. Нам нужно увеличить функцию update()
нашего Game Loop, чтобы отслеживать это.
function update() {
// NOTE: You'll need to initially seed this.lastUpdate
// with the current time when your game loop starts
// this.lastUpdate = Date.now();
// Calculate elapsed time since last frame
var now = Date.now();
var elapsed = (now - this.lastUpdate);
this.lastUpdate = now;
// Do stuff with elapsed
};
Теперь, когда у нас есть прошедшее время, мы можем рассчитать, как далеко должен перемещать каждую кадр. Во -первых, нам нужно отслеживать несколько вещей на объекте спрайта: тока, скорость и направление.
var Sprite = function() {
// The sprite's position relative to the top left of the game world
this.position = {x: 0, y: 0};
// The sprite's direction. A positive x value indicates moving to the right
this.direction = {x: 1, y: 0};
// How many pixels the sprite moves per second
this.speed = 50;
};
Имея в виду эти переменные, вот как мы переместили экземпляр вышеуказанного класса спрайта, используя моделирование на основе времени:
// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;
// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);
Обратите 1
, что direction.x
и direction.y
-1
Элементы управления
Управление было, возможно, самым большим камнем преткновения при развитии натиска! Арена . Самая первая демонстрация поддерживала только клавиатуру; Игроки переместили главного героя вокруг экрана с клавишами стрел и выстрелили в направлении, в котором он столкнулся с космической балкой. Хотя это несколько интуитивно понятно и легко понять, это сделало игру практически невыплачиваемой на более сложных уровнях. С десятками врагов и снарядов, летящих в игрока в любой момент времени, необходимо иметь возможность перепрыгивать между плохими парнями во время стрельбы в любом направлении.
Чтобы сравнить с аналогичными играми в его жанре, мы добавили поддержку мыши, чтобы контролировать целевую сетку, которую персонаж будет использовать для нацеливания своих атак. Персонаж все еще может быть перемещен с клавиатурой, но после этого изменения он мог одновременно стрелять в любом полном направлении на 360 градусов. Хардкорные игроки оценили эту функцию, но у нее был неудачный побочный эффект от разочаровывающих пользователей TrackPad.

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

Нам также повезло, что у нас есть несколько европейских поклонников, но мы слышали от них разочарование, что у них может не быть типичных клавиатур Qwerty, и они не могут использовать ключи Wasd для направленного движения. Левчатые игроки выразили аналогичные жалобы.
С помощью этой сложной схемы управления, которую мы реализовали, есть и проблема игры на мобильных устройствах. Действительно, одна из наших самых распространенных запросов - сделать натиск! Арена доступна на Android, iPad и других сенсорных устройствах (где нет клавиатуры). Одной из основных сил HTML5 является его портативность, поэтому, чтобы получить игру на эти устройства, безусловно, выполнимо, нам просто нужно решить множество проблем (в частности, управление и производительность).
Чтобы решить эти многочисленные проблемы, мы начали играть с одним входным методом игрового процесса, который включает в себя только взаимодействие мыши (или прикосновения). Игроки щелкнули или касаются экрана, и главный герой идет к нажатому месту, автоматически атакуя ближайшего плохого парня. Код выглядит примерно так:
// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
// Found one! Shoot in its direction
var shoot = hostile.boundingBox().center().subtract(
player.boundingBox().center()
).normalize();
}
// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
player.boundingBox().center()
).magnitude();
// Prevent jittering if the character is close enough
if (distance < 3) {
move.zero();
}
// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
player.setDirection(move);
}
Удаление дополнительного фактора необходимости стремиться к врагам может облегчить игру в некоторых ситуациях, но мы чувствуем, что упрощающую ситуацию для игрока имеет много преимуществ. Появляются другие стратегии, такие как необходимость позиционировать персонажа близко к опасным врагам, чтобы нацелиться на них, и способность поддерживать сенсорные устройства неоценима.
Аудио
Среди контроля и производительности одна из наших самых больших проблем при развитии натиска! Арена была тегом HTML5 <audio>
. Вероятно, худший аспект - задержка: почти во всех браузерах есть задержка между вызовом .play()
и звуком, на самом деле играющим. Это может испортить опыт геймера, особенно когда играет с быстро развивающейся игрой, такой как наша.
Другие проблемы включают событие «прогресс», которое не удалось стрелять , что может привести к тому, что поток загрузки игры висит бесконечно. По этим причинам мы приняли то, что мы называем методом «падения», где, если Flash не удается загружать, мы переключаем на аудио HTML5. Код выглядит примерно так:
/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/
// Default to sm2 (Flash)
var api = "sm2";
function initAudio (callback) {
switch (api) {
case "sm2":
soundManager.onerror = (function (init) {
return function () {
api = "html5";
init(callback);
};
}(arguments.callee));
break;
case "html5":
var audio = document.createElement("audio");
if (
audio
&& audio.canPlayType
&& audio.canPlayType("audio/mpeg;")
) {
callback();
} else {
// No audio support :(
}
break;
}
};
Для игры также может быть важно поддерживать браузеры, которые не будут воспроизводиться файлами MP3 (например, Mozilla Firefox). Если это так, то поддержка может быть обнаружена и переключена на что -то вроде OGG Vorbis , с таким кодом, как это:
/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/
var audio = document.createElement("audio");
if (audio && audio.canPlayType) {
if (!audio.canPlayType("audio/mpeg;")) {
// Here you know you CANNOT use .mp3 files
if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
// Here you know you CAN use .ogg files
}
}
}
Сохранение данных
Вы не можете сделать их в аркадном стиле без высоких результатов! Мы знали, что нам понадобятся некоторые из наших игровых данных, чтобы упорствоваться, и хотя мы могли бы использовать что-то старое, похожее на печенье, мы хотели копаться в новой веселой технологии HTML5. Конечно, нет недостатка в параметрах, включая локальное хранилище, хранение сеансов и базы данных Web SQL.

Мы решили использовать localStorage
, так как он новый, потрясающий и прост в использовании. Он поддерживает сохранение базовых паров ключей/значений, что является всей нашей простой игрой. Вот простой пример того, как его использовать:
if (typeof localStorage == "object") {
localStorage.setItem("foo", "bar");
localStorage.getItem("foo"); // Value is "bar"
localStorage.removeItem("foo");
localStorage.getItem("foo"); // Value is now null
}
Есть некоторые "Gotchas", о которых нужно знать. Независимо от того, что вы проходите, значения хранятся как строки, что может привести к некоторым неожиданным результатам:
localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
// It's true!
}
// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)
// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}
Краткое содержание
HTML5 удивительно работать. Большинство реализаций обрабатывают все, что нуждается в разработчике игры, от графики до сохранения состояния игры. Несмотря на то, что существуют некоторые боли в росте (такие как <audio>
рядные проблемы), разработчики браузера движутся быстро и с вещами уже такими же великими, как они есть, будущее выглядит ярким для игр, построенных на HTML5.
