Разработка 2D-игр на HTML5: Реализация гравитации и добавление звука

Анимация реалистичных падений; добавление музыки и звуковых эффектов

В этом цикле статей знаток HTML5 Дэвид Джири шаг за шагом демонстрирует процесс создания 2D-видеоигры на HTML5. В этой статье вы завершите проработку механических аспектов игры Snail Bait — вы узнаете, как реализовать влияние гравитации при падении бегуна. Затем, вы увидите, как осуществляется реализация звука — в том числе музыкального сопровождения и звуковых эффектов.

Дэвид Джири, президент, Clarity Training, Inc.

David GearyАвтор, лектор и консультант Дэвид Джири является президентом компании Clarity Training, Inc., Он обучает разработчиков создавать Web-приложения с использованием JSF и Google Web Toolkit (GWT). Он участвовал в экспертных группах JSTL 1.0 и JSF 1.0/2.0, был соавтором сертификационного экзамена Web Developer Certification Exam компании Sun, а также принимал участие в проектах с открытым кодом, в том числе Apache Struts и Apache Shale. Книга Дэвида Graphic Java Swing стала одной из самых продаваемых книг о Java, а Core JSF (в соавторстве с Кэем Хорстманом) - одна из самых продаваемых книг о JSF. Дэвид регулярно выступает на конференциях и встречах пользовательских групп. Он является регулярным участником конференций NFJS с 2003 года, проводил курсы в университете Java и дважды удостаивался звания JavaOne rock star.



27.11.2013

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

Падение

В игре Snail Bait бегун падает в том случае, если забегает за край платформы или сталкивается с платформой снизу (см. рис. 1).

Рисунок 1. Падение с края платформы
Screen capture of Snail Bait with the runner falling off the edge of a platform

Кроме того, бегун падает в том случае, если промахивается мимо платформы в конце фазы снижения при прыжке (см. рис. 2).

Рисунок 2. Падение в конце прыжка
Screen capture of Snail Bait with the runner falling at the end of a jump

Падение бегуна реализуется посредством объекта fall behavior (манипулятора падения). В листинге 1 показана реализация спрайта бегуна, специфицирующая массив "манипулятор" (behavior).

Листинг 1. Манипуляторы бегуна
Sprite = function () {
   ...
   this.runner = new Sprite('runner',           // type
                            this.runnerArtist,  // artist
                            [ this.runBehavior, // behaviors
                              this.jumpBehavior,
                              this.collideBehavior,
                              this.fallBehavior,
                            ]); 
};

Игра Snail Bait вызывает манипулятор падения бегуна — как и все другие манипуляторы спрайта — в каждом кадре анимации, в котором спрайт является видимым. Большую часть времени манипулятор падения ничего не делает. Пока атрибут falling бегуна имеет значение true, манипулятор падения инкрементно перемещает бегуна в вертикальном направлении на каждом кадре анимации, в результате чего создается впечатление его падения. Значение этому атрибуту присваивает метод fall() бегуна (см. Листинг 2).

Листинг 2. Метод fall() бегуна
SnailBait.prototype = {
   ...
   equipRunner: function () {
      ...
      this.equipRunnerForJumping();
      this.equipRunnerForFalling();
   },

   equipRunnerForFalling: function () {
      ...

      this.runner.fallAnimationTimer = new AnimationTimer();

      this.runner.fall = function (initialVelocity) {
         // set attributes for the runner's fall behavior and
         // start the fall animation timer

         this.velocityY = initialVelocity || 0;
         this.initialVelocityY = initialVelocity || 0;
         this.falling = true;

         this.fallAnimationTimer.start();
      }
   },
   ...
};

Горизонтальная скорость бегуна

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

При запуске игры Snail Bait вызывает свой метод equipRunner(), который, помимо прочего, обеспечивает возможности для прыжков и падений бегуна. Метод equipRunnerForFalling() содержит реализацию метода fall() бегуна. Метод fall() задает начальную скорость бегуна, присваивает его атрибуту falling значение true и запускает таймер анимации для отслеживания времени, прошедшего с начала падения бегуна.

