Разработка 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.



24.07.2013

Обнаружение столкновений и анимация спрайтов ― столпы всех компьютерных игр. Игра Snail Bait ("Средство от улиток"), которую мы создаем в этом цикле статей, не является исключением. На рисунке 1, в левом верхнем углу, показан бегун игры Snail Bait, который взрывается при столкновении с пчелой.

Рисунок 1. Обнаружение столкновений в действии
Столкновение бегуна с пчелой и последующий взрыв

Из этой статьи читатель узнает:

  • как обнаруживать столкновения;
  • как использовать контекст HTML5 Canvas для обнаружения столкновений;
  • как реализовать обнаружение столкновений в виде манипуляторов спрайтов;
  • как обрабатывать столкновения;
  • как реализовать анимации спрайтов, такие как взрывы.

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

Обнаружение столкновений ― это процесс из четырех этапов, один из которых ― собственно обнаружение столкновений:

  1. Перебрать все спрайты игры.
  2. Отбросить спрайты, которые не являются кандидатами на обнаружение столкновений.
  3. Выполнить обнаружение столкновений для спрайтов-кандидатов.
  4. Обработать столкновения.

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

Начнем с обзора методов обнаружения столкновений.

Методы обнаружения столкновений

Столкновение спрайтов можно обнаружить несколькими способами. Вот три наиболее популярных метода в порядке возрастания изощренности и сложности:

  • ограничивающие области (для 3D игр – ограничивающие объемы);
  • проведение лучей;
  • теорема о разделяющей оси.

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

Рисунок 2. Ограничивающие области: столкновение кругов
Иллюстрация принципов, лежащих в основе обнаружения столкновения кругов

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

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

Более надежный метод для небольших, быстро движущихся спрайтов — проведение лучей, как показано на рисунке 3. Метод проведения лучей обнаруживает пересечение векторов скорости двух спрайтов. В каждом из пяти кадров на рисунке 3 вектор скорости шарика ― это синяя диагональная линия, а вектор скорости ведра ― красная горизонтальная линия. (Ведро движется горизонтально). Шарик попадает в ведро, когда пересечение этих векторов лежит внутри ведра, как показано на правом снимке на рисунке 3.

Рисунок 3. Проведение лучей
Иллюстрация принципа обнаружения столкновений методом проведения лучей

Априорное или апостериорное обнаружение столкновений

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

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

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

Рисунок 4. Теорема о разделяющей оси.
Иллюстрация принципа обнаружения столкновений с использованием теоремы о разделяющей оси

В этой статье мы не будем подробно останавливаться на методах проведения лучей или теореме о разделяющей оси. О каждом из них можно прочесть в книге Canvas в HTML5 Core (Prentice Hall, 2012). (См. ссылку в разделе Ресурсы).


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

Обнаружение столкновений в Snail Bait выполняется для относительно крупных спрайтов, которые движутся медленно, так что столкновения можно обнаружить по ограничивающим прямоугольникам. Эти прямоугольники показаны на рисунке 5.

Рисунок 5. Обнаружение столкновений в Snail Bait по ограничивающим прямоугольникам
Обнаружение столкновений в Snail Bait по ограничивающим прямоугольникам

В Snail Bait действия спрайтов, такие как бег, прыжки и взрывы, реализуются как манипуляторы спрайтов — см. статью Реализация поведения спрайтов (developerWorks, январь 2013 г.). То же верно и для обнаружения столкновений. На этом этапе разработки Snail Bait у бегуна три вида манипуляторов: он бегает, прыгает и сталкивается с другими спрайтами. В листинге 1 показан экземпляр спрайта бегуна с этими тремя формами поведения.

Листинг 1. Элементы поведения (манипуляторы) бегуна
Sprite = function () {
   ...
   this.runner = new Sprite('runner',           // тип
                            this.runnerArtist,  // начертатель
                            [ this.runBehavior, // манипулятор
                              this.jumpBehavior,
                              this.collideBehavior
                            ]); 
};

В листинге 2 приведен код манипулятора столкновений бегуна collideBehavior.

