воскресенье, 25 июля 2010 г.

Анимация листов с тайлами (sprite sheets)

Здравствуйте, представляю вашему вниманию перевод статьи How to Blit an animation from a tile sheet embedded at compile time

Речь пойдет об анимации с использованием листов с тайлами.
Такой лист

Лист тайлов для анимации свободного падения



даст подобную анимацию

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/07/25/animation.swf, 70, 400[/SWF]




Как использовать для анимации лист тайлов, внедренный в проект AS3 на этапе компиляции



Сегодня мы поговорим о том, как использовать для анимации лист тайлов, внедренный в проект AS3 во время компиляции. Мы посмотрим, как внедрять, инстанциировать и использовать лист тайлов для создания простой анимации, основанной на блитировании (копировании битового массива). В настоящее время я работаю над ремейком классической игры для Atari 2600 «Sky Diver». Я начал создавать свои стилизованные под изображения 8-битных игрушек листки тайлов. У меня есть только первые три предварительных тайла, созданные для анимации спрайта свободного падения персонажа. Вот как выглядит лист этих тайлов:

Лист тайлов для анимации свободного падения
Рисунок 1. – Лист тайлов для анимации свободного падения



Этот лист шириной в 10 тайлов (320 пикселей) и высотой в 2 тайла (64 пикселя). Размер тайлов - 32х32 пикселя. Сейчас мы будем использовать только три тайла, но на этом листе есть место и для большего количества. Начнем с создания простого класса, который продемонстрирует использования этого листа для того, чтобы анимировать персонажа, используя классическую технику копирования битового массива. Эта техника будет использовать заливку фона, чтобы очищать экран между кадрами анимации. Большая часть теории этого процесса была раскрыта [URL=http://www.8bitrocket.com/2008/07/02/tutorial-as3-the-basics-of-tile-sheet-animation-or-blitting/]здесь[/URL], поэтому не будем повторяться. Итак.

1. Начнем с импорта необходимых классов.



package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;


В этом коде мы импортируем класс Bitmap, который будет отображать содержимое экземпляра класса BitmapData. Все действие будет происходить в экземпляре класса Sprite, в который будет добавляться Bitmap. Также мы будем использовать событие EnterFrame, для чего импортируем класс Event. Это событие мы будем использовать для симуляции покадровой анимации нашего персонажа. Классы Point и Rectangle нужны для блитирования.

2. Теперь посмотрим на переменные нашего класса.



Здесь мы используем директиву компилятора Embed для внедрения нашего листа в проект.

public class TileSheetEmbed extends Sprite{
[Embed(source = 'skyKingGraphics.png')]
private var TileSheet:Class;

private var backGroundBitmapData:BitmapData =new BitmapData(100, 100, false, 0x000000);

private var canvasBitmapData:BitmapData =new BitmapData(100, 100, false, 0xffffff);

private var canvasBitmap:Bitmap = new Bitmap(canvasBitmapData);
private var tileCounter:int = 0;
private var tileList:Array = [0, 0, 0, 1, 1, 1, 2, 2, 2];
private var tileSheet:Bitmap=new TileSheet();
private var backBlitPoint:Point = new Point(0,0);
private var tileBlitPoint:Point = new Point(20,20);
private var blitRect32:Rectangle = new Rectangle(0, 0, 50, 50);
private var tileWidth:int = 50;
private var tileHeight:int = 50;
private var tilesPerRow:int = tileSheet.width / tileWidth;


Заметим, что наш класс расширяет Sprite. Он мог бы расширять и MovieClip, но в этом нет никакой необходимости. Посмотрим на эту часть кода более внимательно.

Сначала мы внедряем файл tilesheet.png как класс TileSheet. Файлы картинок внедряются в swf как экземпляры класса BitmapAsset (это подкласс класса Bitmap используется в Flex), а не как классы BitmapData, как при импорте в библиотеку во Flash IDE.

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

У нашей анимации есть три кадра, которые будут проигрываться циклично. Каждый кадр мы будем проигрывать три раза для создания задержки между кадрами. Для этого создадим массив tileList, который будет хранить эту последовательность. Это поможет несколько замедлить анимацию и не позволить ей проигрываться слишком быстро. Есть другие более изящные методы, чтобы добиться того же результата (например создание и использование переменной в которой хранилось бы значение задержки), но здесь мы для ясности воспользуемся именно такой реализацией. Массив tileList содержит номера тайлов из нашего листа и определяет порядок, в котором они будут проигрываться. Этот порядок можно легко изменить, переставив номера местами. Переменная tileCounter представляет собой идентификационный номер тайла, который используется для блитирования в текущий момент. Наш текущий лист может содержать 20 тайлов (два ряда по 10 штук в каждом). Верхний левый тайл будет иметь номер «0», а правый нижний – номер «19». Нумерация тайлов идет слева направо. После нумерации тайлов из верхней строки, переходим на нижнюю строку и продолжаем нумерацию слева направо. Те три тайла, которые есть на нашем листе, имеют номера 0, 1, 2

Переменная tileSheet хранит экземпляр класса TileSheet, т.е. картинку-лист, которую мы внедрили.

Для операции блитирования мы создаем набор экземпляров класса Point и определяем его на уровне класса. Мы делаем это так, потому что сможем их использовать многократно, и не будем тратить память и время процессора на создание новых экземпляров в каждом кадре. backBlitPoint(0,0) представляет координаты верхнего левого угла, с которого начнется копирования backGroundBitmapData в canvasBitmapData. Точка tileBlitPoint(20,20) представляет левый верхний угол, с которого начнется копирование персонажа размером 32х32 в canvasBitmapData.

Переменная blitRect32 представляет прямоугольную область листа tileSheet размером 32х32 откуда мы будем брать пиксели для копирования их в canvasBitmapData (начиная с точки tileBlitPoint). Координаты х и у этого прямоугольника могут быть изменены в каждом кадре, когда нам понадобиться новый тайл из листа tileSheet. Заметим, что координата у этого прямоугольника в нашем примере изменяться не будет, т.к. в нашем листе всего одни ряд тайлов.

Переменные tileWidth и tileHight хранят ширину и высоту тайла и равняются 32. Переменная tilesPerRow хранит количество тайлов в одной строке.

3. Теперь посмотрим на функцию-конструктор:



  public function TileSheetEmbed() {
addChild(canvasBitmap);
addEventListener(Event.ENTER_FRAME, runDemo, false, 0, true);
}


Она очень проста. Мы добавляем canvasBitmap в список отображения и добавляем слушателя к событию Event.ENTER_FRAME.

4. Метод runDemo:



private function runDemo(e:Event):void {
canvasBitmapData.lock();

canvasBitmapData.copyPixels(backGroundBitmapData,
backGroundBitmapData.rect, backBlitPoint);

blitRect32.x=int(tileList[tileCounter]% tilesPerRow)*tileWidth;
blitRect32.y=int(tileList[tileCounter] / tilesPerRow)*tileHeight;

canvasBitmapData.copyPixels(tileSheet.bitmapData,
blitRect32, tileBlitPoint);

canvasBitmapData.unlock();
tileCounter++;
if (tileCounter > tileList.length-1) {
tileCounter = 0;
}
}
}
}