Когда метод fall() бегуна присваивает атрибуту falling бегуна значение true, он переключает триггер в манипуляторе падения бегуна. После этого бегун падает до тех пор, пока игра не присвоит атрибуту бегуна falling значение false. Остальная часть рассмотрения гравитации в рамках данной статьи посвящена реализации этого манипулятора.


Реализация гравитации

Гравитация как частный случай

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

У поверхности Земли падающие объекты приобретают под действием гравитации ускорение 9,81 метров в секунду за секунду (м/с/c), то есть за каждую секунду скорость падающего объекта увеличивается почти на 10 м/с. С точки зрения разработчика игр реализация гравитации означает, что в дополнение к вычислению положений спрайтов исходя из их скорости необходимо также вычислять скорости спрайтов, когда они падают.

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

  • В начале игры:
    1. Определить ширину игры в пикселях.
    2. Задать ширину игровой зоны в метрах произвольным образом.
    3. Разделить ширину в пикселях на ширину в метрах для получения отношения "пиксели/метры"
  • Затем для каждого кадра анимации:
    1. На основе ускорения свободного падения (9,81 м/с/c) вычислить скорость в метрах в секунду (м/с).
    2. Умножить скорость в м/с на отношение пиксели/метры (вычисленное на шаге 3) для получения размерности "пиксели в секунду".
    3. Вычислить положение на основе значения скорости, выраженной в пикселях в секунду.

Теперь можно преобразовать приведенный только что алгоритм в программный код. Первый шаг состоит в задании для игры силы тяжести и отношения пиксели/метры (см. листинг 3).

Листинг 3. Константы, определяющие силу тяжести и скорость падения
var SnailBait = { // SnailBait constructor function
   ...
  
   this.GRAVITY_FORCE = 9.81,
   this.PIXELS_PER_METER = this.canvas.width / 10; // 10 meters, randomly selected width
   ...
}

Когда бегун забегает за конец платформы или сталкивается с платформой снизу, он начинает падать с нулевой вертикальной скоростью. Однако если бегун промахивается мимо платформы в конце прыжка, он начинает падение с вертикальной скоростью, которую он имел в конце фазы снижения этого прыжка. В листинге 4 показано, как манипулятор прыжка бегуна использует константы GRAVITY_FORCE и PIXELS_PER_METER (заданные в Листинге 3) для вычисления этой начальной скорости.

Листинг 4. Падение в конце прыжка
this.jumpBehavior = {
   ...

   finishDescent: function (sprite) {
      sprite.stopJumping();

      if (snailBait.isOverPlatform(sprite) !== -1) {
         sprite.top = sprite.verticalLaunchPosition;
      }
      else {
         // The argument passed to the sprite's fall() method
         // is the sprite's initial velocity when it begins to fall
   
         sprite.fall(snailBait.GRAVITY_FORCE *
                     (sprite.descendAnimationTimer.getElapsedTime()/1000) *
                     snailBait.PIXELS_PER_METER);
      }
   },
};

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

(9.81 m/s/s) * (время снижения в секундах)* (800 пикселей / 10 m)

Результат этого вычисления имеет размерность пиксели в секунду и представляет собой вертикальную скорость бегуна в конце фазы снижения его прыжка (остальную часть реализации манипулятора прыжка можно посмотреть в шестой статье данного цикла).

Другое место, где игра Snail Bait вычисляет скорость бегуна с использованием констант GRAVITY_FORCE и PIXELS_PER_METER, — это манипулятор падения бегуна (см. листинг 5).

Листинг 5. Задание скорости спрайта и вычисление вертикальной скорости бегуна для текущего кадра
this.fallBehavior = {
   ...
   setSpriteVelocity: function (sprite) {
      sprite.velocityY = sprite.initialVelocityY + 

                         snailBait.GRAVITY_FORCE *
                         (sprite.fallAnimationTimer.getElapsedTime()/1000) *
                         snailBait.PIXELS_PER_METER;
   },

   calculateVerticalDrop: function (sprite, fps) {
      return sprite.velocityY / fps;
   },
};

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

Метод calculateVerticalDrop() использует движение, зависящее от времени — которое я рассматриваю в разделе Движение на основе времени второй статьи этого цикла — для вычисления вертикального падения бегуна на основе скорости, которая была вычислена методом setSpriteVelocity() и текущей частоты кадров.

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

