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

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

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

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

  • Закрыть [x]

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

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

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

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

  • Закрыть [x]

Разработка 2D-игр на HTML5: Спрайты

Реализация персонажей Snail Bait

Дэвид Гири, автор и докладчик, 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. Спрайты – графические объекты, которым можно назначить поведение, - одно из основополагающих и наиболее важных аспектов компьютерных игр. Эта статья посвящена реализации спрайтов, в виде которых выполнены персонажи Snail Bait.

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

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


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


Рисунок 1. Персонажи Snail Bait
Снимок экрана Snail Bait's sprites

Каждый персонаж в Snail Bait — это спрайт. Спрайты представляют собой графические объекты, которых можно наделить поведением; например бегун может бегать, прыгать, падать и сталкиваются с другими спрайтами в игре, тогда как рубины и сапфиры сверкают, подпрыгивают и исчезают, когда сталкиваются с бегуном.

Происхождение термина "спрайт"

Впервые термин спрайт использовал для анимированных персонажей один из разработчиков процессора видеодисплея 9918A компании Texas Instruments. (В английском языке слово sprite — происходящее от латинского spiritus — означает эльфа или фею). Спрайты реализовывались как программно, так и аппаратно; в 1985 году игра Commodore Amiga поддерживала до восьми аппаратных спрайтов.

Так как спрайты ― один из основополагающих аспектов любой игры, и в играх, как правило, бывает много спрайтов, имеет смысл инкапсулировать их основные возможности в многократно используемые объекты. Из этой статьи вы узнаете:

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

(За полным кодом примеров для этой статьи обращайтесь к разделу Загрузки.)

Объекты спрайтов

Я реализовал спрайты Snail Bait как объекты JavaScript, которые можно использовать в любой игре, так что спрайты находятся в отдельном файле. Я включил этот файл в HTML-код Snail Bait следующим образом: <script src='js/sprites.js'></script>.

В таблице 1 перечислены атрибуты объекта Sprite.


Таблица 1. Атрибуты объекта Sprite
АтрибутОписание
artist Начертатель - объект, который рисует спрайт.
behaviors Манипуляторы - массив манипуляторов, каждый из которых управляет поведением своего спрайта тем или иным способом.
Left Координата X верхнего левого угла спрайта.
top Координата Y верхнего левого угла спрайта.
width Ширина спрайта в пикселях.
height Высота спрайта в пикселях.
opacity Степень прозрачности спрайта.
type Строка, указывающая тип спрайта, например bat (летучая мышь), bee (пчела) или runner (бегун).
velocityX Скорость спрайта по горизонтали в пикселях в секунду.
velocityY Скорость спрайта по вертикали в пикселях в секунду.
visible Видимость спрайта. При значении false спрайт не рисуется.

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

Рисованием и поведением спрайтов занимаются другие объекты, называемые соответственно начертателами (artists) и манипуляторами (behaviors).

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


Листинг 1. Конструктор Sprite

var Sprite = function (type, artist, behaviors) { // конструктор
  this.type = type || '';
  this.artist = artist || undefined;
  this.behaviors = behaviors || [];

  this.left = 0;
  this.top = 0; this.width = 10;  // Нечто отличное от нуля, что не имеет смысла
  this.height = 10;  // Нечто отличное от нуля, что не имеет смысла
  this.velocityX = 0;
  this.velocityY = 0;
  this.opacity = 1.0;
  this.visible = true;

  return this;
};  

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

Сигнатуры методов Sprite обеспечивают разделение ответственности между представлением и поведением: метод draw() использует контекст Canvas, чтобы нарисовать спрайт, тогда как метод update() занимается только обновлением состояния спрайта в зависимости от текущего времени и частоты кадров анимации. Манипуляторы не должны рисовать, а начертатели не должны манипулировать состоянием спрайта.

Все аргументы конструктора в листинге 1 необязательны. Если не указать поведение, конструктор создает пустой массив, а если создать спрайт без указания типа, его типом будет пустая строка. Если не указать начертатель, он будет просто не определён.

Помимо атрибутов, спрайты имеют два метода, перечисленные в таблице 2.


Таблица 2. Методы объекта Sprite
МетодОписание
draw(context)Вызывает метод draw() начертателя спрайта, если спрайт видим и имеет свой начертатель.
update(time, fps) Вызывает метод update() для каждого манипулятора спрайта.

