Перейти к тексту

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

Профиль создается, когда вы в первый раз заходите в developerWorks. Выберите данные в своем профиле (имя, страна/регион, компания) которые будут общедоступными и будут отображаться, когда вы публикуете какую-либо информацию. Вы можете изменить данные вашего ИБМ аккаунта в любое время.

Вся введенная информация защищена.

  • Закрыть [x]

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

Вся введенная информация защищена.

  • Закрыть [x]

Разработка 2D-игр на HTML5: Постановка

Реализация объекта игры, приостановки, "замораживания", "оттаивания" и ввода команд с клавиатуры

Дэвид Гири, автор и докладчик, Clarity Training, Inc.
Дэвид Гири
Дэвид Гири (David Geary), автор книги Core HTML5 Canvas, является также сооснователем группы пользователей HTML5 Денвера и автором восьми книг по Java-программированию, в том числе бестселлеров по Swing и JavaServer Faces. Дэвид часто выступает на конференциях, в том числе JavaOne, Devoxx, Strange Loop, NDC и OSCON, и трижды заслужил титул "рок-звезды JavaOne". Для developerWorks он написал циклы статей JSF 2 fu и GWT fu. За новостями Дэвида можно следить в Twitter: @davidgeary.

Описание:  В предлагаемом цикле статей знаток HTML5 Дэвид Гири шаг за шагом демонстрирует процесс создания 2D-видеоигры на HTML5. В этой статье он показывает, как инкапсулировать код игры в объект, как реализовать паузу и возобновление и как использовать переходы CSS3 для реализации обратного отсчета времени при перезагрузке игры.

Больше статей из этой серии

Дата:  08.02.2013
Уровень сложности:  средний
Активность:  1104 просмотров
Комментарии:  


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

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

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

Я также покажу вам, как сделать паузу и "заморозить" Snail Bait, а впоследствии "разморозить" и перезапустить игру с анимированным обратным отсчетом времени. В конце статьи я вернусь к механике игры и покажу, как обрабатывать события клавиатуры для управления положением бегуна по вертикали.

Из этой статьи вы узнаете:

  • как инкапсулировать игровые функции в объект;
  • приостановить и возобновить игру;
  • приостановить игру, когда окно теряет фокус;
  • возобновить игру с анимированным обратным отсчетом времени, когда окно восстанавливает фокус;
  • временно отображать сообщения для пользователя (т.н тосты);
  • обрабатывать ввод с клавиатуры.

Попутно вы узнаете, как определять и создавать экземпляры объектов JavaScript, как пользоваться переходами CSS3 и как сочетать setTimeout() с этими переходами для реализации поэтапной анимации.

Объект игры

До сих пор мы реализовали функции игры Snail Bait вместе с несколькими ее переменными как глобальные переменные. Конечно, этого делать нельзя. Если вы недостаточно знаете о пороках глобальных переменных, обратитесь к разделу Ресурсы за аргументами от таких светил JavaScript, как Дуглас Крокфорд и Николас Закас.

Вместо глобальных переменных мы инкапсулируем все функции и переменные Snail Bait в объект. Этот объект состоит из двух частей, как показано в листингах 1 и 2. (За полным кодом примеров для этой статьи обращайтесь к разделу Загрузки.)

Листинг 1 содержит функцию конструктора игры, которая определяет атрибуты объекта.


Листинг 1. Конструктор игры (частичный листинг)
var SnailBait = function (canvasId) {
   this.canvas  = document.getElementById(canvasId);
   this.context = this.canvas.getContext('2d');

   // Элементы HTML 

   this.toast = document.getElementById('toast'),
   this.fpsElement = document.getElementById('fps');

   // Константы

   this.LEFT = 1;
   this.RIGHT = 2;
   ...

   // В остальной части этой функции определены многие другие атрибуты 
};

Листинг 2 представляет собой прототип игры, определяющий методы объекта.