Листинг 2. Манипулятор столкновений бегуна
var SnailBait =  function () {
   ...

   // Манипулятор столкновений бегуна ...............................................

   this.collideBehavior = {
      execute: function (sprite, time, fps, context) {  // sprite-это бегун
         var otherSprite;

         for (var i=0; i < snailBait.sprites.length; ++i) { 
            otherSprite = snailBait.sprites[i];

            if (this.isCandidateForCollision(sprite, otherSprite)) {
               if (this.didCollide(sprite, otherSprite, context)) { 
                  this.processCollision(sprite, otherSprite);
               }
            }
         }
      },
      ...

   };
};

Так как объект collideBehavior - это манипулятор спрайта, Snail Bait на каждом кадре анимации вызывает его метод execute(). А так как объект collideBehavior связан с бегуном, спрайт, который Snail Bait передает методу execute(), это всегда бегун. Подробнее см. в разделе Основы реализации поведения статьи «Реализация поведения спрайтов».

Метод execute() объекта collideBehavior содержит в себе четыре шага по обнаружению столкновений, перечисленных выше. Три последних шага реализованы следующими методами collideBehavior:

  • isCandidateForCollision(sprite, otherSprite)
  • didCollide(sprite, otherSprite, context)
  • processCollision(sprite, otherSprite)

В следующих разделах обсуждается реализация каждого из этих методов.


Выбор кандидатов на обнаружение столкновений

Спрайт Snail Bait может столкнуться со спрайтом бегуна, если:

  • этот спрайт ― не бегун;
  • и спрайт, и бегун видимы;
  • ни спрайт, ни бегун не взрываются.

Эту логику реализует метод isCandidateForCollision() объекта collideBehavior, показанный в листинге 3.

Листинг 3. Выбор кандидатов на обнаружение столкновений: isCandidateForCollision()
var SnailBait = function () { 
  ...
   isCandidateForCollision: function (sprite, otherSprite) {
      return sprite !== otherSprite &&                       // не тот же
             sprite.visible && otherSprite.visible &&        // видимый
             !sprite.exploding && !otherSprite.exploding;    // не взрывается
   }, 
   ...
};

Теперь посмотрим, как обнаружить столкновение между отобранными спрайтами.


Обнаружение столкновений бегуна с другим спрайтом

В листинге 4 показан метод didCollide() объекта collideBehavior, который определяет, столкнулся ли бегун с другим спрайтом.