Реализация методов, перечисленных в таблице 2, показана в листинге 2.


Листинг 2. Реализация методов объекта Sprite


Sprite.prototype = { // Методы
  draw: function (context) {
  context.save();
  
  // Вызовы save() и restore() делают настройку globalAlpha временной

  context.globalAlpha = this.opacity;

  if (this.artist && this.visible) {
  this.artist.draw(this, context);
  }

  context.restore();
},

update: function (time, fps) {
  for (var i=0; i < this.behaviors.length; ++i) {
    if (this.behaviors[i] === undefined) { // Изменен во время цикла?
      return;
    }

    this.behaviors[i].execute(this, time, fps);
  }
 }
};
  

Скорость спрайтов: указывается в пикселях в секунду

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

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

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


Начертатели спрайтов и страницы спрайтов

Начертатели спрайтов можно реализовать одним из трех способов:

  • контурный начертатель: изображает графические элементы, такие как линии, дуги и кривые;
  • начертатель растрового изображения: создает изображение с помощью метода drawImage() 2D-контекста;
  • начертатель на основе страницы спрайтов: переносит изображение со страницы спрайтов (также методом drawImage()).

Независимо от типа начертателей, все они, как видно из листинга 2, должны отвечать единственному требованию: быть объектами, которые реализуют метод draw(), принимающий в качестве аргументов спрайт и 2D-контекст Canvas.

Рассмотрим каждый тип начертателя с перерывом на изучение страниц спрайтов.

Контурный начертатель

Канонической реализации контурного начертателя не существует; его реализуют согласно обстоятельствам с использованием графических возможностей 2D-контекста Canvas. В листинге 3 показана реализация такого начертателя, который рисует спрайты платформ Snail Bait.


Листинг 3. Контурный начертатель


// Контурные начертатели вырисовывают двухмерные графические элементы Canvas

var SnailBait =  function (canvasId) { // конструктор
   ...

   this.platformArtist = {
      draw: function (sprite, context) {
         var top;

         context.save();

         top = snailBait.calculatePlatformTop(sprite.track);

         // Вызовы save() и restore() делают следующие настройки временными

         context.lineWidth = snailBait.PLATFORM_STROKE_WIDTH;
         context.strokeStyle = snailBait.PLATFORM_STROKE_STYLE;
         context.fillStyle = sprite.fillStyle;

         context.strokeRect(sprite.left, top, sprite.width, sprite.height);
         context.fillRect  (sprite.left, top, sprite.width, sprite.height);

         context.restore();
      }
   },
};

Как видно на рисунке 1, платформы ― это просто прямоугольники. Начертатель платформы, приведенный в листинге 3, рисует их с помощью методов strokeRect() и fillRect() 2D-контекста Canvas. Эти методы подробно описаны во второй статье цикла (см. ее раздел Обзор Canvas HTML5). Расположение и размер результирующего прямоугольника определяется ограничивающим прямоугольником спрайта платформы.

Начертатели растровых изображений

В отличие от контурных начертателей, начертатели растровых изображений имеют каноническую реализацию, показанную в листинге 4.


Листинг 4. Начертатель растровых изображений


// ImageArtists создает растровое изображение

var ImageArtist = function (imageUrl) { // конструктор
   this.image = new Image();
   this.image.src = imageUrl;
};

ImageArtist.prototype = { // Методы
   draw: function (sprite, context) {
      context.drawImage(this.image, sprite.left, sprite.top);
   }
};

Начертателю растровых изображений передается URL-адрес изображения, и его метод draw() создает все изображение в том месте, где находится соответствующий спрайт.

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

Страницы спрайтов

Один из наиболее эффективных способов гарантировать, что Web-сайт быстро загрузится, ― предельно уменьшить число HTTP-запросов. В большинстве игр используется много изображений, и если для каждого из них делать отдельные HTTP-запросы, время запуска заметно пострадает. По этой причине разработчики игр HTML5 создают одно большое полотно, содержащее все изображения игры. Это полотно называется страницей спрайтов (sprite sheet). Страница спрайтов Snail Bait показана на рисунке 2.


Рисунок 2. Страница спрайтов Snail Bait
Снимок экрана страницы спрайтов Snail Bait