Листинг 2. Прототип игры (частичный листинг)
SnailBait.prototype = {
   // Методы draw() и drawRunner()  // обсуждаются во второй статье этого цикла.

  draw function (now) {
 this.setPlatformVelocity();
 this.setOffsets();
 this.drawBackground(); this.drawRunner();
 this.drawPlatforms(); 
},

  drawRunner: function () {
 this.context.drawImage(this.runnerImage,
 this.STARTING_RUNNER_LEFT,
 this.calculatePlatformTop(this.runnerTrack) - this.RUNNER_HEIGHT);
 },
 ...
 // В остальной части этого объекта определены многие другие методы  
};  

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


Таблица 1. Методы Snail Bait на конечной стадии разработки (перечислены в порядке их вызова)
МетодОписание
initializeImages()Инициализирует изображений игры. Обработчик событий onload фонового изображения вызывает функцию start().
start()Начинает игру, вызывая метод requestAnimationFrame(), который вызывает метод animate(), когда приходит время нарисовать первый кадр анимации.
splashToast() [1]Отображает временное сообщение для игрока.
animate() [2]Если игра не приостановлена, этот метод рисует следующий кадр анимации и вызывает метод requestNextAnimationFrame() для планирования другого вызова animate(). Если игра приостановлена, animate() ждет 200 мс и вызывает метод requestNextAnimationFrame().
calculateFps()Вычисляет частоту кадров на основе времени, прошедшего с момента последнего кадра анимации.
draw()Рисует следующий кадр анимации.
setTranslationOffsets()Устанавливает новое значение смещения для фона и платформ.
setBackgroundTranslationOffset().Задает смещение фона в зависимости от текущего времени.
setPlatformTranslationOffset()Задает смещение платформ в зависимости от текущего времени.
setPlatformVelocity()Устанавливает скорость платформ, большую чем скорость фона, для достижения эффекта мягкого параллакса.
drawBackground()Переводит систему координат холста, дважды рисует фон и возвращает систему координат в исходное положение.
drawRunner() [3]Рисует бегуна с помощью drawImage()
drawPlatforms() [3]Рисует прямоугольные платформы с помощью методов 2D-контекста strokeRect() и fillRect().
calculatePlatformTop()Вычисляет координату Y верхней платформы с учетом дорожки (платформы перемещаются по одной из трех горизонтальных дорожек).
turnLeft()Прокрутка фона и платформ вправо.
turnRight()Прокрутка фона и платформ влево.
togglePaused() [1]Приостановка игры.

[1] Вводится в этой статье
[2] Вызывается браузером
[3] Будет заменен в следующей статье этого цикла

Функции и методы

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

Большинство методов — которые до сих пор были просто функциями, — перечисленных в таблице 1, я представил в двух предыдущих статьях этого цикла. В этой статье мы обсудим два новых метода: togglePaused() и splashToast(), помимо изменения других методов, таких как animate().

Код JavaScript из листинга 1 и листинга 2 определяет функцию и прототип, но не создает экземпляр объекта SnailBait. Мы сделаем это в следующем разделе.


Начало игры

Глобальные объекты SnailBait

Как иллюстрируют листинг 1 и листинг 3, в Snail Bait есть только два глобальных объекта: функция snailBait и объект snailBait.

В листинге 3 показан код JavaScript, который начинает игру. В начале листинга показана реализация трех методов SnailBait: animate(), start() и initializeImages().


Листинг 3. Начало

SnailBait.prototype = {
   ...

   // Переменная  this в методе animate() указывает на 
   // объект window, поэтому метод заменяет ее на snailBait 

   animate: function (now) { 
      snailBait.fps = snailBait.calculateFps(now); 
      snailBait.draw(now);

      requestNextAnimationFrame(snailBait.animate);
   },

   start: function () {
      this.turnRight();                     // Приводит все в движение
      this.splashToast('Good Luck!', 2000); // Надпись Good Luck (Удачи!) отображается 
                                                 // на 2 секунды

      requestNextAnimationFrame(this.animate);
   },

   initializeImages: function () {
      this.background.src = 'images/background_level_one_dark_red.png';
      this.runnerImage.src = 'images/runner.png';

      this.background.onload = function (e) {

         // ...переменная this указывает на объект window,
         // поэтому данная функция заменяет ее на snailBait.

         snailBait.start();
      };
   },
}; 
// Конец SnailBait.prototype