Метод runDemo начинается с того, что мы блокируем canvasBitmapData. Это не позволяет canvasBitmap выводить на экран canvasBitmapData пока она не будет разблокирована. Сначала мы блитируем черную область 100х100 из backGroundBitmapData в canvasBitmapData, используя точку backBlitPoint(0,0) в методе copyPixels().

Затем мы вычисляем верхнюю левую точку позиции, откуда нужно начать блитирование нашего персонажа из листа tileSheet. Это делается с помощью текущего номера тайла (0, 1 или 2) в массиве tileList. blitRect32 представляет собой прямоугольную область размером 32х32, которая охватывает персонажа на листе tileSheet и которая будет скопирована (блитирована) в canvasBitmapData. Она изменяется в каждом кадре при изменении индекса тайла в массиве tileList.

Теперь у нас есть все, чтобы скопировать область размером 32х32 из tileSheet в canvasBitmapData. Сделаем это с помощью метода copyPixels.
Изменим переменную tileCounter прибавив к ней единицу, таким образом в следующем кадре метод будет выполняться для следующего тайла в массиве tileList (но не в tileSheet).

На этом у меня все :). Вариант для flash ide лежит в исходниках.

Результат

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/07/25/animation1.swf, 100, 100[/SWF]



Исходники

3 комментария:

  1. [...] тайлу на тайловом листе (можно посмотреть этот перевод). Мы показываем только то, что помещается на [...]

    ОтветитьУдалить
  2. Спасибо за статью.Пример использовал для своего тайла (анимированые наклоны корабля).Но как сделать чтоб фон оставался прозрачным а не чёрным?Ведь за кораблём явно заметен чёрный квадрат на ланшафте... Может у вас есть пример используюший маску?И какой вариант будет лучше-быстрее,через Bitmap или через маску?

    ОтветитьУдалить
  3. С фоном разобрался,просто в параметр transparent поставил true. 
    var tempbmpd:BitmapData = new BitmapData(temp.width, temp.height, true, 0);

    ОтветитьУдалить