При наличии страницы спрайтов нужен способ перенесения прямоугольных фрагментов этой страницы на холст. К счастью, 2D-контекст Canvas позволяет легко это сделать с помощью метода drawImage(). Этот метод и используют начертатели по странице спрайтов.

Начертатели на основе страницы спрайтов

Реализация начертателей на основе страницы спрайтов показана в листинге 5.


Листинг 5. Начертатели на основе страницы спрайтов


// Начертатели на основе страницы спрайтов создают растровое изображение, 
// взятое со страницы спрайтов

SpriteSheetArtist = function (spritesheet, cells) { // конструктор
   this.cells = cells;
   this.spritesheet = spritesheet;
   this.cellIndex = 0;
};

SpriteSheetArtist.prototype = { // Методы
   advance: function () {
      if (this.cellIndex == this.cells.length-1) {
         this.cellIndex = 0;
      }
      else {
         this.cellIndex++;
      }
   },

   draw: function (sprite, context) {
      var cell = this.cells[this.cellIndex];

      context.drawImage(this.spritesheet,
               cell.left,   cell.top,     // исходное значение x, исходное значение y
               cell.width,  cell.height,  // исходная ширина, исходная высота
               sprite.left, sprite.top,   // конечное значение x, конечное значение y
               cell.width,  cell.height); // конечная ширина, конечная высота
   }
};

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

Начертатель на основе страницы спрайтов ведет указатель своих ячеек. Метод draw() страницы спрайтов использует этот индекс для обращения к текущей ячейке, а затем версию с девятью аргументами метода drawImage() 2D-контекста Canvas, чтобы нарисовать содержимое этой ячейки на холсте в месте расположения спрайта.

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

Как видно из листинга 5, реализовать начертатель на основе страницы спрайтов нетрудно. Он прост в применении; достаточно создать экземпляр начертателя на основе страницы спрайтов и ячейки, чтобы потом по мере необходимости вызывать методы advance() и draw(). Самая каверзная часть работы ― определение ячеек.

Определение ячеек страницы спрайтов

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


Листинг 6. Определение ячеек страницы спрайтов Snail Bait
var BAT_CELLS_HEIGHT = 34,

    BEE_CELLS_WIDTH  = 50,
    BEE_CELLS_HEIGHT = 50,
    ...

    SNAIL_CELLS_WIDTH = 64,
    SNAIL_CELLS_HEIGHT = 34,

    ...

    // Ячейки страницы спрайтов................................................

    batCells = [
       { left: 1,   top: 0, width: 32, height: BAT_CELLS_HEIGHT },
       { left: 38,  top: 0, width: 46, height: BAT_CELLS_HEIGHT },
       { left: 90,  top: 0, width: 32, height: BAT_CELLS_HEIGHT },
       { left: 129, top: 0, width: 46, height: BAT_CELLS_HEIGHT },
    ],

    beeCells = [
       { left: 5,   top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT },
       { left: 75,  top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT },
       { left: 145, top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT }
    ],
    ...

    snailCells = [
       { left: 142, top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT },
       { left: 75,  top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT },
       { left: 2,   top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT },
    ];

Определение ограничивающих прямоугольников ― утомительная задача, поэтому стоит потратить время на создание инструмента, который поможет это сделать. На рисунке 3 показан такой инструмент, работающий в онлайне на сайте Core HTML Canvas (см. раздел Ресурсы).


Рисунок 3. Простой инспектор страницы спрайтов
Простой инспектор страницы спрайтов

Инструментарий разработчика игр

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

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

Зная, как реализовать спрайты и их начертатели, можно посмотреть, как Snail Bait создает и инициализирует свои спрайты.


Создание и инициализация спрайтов Snail Bait

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


Листинг 7. Определение массивов спрайтов в конструкторе игры
var SnailBait = function (canvasId) { // конструктор
   ...

   this.bats         = [],
   this.bees         = [], 
   this.buttons      = [],
   this.coins        = [],
   this.platforms    = [],
   this.rubies       = [],
   this.sapphires    = [],
   this.snails       = [],

   this.runner = new Sprite('runner', this.runnerArtist);

   this.sprites = [ this.runner ]; // Позже добавятся другие спрайты
   ...
};

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

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

В начале игры Snail Bait (в числе других вещей) вызывает метод createSprites(), как показано в листинге 8.