// Запуск игры

var snailBait = new SnailBait(); 
// Примечание. По соглашению, ссылка на объект
// начинается со строчной, а
// имя функции ― с прописной.

snailBait.initializeImages();

Привередливый объект JavaScript this

Те, кто уже работал с классическим объектно-ориентированным языком, таким как Java, ожидают, что переменная объекта this всегда указывает на соответствующий объект метода.

Один из самых острых углов JavaScript ― это тот факт, что переменная this может меняться. В листинге 2 переменные this в методе animate() и обработчик событий onload фонового изображения ссылаются на объект window, а не на объект snailBait, поэтому эти методы обращаются непосредственно к объекту snailBait.

JavaScript в листинге 3 создает объект SnailBait и вызывает его метод initializeImages(), который устанавливает обработчик событий фонового изображения onload. Когда изображение загружено, этот обработчик событий вызывает метод start().

Метод start() вызывает метод turnRight(), который приводит фон и платформы в движение. Он также вызывает метод splashToast(), который на две секунды отображает надпись Good Luck! (Удачи!). Наконец, метод start() вызывает полифилл requestNextAnimationFrame() ― который обсуждался во второй статье этого цикла (см. раздел Полифилл requestAnimationFrame() той статьи) ― который в конечном итоге вызывает метод игры animate().

Метод animate() рисует текущий кадр, а затем для поддержания анимации вызывает requestNextAnimationFrame() ― указывая себя в качестве функции обратного вызова.

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


Приостановка игры

Игры HTML5 — особенно видеоигры — должны иметь состояние паузы. В листинге 4 я изменил игровой цикл Snail Bait, чтобы приостанавливать и возобновлять игру.


Листинг 4. Приостановка и возобновление игры
var SnailBait = function (canvasId) {
   ...
   this.paused = false,
   this.PAUSED_CHECK_INTERVAL = 200; // миллисекунды
   ...
};

SnailBait.prototype = {
   animate: function (now) { 
      if (snailBait.paused) {

         // Снова проверка миллисекунд в snailBait.PAUSED_CHECK_INTERVAL

         setTimeout( function () {

            requestNextAnimationFrame(snailBait.animate);

         }, snailBait.PAUSED_CHECK_INTERVAL);
      }
      else {

         // Игровой цикл из листинга 1

         snailBait.fps = snailBait.calculateFps(now); 
         snailBait.draw(now);
         requestNextAnimationFrame(snailBait.animate);
      }
   },

   togglePaused: function () {
      this.paused = !this.paused;
   },
};

Метод togglePaused() просто переключает переменную игры paused. Значение true этой переменной означает, что игра приостановлена, и метод animate() не выполняет игровой цикл.

Чтобы возобновить приостановленную игру, не обязательно — и не эффективно — проверять это 60 раз в секунду (если частота кадров составляет 60 кадров/с); поэтому метод animate() в листинге 4 ждет 200 мс, прежде чем вызвать полифилл requestNextAnimationFrame(), который планирует еще один вызов animate(), когда приходит время рисовать следующий кадр анимации.

Автоматическая приостановка игры, когда окно теряет фокус

В спецификации W3C Управление временем в анимации на основе сценариев сказано об анимации с помощью requestAnimationFrame() буквально следующее:

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

Термин резко замедлить означает, что браузер делает обратный вызов анимации с очень низкой частотой, обычно где-то между 1 и 10 кадров/с, как показано на рисунке 1, на котором видно, что сразу после того как окно обретает фокус частота составляет 6 кадров/с.