Листинг 6. Метод execute() манипулятор падения
this.fallBehavior = {
      execute: function (sprite, time, fps) { // sprite is the runner
         var deltaY;

         if (sprite.jumping) {
            return;
         }

         if (this.isOutOfPlay(sprite) || sprite.exploding) {
            if (sprite.falling) {
               sprite.stopFalling();
            }
            return;
         }
         
         if (!sprite.falling) {
            if (!sprite.exploding && !this.isPlatformUnderneath(sprite)) {
               sprite.fall();
            }
            return;

         }

         this.setSpriteVelocity(sprite);
         deltaY = this.calculateVerticalDrop(sprite, fps);
               
         if (!this.willFallBelowCurrentTrack(sprite, deltaY)) {
            sprite.top += deltaY;
         }
         else { // will fall below current track
            if (this.isPlatformUnderneath(sprite)) {
               this.fallOnPlatform(sprite);
               sprite.stopFalling();
            }
            else {
               sprite.top += deltaY;
               sprite.track--;
            }
         }
      }
   },

Когда бегун прыгает, выпадает из игры или взрывается, метод execute() манипулятора падения инициирует или останавливает падение, а затем возвращает управление. Если бегун выпадает из игры или взрывается при падении, то метод execute() вызывает метод stopFalling(). Если в настоящий момент времени бегун не падает, не взрывается и не имеет под собой платформы, то метод execute() осуществляет вызов метода fall() бегуна.

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

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

Листинг 7. Выявление выпадения бегуна из игры или падения бегуна ниже его текущей дорожки
this.fallBehavior = {
   isOutOfPlay: function (sprite) {
      return sprite.top > snailBait.TRACK_1_BASELINE;
   },

   willFallBelowCurrentTrack: function (sprite, deltaY) {
      return sprite.top + sprite.height + deltaY >
             snailBait.calculatePlatformTop(sprite.track);
   },
   ...
};

Вспомогательные методы в листинге 8 определяют, находится ли под бегуном какая-либо платформа, и приземляют бегуна на такую платформу.

Листинг 8. Определение наличия платформы под бегуном и приземление бегуна на платформу
this.fallBehavior = {
   isPlatformUnderneath: function (sprite) {
      return snailBait.isOverPlatform(sprite) !== -1;
   },

   fallOnPlatform: function (sprite) {
      sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
      sprite.stopFalling();
   },
   ...
};

Необходимо осознавать, что метод execute() манипулятора падения перемещает бегуна по вертикали только в том случае, когда его атрибут falling имеет значение true. Метод бегуна stopFalling() присваивает этому атрибуту значение false (см. листинг 9), присваивает вертикальной скорости бегуна значение 0 и останавливает таймер анимации падения бегуна:

Листинг 9. Метод stopFalling() манипулятора падения
SnailBait.prototype = {
   ...

   equipRunnerForFalling: function () {
      ...

      this.runner.stopFalling = function () {
         this.falling = false;
         this.velocityY = 0;
         this.fallAnimationTimer.stop();
      }
   },
   ...
};

Приостановка при падении бегуна

Как было описано в разделе Приостановка манипуляторов седьмой статьи данного цикла, в манипуляторах должны быть реализованы методы pause() и unpause(), которые позволяют приостанавливать и возобновлять манипуляторы в соответствии с игрой в целом. Манипулятор падения бегуна соответствует этому требованию (см. листинг 10).

Листинг 10. Приостановка и возобновление манипулятора падения
 var SnailBait = function () {
   ...

   this.fallBehavior = {
      pause: function (sprite) {
         sprite.fallAnimationTimer.pause();
      },

      unpause: function (sprite) {
         sprite.fallAnimationTimer.unpause();
      },
   }
   ...
}

Манипулятор падения отслеживает прошедшее время падения с помощью таймера анимации падения бегуна. На основании этой величины методы pause() и unpause() этого манипулятора просто приостанавливают или вновь запускают этот таймер.

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


Управление звуковыми эффектами и музыкой

Игра Snail Bait может одновременно воспроизводить музыкальное сопровождение и генерировать звуковые эффекты. В нижнем левом углу рисунка 3 расположены флажки Sound (звук) и Music (музыка), которые позволяют пользователю включать в игре звуковые эффекты и/или музыку.