Листинг 4. Проверка столкновений: didCollide()
var SnailBait =  function () {
   ...

   didCollide: function (sprite,      // бегун
                          otherSprite, // кандидат на столкновение
                          context) {   // для context.isPointInPath()
      var left = sprite.left + sprite.offset,
          right = sprite.left + sprite.offset + sprite.width,
          top = sprite.top,
          bottom = sprite.top + sprite.height,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;

      // Все предыдущие переменные ― left, right и т.д. ― относятся к спрайту бегуна.

      if (otherSprite.type !== 'snail bomb') {
         return this.didRunnerCollideWithOtherSprite(left, top, right, bottom,
                                                         centerX, centerY,
                                                         otherSprite, context);
      }
      else {
         return this.didSnailBombCollideWithRunner(left, top, right, bottom,
                                                       otherSprite, context);
      }
   },

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

Если другой спрайт ― не снаряд улитки, то didCollide() вызывает метод didRunnerCollideWithOtherSprite(), показанный в листинге 5.

Листинг 5. didRunnerCollideWithOtherSprite()
didRunnerCollideWithOtherSprite: function (left, top, right, bottom,
                                              centerX, centerY,
                                              otherSprite, context) {
   // Находится ли любой из четырёх углов бегуна или его центр
   // в пределах прямоугольника, ограничивающего другой спрайт? 

   context.beginPath();
   context.rect(otherSprite.left - otherSprite.offset, otherSprite.top,
                otherSprite.width, otherSprite.height);

   return context.isPointInPath(left,    top)     ||
          context.isPointInPath(right,   top)     ||

          context.isPointInPath(centerX, centerY) ||

          context.isPointInPath(left,    bottom)  ||
          context.isPointInPath(right,   bottom);
},

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

Контекст Canvas ― это не только графика

Метод isPointInPath() контекста Canvas определяет, находится ли точка на текущем пути. В Snail Bait это используется для определения того, находится ли точка внутри прямоугольника. Но isPointInPath() особенно удобен тогда, когда путь имеет неправильную форму. Определить вручную, лежит ли точка в пределах области неправильной формы, довольно трудно.

Чтобы определить, находится ли точка внутри прямоугольника, больших математических знаний не требуется; однако 2D-контекст элемента canvas HTML5 еще больше упрощает это благодаря методу isPointInPath(), который возвращает значение true, если точка находится в пределах текущего пути контекста холста.

Метод didRunnerCollideWithOtherSprite() с помощью вызовов beginPath() и rect() создает прямоугольный путь, который представляет собой ограничивающий прямоугольник другого спрайта. Впоследствии метод didRunnerCollideWithOtherSprite() вызывает isPointInPath(), чтобы определить, находится ли одна из пяти точек бегуна в пределах ограничивающего прямоугольника другого спрайта.

Метод didRunnerCollideWithOtherSprite() правильно идентифицирует столкновения бегуна со всеми другими спрайтами, за исключением снаряда улитки, как показано на рисунке 6.

Рисунок 6. Бегун и снаряд улитки
Бегун и снаряд улитки

Для снаряда он работает неправильно просто потому, что снаряд улитки достаточно мал, чтобы пройти через бегуна, не задев какой-либо из углов ограничивающего прямоугольника бегуна или его центр. Из-за этого неудачного соотношения размеров бегуна и снаряда, когда другой спрайт ― это снаряд, метод didCollide() из листинга 4 вызывает метод didSnailBombCollideWithRunner(), показанный в листинге 6.

Листинг 6. Метод didSnailBombCollideWithRunner()
didSnailBombCollideWithRunner: function (left, top, right, bottom,
                                             snailBomb, context) {
   // Определяет, находится ли центр снаряда улитки в пределах
   // ограничивающего прямоугольника бегуна  

   context.beginPath();
   context.rect(left, top, right - left, bottom - top); // runner's bounding box

   return context.isPointInPath(
                 snailBomb.left - snailBomb.offset + snailBomb.width/2,
                 snailBomb.top + snailBomb.height/2);
},

Метод didSnailBombCollideWithRunner() представляет собой инверсию метода didRunnerCollideWithOtherSprite(): если didRunnerCollideWithOtherSprite() проверяет, находятся ли точки бегуна в пределах другого спрайта, то didSnailBombCollideWithRunner() проверяет, находится ли центр другого спрайта (снаряда) в пределах бегуна.

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


Уточнение ограничивающих прямоугольников

Как видно из рисунка 5, ограничивающий прямоугольник для обнаружения столкновений заключает в себе спрайт, который он представляет. Однако вблизи углов эти ограничивающие прямоугольники часто бывают прозрачными. Так происходит со спрайтом бегуна, как показано на рисунке 7. Эти прозрачные области могут привести к ложным столкновениям, что особенно заметно в случае столкновения двух прозрачных областей.

Рисунок 7. Первоначальный ограничивающий прямоугольник бегуна
Первоначальный ограничивающий прямоугольник бегуна

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

Рисунок 8. Новый ограничивающий прямоугольник бегуна
Новый ограничивающий прямоугольник бегуна

Snail Bait уменьшает размер ограничивающего прямоугольника бегуна с помощью отредактированного метода didCollide(), показанного на рисунке 7.

Листинг 7. Модификация ограничивающего прямоугольника бегуна
var SnailBait =  function () {
...

didCollide: function (sprite,      // бегун
                       otherSprite, // кандидат на столкновение
                       context) {   // для context.isPointInPath()
      var MARGIN_TOP = 10,
          MARGIN_LEFT = 10,
          MARGIN_RIGHT = 10,
          MARGIN_BOTTOM = 0,
          left = sprite.left + sprite.offset + MARGIN_LEFT,
          right = sprite.left + sprite.offset + sprite.width - MARGIN_RIGHT,
          top = sprite.top + MARGIN_TOP,
          bottom = sprite.top + sprite.height - MARGIN_BOTTOM,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;
       ...
   },
   ...
};

Сокращение ограничивающего прямоугольника бегуна устраняет ложное обнаружение столкновений в игре Snail Bait. Теперь посмотрим, как сделать так, чтобы обнаружение столкновений лучше работало.


Пространственное разделение

Подробнее о пространственном разделении

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

К пространственному разделению относится разбиение пространства игры на ячейки, так что столкнуться могут только спрайты, находящиеся в одной и той же ячейке. Исключая обнаружение столкновений спрайтов, находящихся в разных ячейках, пространственное разделение часто приводит к существенному повышению быстродействия. Работа Snail Bait ускоряется благодаря разбиению пространства, как показано на рисунке 9.

Рисунок 9. Разделение пространства Snail Bait
Разделение пространства Snail Bait С бегуном могут столкнуться только те спрайты, которые находятся в левой секции (занимающей около одной десятой части экрана). Спрайты, находящиеся в большей, правой секции, не могут столкнуться с бегуном.

Как показано в листинге 8, Snail Bait исключает все спрайты, находящиеся в правой секции рисунка 9, из процесса обнаружения столкновений, что значительно сокращает количество операций обнаружения столкновений в игре.

Листинг 8. Уточнение логики отбора спрайтов для обнаружения столкновений
this.isCandidateForCollision: function (sprite, otherSprite) {
   return sprite !== otherSprite &&
          sprite.visible && otherSprite.visible &&
          !sprite.exploding && !otherSprite.exploding &&
          otherSprite.left - otherSprite.offset < sprite.left + sprite.width;
},

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


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

Когда столкновение обнаружено, с ним нужно что-то делать. Метод Snail Bait processCollision() обрабатывает столкновения бегуна с другими спрайтами, как показано в листинге 9.

Листинг 9. Обработка столкновений: processCollision()
var SnailBait =  function () {
   processCollision: function (sprite, otherSprite) {
      if ('coin'  === otherSprite.type    ||  // плохие
          'sapphire' === otherSprite.type ||
          'ruby' === otherSprite.type     ||
          'button' === otherSprite.type   ||
          'snail bomb' === otherSprite.type) {
         otherSprite.visible = false;
      }

      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);
      }
   },
   ...
};