Рисунок 1. Snail Bait после потери и восстановления фокуса
Снимок экрана Snail Bait после восстановления фокуса

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


Листинг 5. Автопауза
window.onblur = function () { // окно теряет фокус
   if (!snailBait.paused) {
      snailBait.togglePaused();
   }
};

window.onfocus = function () { // окно восстанавливает фокус
   if (snailBait.paused) {
      snailBait.togglePaused();
   }
};

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


Замораживание игры

Приостановка игры включает в себя не только прекращение анимации. Игру нужно будет возобновить точно с того же места, где она прервалась. В листинге 4 это требование как будто выполняется; ведь пока игра приостановлена, ничего не происходит, поэтому, казалось бы, что игра должна возобновиться с того же места, где была приостановлена. Однако это не так, потому что основной валютой всех анимаций — включая Snail Bait — является время.

Как говорилось во второй статье (см. раздел requestAnimationFrame()), requestAnimationFrame() передает время указанной функции обратного вызова; в случае Snail Bait это обратный вызов метода animate(), который в свою очередь передает время методу draw().

Когда игра приостановлена, анимация не запускается, но время-то по-прежнему течет. А так как метод Snail Bait draw() рисует следующий кадр анимации, основываясь на времени, которое он получает от animate(), при возобновлении приостановленной игры реализация togglePaused() в листинге 4 вызовет скачок игры вперед.

В листинге 6 показано, как Snail Bait обходит резкий сдвиг времени при возобновлении приостановленной игры.


Листинг 6. Замораживание игры
var SnailBait = function (canvasId) {
   ...
   this.paused = false,
   this.pauseStartTime = 0,
   this.totalTimePaused = 0,
   this.lastAnimationFrameTime = 0,
   ...
};

SnailBait.prototype = {
   ...
   calculateFps: function (now) {
      var fps = 1000 / (now - this.lastAnimationFrameTime);
      this.lastAnimationFrameTime = now;

      if (now - this.lastFpsUpdateTime > 1000) {
         this.lastFpsUpdateTime = now;
         this.fpsElement.innerHTML = fps.toFixed(0) + 'fps';

      }

      return fps; 
   },

   togglePaused: function () {
      var now = +new Date();

      this.paused = !this.paused;

      if (this.paused) {
         this.pauseStartTime = now;
      }
      else {
         this.lastAnimationFrameTime += (now - this.pauseStartTime);
      }
   },
};

В листинге 6 я изменил методы Snail Bait togglePaused() и calculateFps() с учетом времени, истекшего с момента приостановки игры.

Для вычисления частоты кадров предыдущего кадра анимации мы вычитаем время прорисовки последнего кадра из текущего времени и делим 1000 на это значение, что дает частоту кадров в секундах вместо миллисекунд. (См. раздел второй статьи Вычисление скорости анимации в кадрах/с.)

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


"Оттаивание" игры при восстановлении фокуса

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

Тосты

Тост ― это сообщение, которое во время игры временно отображается игроку, такое как Good Luck! на рисунке 2.


Рисунок 2. Тосты
Снимок экрана тоста Good Luck! в игре Snail Bait

Как и сама игра, тосты Snail Bait реализуются с помощью комбинации HTML, CSS и JavaScript, как видно из следующих трех листингов.

В листинге 7 показан код HTML тоста.


Листинг 7. Тосты: HTML
<!DOCTYPE html>
<html>
   <head>
      ...
   </head>

   <body>
      <div id='wrapper'>
         <!-- Toast...................................................-->

         <div id='toast'></div>
         ...

      </div>
      ...
  </body>
</html>

В листинге 8 приведена таблица CSS для реализации тоста Good Luck! в игре Snail Bait.


Листинг 8. Тосты: CSS
#toast {
   position: absolute;
   ...

   -webkit-transition: opacity 0.5s;
   -moz-transition: opacity 0.5s;
   -o-transition: opacity 0.5s;
   transition: opacity 0.5s;

   opacity: 0;
   z-index: 1;
   display: none;
}