Листинг 8. Начало игры
SnailBait.prototype = { // Методы
   ...
   start: function () {
      this.createSprites();
      this.initializeImages();
      this.equipRunner();
      this.splashToast('Good Luck!');
   },
};

Метод createSprites(), который создает все спрайты игры, за исключением бегуна, показан в листинге 9.


Листинг 9. Создание и инициализация спрайтов Snail Bait
SnailBait.prototype = { // Методы
   ...
   createSprites: function() {  
      this.createPlatformSprites();

      this.createBatSprites();
      this.createBeeSprites();
      this.createButtonSprites();
      this.createCoinSprites();
      this.createRubySprites();
      this.createSapphireSprites();
      this.createSnailSprites();

      this.initializeSprites();

      this.addSpritesToSpriteArray();
   },

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


Листинг 10. Создание отдельных спрайтов
SnailBait.prototype = { // Методы
  ...
  createBatSprites: function () {
    var bat,  batArtist = new SpriteSheetArtist(this.spritesheet, this.batCells),
   redEyeBatArtist = new SpriteSheetArtist(this.spritesheet, this.batRedEyeCells);

    for (var i = 0; i < this.batData.length; ++i) {
      if (i % 2 === 0) bat = new Sprite('bat', batArtist);
      else             bat = new Sprite('bat', redEyeBatArtist);

      bat.width  = this.BAT_CELLS_WIDTH;
      bat.height = this.BAT_CELLS_HEIGHT;

      this.bats.push(bat);
    }
  },

  createBeeSprites: function () {
    var bee, beeArtist = new SpriteSheetArtist(this.spritesheet, this.beeCells);

    for (var i = 0; i < this.beeData.length; ++i) {
      bee = new Sprite('bee', beeArtist);
      bee.width  = this.BEE_CELLS_WIDTH;
      bee.height = this.BEE_CELLS_HEIGHT;

      this.bees.push(bee);
    }
  },

  createButtonSprites: function () {
    var button, buttonArtist = new SpriteSheetArtist(this.spritesheet, this.buttonCells),

    goldButtonArtist = new SpriteSheetArtist(this.spritesheet, this.goldButtonCells);

    for (var i = 0; i < this.buttonData.length; ++i) {
      if (i === this.buttonData.length - 1) {
         button = new Sprite('button', goldButtonArtist);
      }
      else {
         button = new Sprite('button', buttonArtist);
      }

      button.width  = this.BUTTON_CELLS_WIDTH;
      button.height = this.BUTTON_CELLS_HEIGHT;

      button.velocityX = this.BUTTON_PACE_VELOCITY;
      button.direction = this.RIGHT;

      this.buttons.push(button);
    }
  },

  createCoinSprites: function () {
    var coin, coinArtist = new SpriteSheetArtist(this.spritesheet, this.coinCells);

    for (var i = 0; i < this.coinData.length; ++i) {
      coin        = new Sprite('coin', coinArtist);
      coin.width  = this.COIN_CELLS_WIDTH;
      coin.height = this.COIN_CELLS_HEIGHT;

      this.coins.push(coin);
    }
  },

  createPlatformSprites: function () {
    var sprite, pd;  // Sprite, Platform data

    for (var i=0; i < this.platformData.length; ++i) {
      pd = this.platformData[i];
      sprite           = new Sprite('platform-' + i, this.platformArtist);
      sprite.left      = pd.left;
      sprite.width     = pd.width;
      sprite.height    = pd.height;
      sprite.fillStyle = pd.fillStyle;
      sprite.opacity   = pd.opacity;
      sprite.track     = pd.track;
      sprite.button    = pd.button;
      sprite.pulsate   = pd.pulsate;
      sprite.power     = pd.power;
      sprite.top       = this.calculatePlatformTop(pd.track);

      this.platforms.push(sprite);
    }
  },

  createSapphireSprites: function () {
    // Листинг опущен для краткости. Подробно обсуждается в следующей статье этого цикла.
  },

  createRubySprites: function () {
    // Листинг опущен для краткости. Подробно обсуждается в следующей статье этого цикла.
  },

  createSnailSprites: function () {
    // Листинг опущен для краткости. Подробно обсуждается в следующей статье этого цикла.
  },
};

Методы, показанные в листинге 10, примечательны по трем причинам. Во-первых, все эти методы довольно просты. Каждый метод создает спрайты, устанавливает их ширину и высоту и добавляет их в отдельные массивы спрайтов. Во-вторых, методы createBatSprites() и createButtonSprites() используют более одного начертателя для создания спрайтов одного и того же типа. Метод createBatSprites() меняет начертатели, так что у половины летучих мышей глаза красные, а у другой ― белые, как видно на рисунке 4. Метод createButtonSprites() использует начертатели синих и золотых кнопок.


Рисунок 4. Летучие мыши с красными и белыми глазами
Игра Snail Bait в браузере Chrome

Третий и самый интересный аспект методов из листинга 10 ― это то, что все они создают спрайты из массивов метаданных спрайтов.


Создание спрайтов с помощью метаданных

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


Листинг 11. Метаданные спрайтов


var SnailBait = function (canvasId) {
  // Летучие мыши..............................................................

   this.batData = [
      { left: 1150, top: this.TRACK_2_BASELINE - this.BAT_CELLS_HEIGHT },
      { left: 1720, top: this.TRACK_2_BASELINE - 2*this.BAT_CELLS_HEIGHT },
      { left: 2000, top: this.TRACK_3_BASELINE }, 
      { left: 2200, top: this.TRACK_3_BASELINE - this.BAT_CELLS_HEIGHT },
      { left: 2400, top: this.TRACK_3_BASELINE - 2*this.BAT_CELLS_HEIGHT },
   ],

   // Пчелы..............................................................

   this.beeData = [
      { left: 500,  top: 64 },
      { left: 944,  top: this.TRACK_2_BASELINE - this.BEE_CELLS_HEIGHT - 30 },
      { left: 1600, top: 125 },
      { left: 2225, top: 125 },
      { left: 2295, top: 275 },
      { left: 2450, top: 275 },
   ],

   // Кнопки...........................................................

   this.buttonData = [
      { platformIndex: 7 },
      { platformIndex: 12 },
   ],

   // Метаданные других спрайтов Snail Bait опущены для краткости
};

Создание спрайтов из метаданных ― хорошая идея, потому что:

  • метаданные расположены в одном месте, а не распространены по всему коду;
  • методы, которые создают спрайты, проще, когда они отделены от метаданных;
  • метаданные могут поступать откуда угодно.

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

Как вы помните из листинга 9, после создания спрайтов игры метод Snail Bait createSprites() вызывает два метода ― —initializeSprites() и addSpritesToSpriteArray(). Метод initializeSprites() показан в листинге 12.


Листинг 12. Инициализация спрайтов Snail Bait
SnailBait.prototype = { // Методы
   ...

   initializeSprites: function() {  
      this.positionSprites(this.bats,       this.batData);
      this.positionSprites(this.bees,       this.beeData);
      this.positionSprites(this.buttons,    this.buttonData);
      this.positionSprites(this.coins,      this.coinData);
      this.positionSprites(this.rubies,     this.rubyData);
      this.positionSprites(this.sapphires,  this.sapphireData);
      this.positionSprites(this.snails,     this.snailData);
   },

   positionSprites: function (sprites, spriteData) {
      var sprite;

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

         if (spriteData[i].platformIndex) { // помещение спрайта на платформу
            this.putSpriteOnPlatform(sprite, this.platforms[spriteData[i].platformIndex]);
         }
         else {
            sprite.top  = spriteData[i].top;
            sprite.left = spriteData[i].left;
         }
      }
   },
};