Когда бегун сталкивается с "хорошими" — монетами, сапфирами, рубинами и кнопками — или со снарядом улитки, другой спрайт становится невидимым, так как его атрибут visible принимает значение false.

Когда бегун сталкивается с "плохими" — летучими мышами, пчелами, улитками и снарядом улитки, ― метод processCollision() взрывает бегуна с помощью метода explode() снаряда улитки. Пока метод explode() просто выводит на консоль BOOM. Об окончательной реализации метода explode() мы поговорим в следующей статье этого цикла.

Наконец, метод processPlatformCollisionDuringJump(), показанный в листинге 10, обрабатывает столкновения с платформами, когда бегун прыгает.

Листинг 10. Метод processPlatformCollisionDuringJump()
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 { // Столкновение с платформой при подпрыгивании
         sprite.fall();
      }
   }
};

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

Листинг 11. Временный метод для падения
var SnailBait =  function () {

   ...
   this.runner.fall = function () {
      snailBait.runner.track = 1;
      snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) -
                             snailBait.runner.height;
   };
   ...
};

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


Мониторинг производительности при обнаружении столкновений

90% простоя?

Верхняя запись таблицы, приведенной на рисунке 10,означает, что 90% времени Snail Bait просто чего-нибудь ждет. Такую замечательную производительность Snail Bait демонстрирует потому, что браузер Chrome (версии 26) — как и все современные браузеры — ускоряет работу элемента canvas. Обычно поставщики браузеров реализуют ускорение для элемента canvas путем передачи вызовов API Canvas в WebGL, так что вы получаете удобство API Canvas и производительности WebGL.

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

Ко всем современным браузерам прилагается мощная среда разработки; например, Chrome, Safari, Firefox, Internet Explorer и Opera - все они позволяют следить за характеристиками исполняемого кода. На рисунке 10 показан профилировщик Chrome, который показывает, какой процент времени занимают отдельные методы.

Рисунок 10. Производительность при обнаружении столкновений
Профилировщик Chrome отображает процент времени, занимаемый отдельными методами Snail Bait

На рисунке 10 видно, что метод didCollide() занимает всего 0,05% времени игры. (В столбце Self со значением 0,01% для метода didCollide() указано только время, потраченное самим методом, а не вызываемыми им методами.)