В листинге 9 показан код JavaScript тоста Good Luck!.


Листинг 9. Тосты: JavaScript
var SnailBait =  function () {
   ...
   this.toast = document.getElementById('toast'),
   this.DEFAULT_TOAST_TIME = 3000, // 3 секунды
   ...
};

SnailBait.prototype = {
   ...
   start: function () {
      ...
      snailBait.splashToast('Good Luck!');
   },

   splashToast: function (text, howLong) {
      howLong = howLong || this.DEFAULT_TOAST_TIME;

      toast.style.display = 'block';
      toast.innerHTML = text;

      setTimeout( function (e) {
         toast.style.opacity = 1.0; // После отображения тоста
      }, 50);

      setTimeout( function (e) {
         toast.style.opacity = 0; // Начало перехода CSS3

         setTimeout( function (e) {
            toast.style.display = 'none'; // Непосредственно перед окончанием 
                                 // анимации CSS3
         }, 480);
      }, howLong);
   },
   ...
}

Тосты, как они реализованы в предыдущих трех листингах, - это просто элементы DIV, см. листинг 7. Интереснее листинг 8, где приведены CSS DIV. Положение элемента DIV абсолютно, что означает, что он может появляться над или под другими элементами DIV, а не до или после них. К тому же элемент DIV toast имеет значение z-index, равное 1, что означает, что он всегда отображается поверх холста игры, значение z-index которого по умолчанию равно 0. Наконец, CSS элемента toast определяет переход в 0,5 с, привязанный к свойству opacity; при изменении этого свойства CSS плавно анимирует DIV с предыдущего значения непрозрачности к новому значению за 0,5 секунды.

Еще интереснее метод splashToast(), приведенный в листинге 9, который отображает тост в течение заданного времени. Когда Snail Bait вызывает splashToast() с временем отображения по умолчанию 3 секунды, тост плавно появляется за 0,5 секунды, отображается в течение 2,5 с, а затем исчезает за 0,5 секунды. Вот как это работает.

Метод splashToast() начинает с установки атрибута display элемента DIV toast в значение block, что обычно делает DIVвидимым; однако так как его атрибут opacity изначально равен 0, элемент DIV toast остается невидимым. Затем splashToast() задает текст внутреннего HTML-кода элемента DIV toast, который передается методу, но параметр непрозрачности остается, поэтому заданный текст тоже не делает DIV видимым.

Чтобы сделать элемент DIV toast видимым, я устанавливаю его непрозрачность равной 1,0. Это запускает анимацию CSS3 благодаря переходу, приведенному в листинге 8, но только если позднее (в данном случае через 50 мс) будет задан параметр непрозрачности с помощью странно выглядящего метода setTimeout(), в который он заключен. И вот почему.

Переходы CSS3 можно указывать только для свойств элементов с промежуточными состояниями. Например, если изменить непрозрачность с 0,2 на 0,3 (выберем два случайных числа), нужно пройти промежуточные значения 0,21, 0,22 и т.д.

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

Полупрозрачные элементы DIV и события

После предыдущего обсуждения splashToast() вы можете спросить, зачем вообще этому методу манипулировать свойством display элемента DIV toast? Почему бы просто не манипулировать непрозрачностью DIV, делая его видимым или невидимым? Дело в том, что если вы не делаете это специально, то оставленные невидимыми элементы DIV ― плохая идея, потому что они могут проявить свое присутствие другими, неожиданными способами, например, перехватывая события.

Так как CSS3 игнорирует переход непрозрачности, то если splashToast() установит свойства display и opacity элемента DIV toast одновременно, то этот метод установит значение непрозрачности 1.0 после установки свойства display ― точнее, примерно через 50 мс.