Рисунок 3. Органы управления звуком и музыкой
Screen capture of Snail Bait that shows the check boxes used to control sound and music

В листинге 11 показан HTML-код для флажков.

Листинг 11. Флажки Sound и Music игры Snail Bait
<div id='sound-and-music'>
   <div class='checkbox-div'>
      Sound <input id='sound-checkbox' type='checkbox' checked/>
   </div>
          
   <div class='checkbox-div'>
      Music <input id='music-checkbox' type='checkbox'/>
   </div>
</div>

Для управления первоначальной установкой флажка можно использовать атрибут checked флажка (без значения). Если этот атрибут имеется, то флажок исходно присутствует, в противном случае, он отсутствует (см. рис. 3 и листинг 11).

В листинге 12 игра Snail Bait программным способом обращается к элементам флажков и поддерживает две переменные — soundOn и musicOn — которые синхронизируются с флажками посредством обработчиков событий. Кроме того, обработчик событий флажка Music воспроизводит или приостанавливает музыкальное сопровождение игры, которое (в отличие от звуковых эффектов) непрерывно воспроизводится в фоновом режиме.

Листинг 12. Обработчики событий флажков Sound и Music
var SnailBait = function () {
   ...
   this.soundCheckbox = document.getElementById('sound-checkbox');
   this.musicCheckbox = document.getElementById('music-checkbox');

   this.soundOn = this.soundCheckbox.checked;
   this.musicOn = this.musicCheckbox.checked;
   ... 
};
...

snailBait.soundCheckbox.onchange = function (e) {
   snailBait.soundOn = snailBait.soundCheckbox.checked;
};

snailBait.musicCheckbox.onchange = function (e) {
   snailBait.musicOn = snailBait.musicCheckbox.checked;

   if (snailBait.musicOn) {
      snailBait.soundtrack.play();
   }
   else {
      snailBait.soundtrack.pause();
   }
};

Метод startGame() игры Snail Bait воспроизводит музыку при установленном флажке Music (см. листинг 13).