Когда бегун сталкивается с "плохими", он взрывается. Теперь давайте рассмотрим, как осуществить такой взрыв.


Анимация спрайтов

Рисунок 11, сверху вниз, иллюстрирует анимацию взрыва, который Snail Bait отображает, когда бегун сталкивается с кем-то плохим, таким как пчела.

Рисунок 11. Бегун, взрывающийся после столкновения
Бегун, взрывающийся после столкновения

Snail Bait реализует анимацию спрайта, как показано на рисунке 11, с помощью аниматоров спрайтов. Аниматор спрайта на определенное время заменяет ячейки, изображенные построителем спрайтов. Например, аниматор спрайта взрыва изменяет ячейки анимации бегуна, как показано на рисунке 12, на 500 мс.

Рисунок 12. Ячейки взрыва страницы спрайтов Snail Bait
Ячейки листа анимации, подставляемые аниматором спрайта взрыва

Конструктор объектов аниматоров спрайтов показан в листинге 12.

Листинг 12. Конструктор аниматоров спрайтов
// Аниматор спрайтов...........................................................

var SpriteAnimator = function (cells, duration, callback) {
   this.cells = cells;
   this.duration = duration || 1000;
   this.callback = callback;
};

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

Методы SpriteAnimator определены в прототипе объекта, как показано в листинге 13.

Листинг 13. Методы аниматоров спрайтов
SpriteAnimator.prototype = {
   start: function (sprite, reappear) {
      var originalCells = sprite.artist.cells,
          originalIndex = sprite.artist.cellIndex,
          self = this;

      sprite.artist.cells = this.cells;
      sprite.artist.cellIndex = 0;

      setTimeout(function() {
         sprite.artist.cells = originalCells;
         sprite.artist.cellIndex = originalIndex;

         sprite.visible = reappear;

         if (self.callback) {
            self.callback(sprite, self);
         }
      }, self.duration); 
   },
};

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

В листинге 14 показано, как Snail Bait использует аниматор спрайтов, чтобы бегун взорвался.

Листинг 14. Создание аниматора взрыва
var SnailBait =  function () {
   this.canvas = document.getElementById('game-canvas'),
   this.context = this.canvas.getContext('2d'),
   ...

   this.RUN_ANIMATION_RATE = 17,     // кадров в секунду
   this.EXPLOSION_CELLS_HEIGHT = 62, // пикселей
   this.EXPLOSION_DURATION = 500,    // миллисекунд

   this.explosionCells = [
      { left: 1,   top: 48, width: 50, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 60,  top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 143, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 230, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 305, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 389, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 470, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT }
   ],
   ...

   this.explosionAnimator = new SpriteAnimator(
      this.explosionCells,          // Ячейки листа анимации
      this.EXPLOSION_DURATION,      // Продолжительность взрыва

      function (sprite, animator) { // Обратный вызов после анимации
         sprite.exploding = false; 

         if (sprite.jumping) {
            sprite.stopJumping();
         }
         else if (sprite.falling) {
            sprite.stopFalling();
         }

         sprite.visible = true;
         sprite.track = 1;
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
         sprite.artist.cellIndex = 0;
         sprite.runAnimationRate = snailBait.RUN_ANIMATION_RATE;
      }
   );
};

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

В листинге 15 показан метод бегуна explode().

Листинг 15. Метод explode() Snail Bait
SnailBait.prototype = {
   ...

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

      sprite.exploding = true;

      this.explosionAnimator.start(sprite, true);  // true означает появление спрайта
   },
};

Когда бегун прыгает, он не анимируется, потому что его скорость анимации равна нулю. Метод explode() приводит скорость анимации к своему нормальному значению, и выполняется анимация с использованием ячеек взрыва. Затем метод explode() присваивает атрибуту exploding() бегуна значение true и запускает аниматор взрыва.


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

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


Загрузка

ОписаниеИмяРазмер
Пример кодаwa-html5-game8-code.zip1,2 MБ

Ресурсы

Научиться

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

  • Replica Island: загрузите открытый исходный код этого популярного платформера для Android. Большая часть графики Snail Bait взята из Replica Island (используется с разрешения).

Комментарии

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=938471
ArticleTitle=Разработка 2D-игр на HTML5: Обнаружение столкновений и анимация спрайтов
publish-date=07242013