Наконец, по истечении необходимого времени отображения splashToast() сбрасывает свойство opacity элементов DIV toast в 0, что снова вызывает анимацию CSS3 на 0,5 с. Спустя две секунды после начала анимации CSS3 метод splashToast() сбрасывает свойство display в 0.

Размораживание Snail Bait

Когда игра Snail Bait возобновляется после приостановки, она дает игрокам время на подготовку в виде трехсекундного обратного отсчета времени, как показано на рисунке 3.


Рисунок 3. Обратный отсчет при оттаивании
Снимок отсчета времени Snail Bait

В листинге 10 показан соответствующий код JavaScript.


Листинг 10. Обратный отсчет времени: JavaScript

var SnailBait = function (canvasId) {
   ...
   this.toast = document.getElementById('toast'),
};
window.onblur = function (e) {  // Приостановка, если возобновлена
   if (!snailBait.paused) {
      snailBait.togglePaused();
   }
};

window.onfocus = function (e) {  // Возобновление, если приостановлена
   var originalFont = snailBait.toast.style.fontSize;

   if (snailBait.paused) {
      snailBait.toast.style.font = '128px fantasy';

      snailBait.splashToast('3', 500); // Отображение цифры 3 на полсекунды

      setTimeout(function (e) {
         snailBait.splashToast('2', 500); // Отображение цифры 2 на полсекунды

         setTimeout(function (e) {
            snailBait.splashToast('1', 500); // Отображение цифры 1 на полсекунды

            setTimeout(function (e) {
               snailBait.togglePaused();

               setTimeout(function (e) { // Ожидание единицы, чтобы исчезнуть
                  snailBait.toast.style.fontSize = originalFont;
               }, 2000);
            }, 1000);
         }, 1000);
      }, 1000);
   }
};

Когда окно Snail Bait восстанавливает фокус, начинается обратный отсчет времени с помощью метода splashToast(). Каждая цифра появляется за 0,5 секунды, а затем исчезает за 0,5 секунды. Когда отсчет достигает нуля, обработчик onfocus перезапускает игру.

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


Листинг 11. Учет потери фокуса во время обратного отсчета
var SnailBait = function (canvasId) {
   ...
   this.windowHasFocus = true,
   ...
};
...

SnailBait.prototype = {
   ...

   splashToast: function (text, howLong) {
      howLong = howLong || this.DEFAULT_TOAST_TIME;

      toast.style.display = 'block';
      toast.innerHTML = text;

      setTimeout( function (e) {
         if (snailBait.windowHasFocus) {
            toast.style.opacity = 1.0; // После отображения тоста
         }
      }, 50);

      setTimeout( function (e) {
         if (snailBait.windowHasFocus) {
            toast.style.opacity = 0; // Начало перехода CSS3
         }

         setTimeout( function (e) { 
            if (snailBait.windowHasFocus) {
               toast.style.display = 'none'; 
            }
         }, 480);
      }, howLong);
   },
   ...
};
...

window.onblur = function (e) {  // Приостановка, если возобновлена
   snailBait.windowHasFocus = false;

   if (!snailBait.paused) {
      snailBait.togglePaused();
   }
};

window.onfocus = function (e) {  // Возобновление, если приостановлена
   var originalFont = snailBait.toast.style.fontSize;

   snailBait.windowHasFocus = true;

   if (snailBait.paused) {
      snailBait.toast.style.font = '128px fantasy';

      snailBait.splashToast('3', 500); // Отображение цифры 3 на полсекунды

      setTimeout(function (e) {
         snailBait.splashToast('2', 500); // Отображение цифры 2 на полсекунды

         setTimeout(function (e) {
            snailBait.splashToast('1', 500); // Отображение цифры 1 на полсекунды

            setTimeout(function (e) {
               if ( snailBait.windowHasFocus) {
                  snailBait.togglePaused();
               }

               setTimeout(function (e) { // Ожидание единицы, чтобы исчезнуть
                  snailBait.toast.style.fontSize = originalFont;
               }, 2000);
            }, 1000);
         }, 1000);
      }, 1000);
   }
};