Метод initializeSprites() вызывает метод positionSprites() для каждого из массивов спрайтов игры. Этот метод, в свою очередь, помещает спрайты в места, указанные в метаданных спрайта. Обратите внимание, что некоторые спрайты, такие как кнопки и улитки, находятся на платформах. В листинге 13 показан метод putSpriteOnPlatform().


Листинг 13. Помещение спрайтов на платформы
SnailBait.prototype = { // Методы
   ...

   putSpriteOnPlatform: function(sprite, platformSprite) {
      sprite.top  = platformSprite.top - sprite.height;
      sprite.left = platformSprite.left;
      sprite.platform = platformSprite;
   },
}   

Учитывая спрайт и платформу, метод putSpriteOnPlatform() помещает спрайт на платформу и сохраняет ссылку на эту платформу в спрайте для дальнейшего отслеживания.

Как вы, наверное, догадались и как подтверждает листинг 14, добавить отдельных спрайты в общий массив sprites нетрудно.


Листинг 14. Создание и инициализация спрайтов Snail Bait
SnailBait.prototype = { // Методы
   ...

   addSpritesToSpriteArray: function () {
      var i;

      for (i=0; i < this.bats.length; ++i) {
         this.sprites.push(this.bats[i]);
      }

      for (i=0; i < this.bees.length; ++i) {
         this.sprites.push(this.bees[i]);
      }

      for (i=0; i < this.buttons.length; ++i) {
         this.sprites.push(this.buttons[i]);
      }

      for (i=0; i < this.coins.length; ++i) {
         this.sprites.push(this.coins[i]);
      }

      for (i=0; i < this.rubies.length; ++i) {
         this.sprites.push(this.rubies[i]);
      }

      for (i=0; i < this.sapphires.length; ++i) {
         this.sprites.push(this.sapphires[i]);
      }

     for (i=0; i < this.snails.length; ++i) {
         this.sprites.push(this.snails[i]);
      }

      for (i=0; i < this.snailBombs.length; ++i) {
         this.sprites.push(this.snailBombs[i]);
      }
   },
};

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