Листинг 13. Запуск игры
SnailBait.prototype = {
SnailBait.prototype = {
   ...

   startGame: function () {
      if (this.musicOn) {
         this.soundtrack.play();
      }
      requestNextAnimationFrame(this.animate);
   },
   ...

Кроме того, я изменил метод togglePaused() игры таким образом, чтобы приостанавливать и возобновлять воспроизведение музыки в зависимости от того, приостановлена ли сама игра (см. листинг 14).

Листинг 14. Приостановка воспроизведения музыки
SnailBait.prototype = {
   ...

   togglePaused: function () {
      ...

      if (this.paused && this.musicOn) {
         this.soundtrack.pause();
      }
      else if ( ! this.paused && this.musicOn) {
         this.soundtrack.play();
      }
   },
};

Реализация звуковых эффектов

Игра Snail Bait генерирует звуковые эффекты в различные моменты игры, — например, когда бегун сталкивается с пчелой или с летучей мышью, — и иногда должна воспроизводить несколько звуков одновременно. Для реализации звуковых эффектов в игре Snail Bait используются HTML5-элементы audio.

HTML-элементы audio

Snail Bait создает элемент audio для каждого из своих звуков (см. листинг 15).

Листинг 15. Элементы audio игры Snail Bait
<!DOCTYPE html>
<html>
   <head>
      <title>Snail Bait</title>
      <link rel='stylesheet' href='game.css'/>
   </head>
 
   <body>
      <audio id='soundtrack'>
        <source src='sounds/soundtrack.mp3' type='audio/mp3'>
        <source src='sounds/soundtrack.ogg' type='audio/ogg'>
      </audio>

      <audio id='plop-sound' >
        <source src='sounds/plop.mp3' type='audio/mp3'>
        <source src='sounds/plop.ogg' type='audio/ogg'>
      </audio>

      <audio id='chimes-sound' >
        <source src='sounds/chimes.mp3' type='audio/mp3'>
        <source src='sounds/chimes.ogg' type='audio/ogg'>
      </audio>

      <audio id='whistle-down-sound' >
        <source src='sounds/whistledown.mp3' type='audio/mp3'>
        <source src='sounds/whistledown.ogg' type='audio/ogg'>
      </audio>

      <audio id='thud-sound' >
        <source src='sounds/thud.mp3' type='audio/mp3'>
        <source src='sounds/thud.ogg' type='audio/ogg'>
      </audio>

      <audio id='jump-sound' >
        <source src='sounds/jump.mp3' type='audio/mp3'>
        <source src='sounds/jump.ogg' type='audio/ogg'>
      </audio>

      <audio id='coin-sound' >
        <source src='sounds/coin.mp3' type='audio/mp3'>
        <source src='sounds/coin.ogg' type='audio/ogg'>
      </audio>

      <audio id='explosion-sound' >
        <source src='sounds/explosion.mp3' type='audio/mp3'>
        <source src='sounds/explosion.ogg' type='audio/ogg'>
      </audio>
      ...

  </body>
</html>

Внутри каждого элемента audio я определяю два звуковых файла в разных форматах; браузер выбирает тот из этих форматов, который он способен воспроизводить. Форматы MP3 и OGG охватывают все варианты современных браузеров; в разделе Ресурсы приведены ссылки на дополнительную информацию о форматах HTML5 audio.

Кроме того, Snail Bait обращается к каждому элементу audio в JavaScript-коде посредством метода getElementById() в объекте document (см. Листинг 16).

Листинг 16. Обращение к элементам audio игры Snail Bait в JavaScript-коде
SnailBait = function () {
   ...

   this.coinSound           = document.getElementById('coin-sound'),
   this.chimesSound         = document.getElementById('chimes-sound'),
   this.explosionSound      = document.getElementById('explosion-sound'),
   this.fallingWhistleSound = document.getElementById('whistle-down-sound'),
   this.plopSound           = document.getElementById('plop-sound'),
   this.jumpWhistleSound    = document.getElementById('jump-sound'),
   this.soundtrack          = document.getElementById('soundtrack'),
   this.thudSound           = document.getElementById('thud-sound'),
   ...

};

Уровень громкости

Для каждого воспроизводимого звука игра Snail Bait определяет уровень громкости в диапазоне от 0.0 (нет звука) до 1.0 (максимальная громкость). В листинге 17 показаны константы, заданные в игре Snail Bait для разных звуков (я подобрал их опытным путем).

Листинг 17. Задание уровней громкости для звуков игры Snail Bait
SnailBait = function () {
   // Sound-related constants

   this.COIN_VOLUME            = 1.0,
   this.CHIMES_VOLUME          = 1.0,
   this.EXPLOSION_VOLUME       = 0.25,
   this.FALLING_WHISTLE_VOLUME = 0.10,
   this.JUMP_WHISTLE_VOLUME    = 0.05,
   this.PLOP_VOLUME            = 0.20,
   this.SOUNDTRACK_VOLUME      = 0.12,
   this.THUD_VOLUME            = 0.20,
   ...

};

При запуске игры Snail Bait производится инициализация элементов audio с использованием ссылок на эти элементы и констант, представляющих уровни громкости (см. листинг 18).

Листинг 18. Задание уровней громкости для звуков Snail Bat
SnailBait.prototype = {
   ...

   initializeSounds: function () {
      this.coinSound.volume           = this.COIN_VOLUME;
      this.chimesSound.volume         = this.CHIMES_VOLUME;
      this.explosionSound.volume      = this.EXPLOSION_VOLUME;
      this.fallingWhistleSound.volume = this.FALLING_WHISTLE_VOLUME;
      this.plopSound.volume           = this.PLOP_VOLUME;
      this.jumpWhistleSound.volume    = this.JUMP_WHISTLE_VOLUME;
      this.soundtrack.volume          = this.SOUNDTRACK_VOLUME;
      this.thudSound.volume           = this.LANDING_VOLUME;
   },

   start: function () {
      this.createSprites();
      this.initializeImages();
      this.initializeSounds();
      this.equipRunner();
      this.splashToast('Good Luck!');
      ...
   },
   ...   
   
};

Одновременное воспроизведение нескольких звуков

HTML5-элементы audio имеют простой API-интерфейс, в том числе следующие методы, которые используются в игре Snail Bat для воспроизведения звуков:

  • play()
  • pause()
  • load()

Кроме того, в игре Snail Bait используются следующие атрибуты элемента audio:

  • currentTime
  • ended

В листинге 19 можно увидеть использование всех предыдущих методов и атрибутов (за исключением метода pause(), который используется в листинге 12 и в листинге 14):

Листинг 19. Воспроизведение звуков с помощью аудиотреков Snail Bat
SnailBait = function () {
   ...
   this.soundOn = true,

   this.audioTracks = [ // 8 tracks is more than enough
      new Audio(), new Audio(), new Audio(), new Audio(), 
      new Audio(), new Audio(), new Audio(), new Audio()
   ],
   ...

   // Playing sounds.......................................................

   soundIsPlaying: function (sound) {
      return !sound.ended && sound.currentTime > 0;
   },

   playSound: function (sound) {
      var track, index;

      if (this.soundOn) {
         if (!this.soundIsPlaying(sound)) {
            sound.play();
         }
         else {
            for (i=0; index < this.audioTracks.length; ++index) {
               track = this.audioTracks[index];
            
               if (!this.soundIsPlaying(track)) {
                  track.src = sound.currentSrc;
                  track.load();
                  track.volume = sound.volume;
                  track.play();

                  break;
               }
            }
         }              
      }
   },
   ...
};

Чтобы воспроизводить несколько звуков одновременно, игра Snail Bait создает массив из восьми элементов audio. Метод playSound() игры Snail Bait выполняет обход этого массива и активизирует первый элемент audio, который в настоящий момент не занят воспроизведением звука.

Необходимо понимать, что игра Snail Bait никогда не воспроизводит звуковые эффекты с помощью исходных элементовaudio специфицированных в HTML-коде в листинге 15. Вместо этого игра воспроизводит звуковые эффекты посредством восьми элементов audio, которые она создает программным способом в листинге 19. Игра загружает звук из исходного элемента audio в программно созданный элемент, а затем воспроизводит этот программно созданный элемент.


Воспроизведение звуковых эффектов в игре Snail Bat

В листингах 20 - 23 показаны фрагменты кода, которые воспроизводят звуковые эффекты игры Snail Bat в различных ее точках. Я уже описал весь код этих листингов в предшествующих статьях данного цикла, поэтому сейчас не буду повторять эти описания. Для контекста я оставил достаточную часть логики, окружающей вызовы playSound().

Когда бегун прыгает, игра Snail Bat играет свистящий звук (см. листинг 20).

Листинг 20. Звук, производимый бегуном при прыжке
var SnailBait = function () {
   ...

   this.equipRunnerForJumping: function() {
       ...
      this.runner.jump = function () {
         if (this.jumping) // 'this' is the runner
            return;

         this.runAnimationRate = 0;
         this.jumping = true;
         this.verticalLaunchPosition = this.top;
         this.ascendAnimationTimer.start();

         snailBait.playSound(snailBait.jumpWhistleSound);
      };
   },

При взрыве спрайта игра Snail Bait воспроизводит звук explosionSound (см. листинг 21).

Листинг 21. Взрывы
SnailBait.prototype = {
   ...

   explode: function (sprite, silent) {
      if (sprite.runAnimationRate === 0) {
         sprite.runAnimationRate = this.RUN_ANIMATION_RATE;
      }
               
      sprite.exploding = true;

      this.playSound(this.explosionSound);
      this.explosionAnimator.start(sprite, true);  // true means sprite reappears
   },
   ...
};

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

Листинг 22. Звуки, ассоциированные с падениями
SnailBait = function () {
   ...

   this.fallBehavior = {
      ...

      fallOnPlatform: function (sprite) {
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
         sprite.stopFalling();
         snailBait.playSound(snailBait.thudSound);
      },

      execute: function (sprite, time, fps) {
         var deltaY;

         if (!this.willFallBelowCurrentTrack(sprite, deltaY)) {
            sprite.top += deltaY;
         }
         else { // will fall below current track
            if (this.isPlatformUnderneath(sprite)) {
               this.fallOnPlatform(sprite);
               sprite.stopFalling();
            }
            else {
               sprite.track--;

               sprite.top += deltaY;

               if (sprite.track === 0) {
                  snailBait.playSound(snailBait.fallingWhistleSound);
               }
            }
         }
         ...
      }
   };
   ...
};

При столкновениях спрайтов воспроизводятся различные звуки в зависимости от того, какие спрайты вовлечены в это столкновение (см. листинг 23).

Листинг 23. Звуки столкновения
var SnailBait =  function () {
   ...

   this.collideBehavior = {
      ...

      processCollision: function (sprite, otherSprite) {
         if (otherSprite.value) { // Modify Snail Bait sprites so they have values
            // Keep score...
         }

         if ('coin'  === otherSprite.type    ||
             'sapphire' === otherSprite.type ||
             'ruby' === otherSprite.type     || 
             'button' === otherSprite.type   ||
             'snail bomb' === otherSprite.type) {
            otherSprite.visible = false;

            if ('coin' === otherSprite.type) {
               snailBait.playSound(snailBait.coinSound);
            }
            if ('sapphire' === otherSprite.type || 'ruby' === otherSprite.type) {
               snailBait.playSound(snailBait.chimesSound);
            }
         }

         if ('bat' === otherSprite.type   ||
             'bee' === otherSprite.type   ||
             'snail' === otherSprite.type || 
             'snail bomb' === otherSprite.type) {
            snailBait.explode(sprite);
         }

         if (sprite.jumping && 'platform' === otherSprite.type) {
            this.processPlatformCollisionDuringJump(sprite, otherSprite);
         }
      },
   },

   processPlatformCollisionDuringJump: function (sprite, platform) {
      var isDescending = sprite.descendAnimationTimer.isRunning();

      sprite.stopJumping();

      if (isDescending) {
         sprite.track = platform.track; 
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
      }
      else { // Collided with platform while ascending
         snailBait.playSound(snailBait.plopSound);
         sprite.fall(); 
      }
   },
   ...
};

В следующей статье данного цикла

В следующей статье этого цикла я завершу цикл Разработка 2D-игр на HTML5. В частности, я опишу итоговый геймплей и добавлю завершающие штрихи, такие как переходы между жизнями бегуна и анимация в конце игры.


Загрузка

ОписаниеИмяРазмер
Учебный программный кодcode.zip11.9 МБ

Ресурсы

Научиться

  • Оригинал статьи: HTML5 2D game development: Implement gravity and add sound.
  • Форматы HTML5 Audio: официальная документация.
  • Core HTML5 Canvas: (David Geary, Prentice Hall, 2012): в книге Дэвида Джири приведен подробный обзор API Canvas и методов разработки игр. Посетите также сопутствующий веб-сайт и блог.
  • Snail Bait: игра Snail Bait работает в онлайновом режиме в любом браузере с поддержкой HTML5 (лучше всего подойдет Chrome версии 18 и выше).
  • Mind-blowing apps with HTML5 Canvas (Умопомрачительные приложения с применением HTML5 Canvas): выступление Дэвида Джири на конференции Strange Loop 2011.
  • HTML5 Game Development (Разработка HTML5-игр): выступление Дэвида Гири на конференции норвежских разработчиков NDC-2011.
  • Platform games статья в Википедии об играх-платформерах.
  • Side-scroller video games статья в Википедии об играх-сайдскроллерах.
  • Strategy статья в Википедии об этом шаблоне проектирования.
  • Основы HTML5: изучите основы HTML5 в рамках учебной программы developerWorks.
  • Раздел для веб-разработчиков на ресурсе developerWorks. статьи по различным веб-решениям. Читайте также материалы по веб-разработке в технической библиотеке: обширный ассортимент технических статей, рекомендаций, руководств, учебных пособий и стандартов, а также руководств серии IBM Redbook.

Получить продукты и технологии

  • Replica Island: загрузите открытый исходный код этого популярного платформера для Android. Большая часть спрайтов Snail Bait взята из Replica Island (используется с разрешения).
  • Загрузите ознакомительные версии программных продуктов IBM и приобретите опыт работы с инструментами разработки и продуктами связующего уровня семейств DB2®, Lotus®, Rational®, Tivoli® и WebSphere®.

Обсудить

  • Присоединяйтесь к сообществу developerWorks. Связывайтесь с другими пользователями developerWorks и знакомьтесь с ориентированными на разработчиков форумами, блогами, группами и вики-ресурсами.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


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


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

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

 


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

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

Выберите имя, которое будет отображаться на экране



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

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

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

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

 


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


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