Ввод с клавиатуры

Для управления бегуном в Snail Bait используется клавиатура, поэтому я закончу эту статью кратким обсуждением того, как игра обрабатывает ввод с клавиатуры. Клавиши d и k перемещают бегуна влево и вправо, а клавиши j и f заставляют его подпрыгивать и спрыгивать соответственно. На рисунке 4 показан бегун после прыжка на платформу третьей дорожки.


Рисунок 4. Бег после прыжка между дорожками
Бегун после прыжка между дорожками

Прослушиватели событий клавиатуры можно добавлять только к фокусируемым HTML-элементам. Элемент canvas не фокусируемый, так что Snail Bait добавляет обработчик событий onkeydown к объекту window, как показано в листинге 12.


Листинг 12. Реагирование на ввод с клавиатуры
var runnerTrack = 1,
    BACKGROUND_VELOCITY = 42;

function turnLeft() {
   bgVelocity = -BACKGROUND_VELOCITY;
}

function turnRight() {
   bgVelocity = BACKGROUND_VELOCITY;
}

window.onkeydown = function (e) {
   var key = e.keyCode;

   if (key === 80 || (paused && key !== 80)) {  // p
      togglePaused();
   }

   if (key === 68 || key === 37) { // d или стрелка влево
      turnLeft();
   }
   else if (key === 75 || key === 39) { // k или стрелка вправо
      turnRight();
   }
   else if (key === 74) { // j
      if (runnerTrack === 3) {
         return;
      }
      runnerTrack++;
   }
   else if (key === 70) { // f
      if (runnerTrack === 1) {
         return;
      }
      runnerTrack--;
   }
};

Важно понимать, что игровой цикл Snail Bait работает постоянно. Функция animate() непрерывно вызывается браузером, когда тот готов нарисовать следующий кадр анимации, а animate(), в свою очередь, непрерывно вызывает функцию draw() (как указано в листинге 2).

Поскольку игровой цикл работает постоянно, обработчик событий клавиатуры просто устанавливает переменные игры. Например, при нажатии клавиши k, чтобы переместить бегуна вправо, обработчик событий устанавливает значение bgVelocity равным BACKGROUND_VELOCITY = 42 (пикселей в секунду), а при нажатии клавиши d для перемещения бегуна влево обработчик событий устанавливает bgVelocity равным 42 пикселя в секунду. Эти параметры вступают в силу не раньше, чем игра нарисует следующий кадр анимации.


В следующей статье

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



Загрузка

ОписаниеИмяРазмерМетод загрузки
Пример кодаj-html5-game3.zip3,9 МБHTTP

Информация о методах загрузки


Ресурсы

Об авторе

Дэвид Гири

Дэвид Гири (David Geary), автор книги Core HTML5 Canvas, является также сооснователем группы пользователей HTML5 Денвера и автором восьми книг по Java-программированию, в том числе бестселлеров по Swing и JavaServer Faces. Дэвид часто выступает на конференциях, в том числе JavaOne, Devoxx, Strange Loop, NDC и OSCON, и трижды заслужил титул "рок-звезды JavaOne". Для developerWorks он написал циклы статей JSF 2 fu и GWT fu. За новостями Дэвида можно следить в Twitter: @davidgeary.

Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Спасибо. Эта запись была помечена для модератора.


Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Сообщение о нарушении не было отправлено. Попробуйте, пожалуйста, позже.


developerWorks: вход


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


При первом входе в developerWorks для Вас будет создан профиль. Выберите информацию отображаемую в Вашем профиле — скрыть или отобразить поля можно в любой момент.

Выберите ваше отображаемое имя

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

(Должно содержать от 3 до 31 символа.)


Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Оценить эту статью

Комментарии

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Web-архитектура
ArticleID=857633
ArticleTitle=Разработка 2D-игр на HTML5: Постановка
publish-date=02082013