Включение спрайтов в игровой цикл

Вспомните из второй статьи цикла (см. ее раздел Прокрутка фона), что почти все горизонтальное движение в Snail Bait является результатом преобразования координат 2D-контекста Canvas. Подавляющее большинство своих спрайтов Snail Bait всегда рисует в одном и том же месте по горизонтали, а их кажущиеся движение в горизонтальном направлении является исключительно результатом этого преобразования. Как показано в листинге 15, большинство спрайтов Snail Bait перемещается по горизонтали вместе с платформами игры.


Листинг 15. Обновление смещения спрайтов
SnailBait.prototype = {
   draw: function (now) {
      this.setPlatformVelocity();
      this.setTranslationOffsets();

      this.drawBackground();

      this.updateSprites(now);
      this.drawSprites();
   },

   setPlatformVelocity: function () {
      // Задание скорости  платформ обсуждалось во второй статье этого цикла

      this.platformVelocity = this.bgVelocity * this.PLATFORM_VELOCITY_MULTIPLIER; 
   },

   setTranslationOffsets: function () {
      // Смещение  фона обсуждалась во второй статье этого цикла

      this.setBackgroundTranslationOffset();
      this.setSpriteTranslationOffsets();
   },

   setSpriteTranslationOffsets: function () {
      var i, sprite;

      this.spriteOffset += this.platformVelocity / this.fps; // Вместе с платформами

      for (i=0; i < this.sprites.length; ++i) {
         sprite = this.sprites[i];

         if ('runner' !== sprite.type) {
            sprite.offset = this.platformOffset; // Вместе с платформами
         }
      }
   },
   ...
};

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

После расчета смещения и рисования фона метод draw() обновляет и рисует спрайты игры с помощью методов updateSprites() и drawSprites(). Эти методы показаны в листинге 16.


Листинг 16. Обновление и рисование спрайтов
SnailBait.prototype = {
   ...
   updateSprites: function (now) {
      var sprite;

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

         if (sprite.visible && this.spriteInView(sprite)) {
            sprite.update(now, this.fps);
         }
      }
   },

   drawSprites: function() {
      var sprite;

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

         if (sprite.visible && this.spriteInView(sprite)) {
            this.context.translate(-sprite.offset, 0);

            sprite.draw(this.context);

            this.context.translate(sprite.offset, 0);
         }
      }
   },

   spriteInView: function(sprite) {
      return sprite === this.runner || // бегуна видно всегда
         (sprite.left + sprite.width > this.platformOffset &&
          sprite.left < this.platformOffset + this.canvas.width);   
   },

Когда спрайты не видны

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

Оба метода updateSprites() и drawSprites() перебирают все спрайты игры и соответственно обновляют и изображают спрайты — но только если они видны и находятся в области холста, отображаемой в данный момент.

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


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

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



Загрузка

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

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


Ресурсы

Научиться

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

  • Replica Island: загрузите открытый исходный код этого популярного платформера для Android.

Об авторе

Дэвид Гири

Дэвид Гири (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=857637
ArticleTitle=Разработка 2D-игр на HTML5: Спрайты
publish-date=03082013