воскресенье, 12 сентября 2010 г.

Основы работы с классом Bitmap

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

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part9.swf, 200, 200[/SWF]



Введение в класс Bitmap. Урок 1 – Создание простого тайлового листа.

Мы уже много говорили о блитировании и BitmapData, но почти ничего о контейнере для BitmapData – классе Bitmap. В этой серии статей мы обсудим использование класса Bitmap во множестве различных ситуаций и управление свойствами BitmapData для создания анимации и трансформации. Инстанциирование класса Bitmap дает нам отображаемый объект, с помощью которого мы можем управлять отображением BitmapData. Это сочетание классов Bitmap и BitmapData позволяет использовать встроенные возможности отображаемых объектов во Flash, одновременно с мощью управления отдельными пикселями данных для быстрого рендеринга.

Первый урок состоит из трех частей. Сначала мы посмотрим на динамическое рисование простых фигур в холсте класса Bitmap. Затем мы создадим динамический тайловый лист, используя BitmapData, как свойство экземпляра класса Bitmap и наконец, мы создадим простую анимацию, используя этот тайловый лист.

Часть 1. Отрисовка пикселей в холсте Bitmap.

Класс Bitmap имеет свойство, в котором храниться ссылка на определенный экземпляр класса BitmapData. Это дает изображение Bitmap и возможность управления содержимым этого изображения программно. Первое, что мы сделаем – отрисуем простой «космический корабль» размером 32х32. Этот корабль будет выглядеть следующим образом:

Рисунок 1. – «Космический корабль»


Рисунок 1. – «Космический корабль»



Это изображение с прозрачным фоном в формате png. Этот корабль не занимает весь размер 32х32, а имеет расстояние от краев равное 4 пикселям. Создадим экземпляр класса Bitmap:

private var bitmapToDisplay:Bitmap;


Также создадим объект BitmapData, в котором будет храниться изображение судна:

private var bitmapDataToDisplay:BitmapData;


Мы отрисуем пиксели нашего корабля, устанавливая цвет каждого отдельно пикселя объекта bitmapDataToDisplay. Т.к. у нас есть две прямые линии (толщиной 2 px), мы можем создать очень простой цикл для отрисовки нашего корабля. Для этого мы будем использовать метод setPixel32 класса BitmapData:

for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr,27,0xff0066ff)
}


В этом цикле мы создаем вертикальную линию от 4-го до 27-го пикселя по вертикали и в 15-м и 16-м пикселе по горизонтали и горизонтальную линию в от 4-го до 27-го пикселя по горизонтали и в 26-м и 27-м пикселе по вертикали. Это даст нам изображение корабля, показанное на рисунке 1. Очевидно, что это вероятно самое простое изображение корабля, когда-либо рисовавшееся, но оно демонстрирует как управлять пикселями «на лету» и будет основанием для большей части дальнейшего обсуждения. Не стесняетесь изменять это изображение, чтобы удовлетворить ваше чувство прекрасного :).

Вот полный код для первой части этого урока.

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

/**
* ...
* @author Jeff Fulton
*/
public class Part1 extends Sprite {
private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

public function Part1():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(32, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);
createBitmapData();
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff)
bitmapDataToDisplay.setPixel32(ctr,27,0xff0066ff)
}
}
}
}


Вот результат этого кода:
[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part1.swf, 200, 200[/SWF]

Часть 2. Отрисовка второго кадра в том же объекте BitmapData для создания тайлового листа.

Итак, мы создали экземпляр класс BitmapData, названный bitmapDataToDisplay и отрисовали в нем простой космический корабль. В этой части мы увеличим размер этого экземпляра BitmapData с 32 до 64 пикселей по ширине. Мы отрисуем второй кадр нашего простого корабля. Этот второй кадр будет вторым тайлом тайлового листа.

Вот код, которым мы изменим ширину BitmapData с 32-х до 64-х:

bitmapDataToDisplay = new BitmapData(64, 32, true, 0×00000000);


Вот изображение корабля, которое будет отрисовано, начиная с 32-го пикселя и заканчивая 64-м пикселем:

<br/>Рисунок 2. – Второй кадр корабля


Рисунок 2. – Второй кадр корабля



Единственное изменение, по сравнению с первым кадром это маленький красный «выхлоп» внизу корабля. Это второй кадр анимации, который будет использоваться для симуляции полета корабля в космосе.

Для отрисовки второго кадра в BitmapData мы скопируем пиксели из первого кадра во второй кадр. Для этого мы используем метод copyPixels() класса BitmapData:

bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(33, 0));


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

Теперь рисуем красный выхлоп:

for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(49, ctr, 0xffff0000);
}


Подобно тому, как мы рисовали линии корабля мы создаем красную линию толщиной 2 пикселя.

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

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;

/**
* ...
* @author Jeff Fulton
*/

public class Part2 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

public function Part2():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point

bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);

createBitmapData();
}

private function createBitmapData():void {
//draw vertical line with setPixel32

for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(33, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(49, ctr, 0xffff0000);
}
}
}

}


Вот результат выполнения этого кода:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part2.swf, 200, 200[/SWF]

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


Часть 3. Анимация тайлового листа

Свойство Bitmap.scrollRect() используется для определения видимой части объекта Bitmap. Фактически это работает как маска, под прямоугольной областью которой будет виден рисунок, а все остальное будет скрыто.

Нам нужно создать переменную, в которой будет храниться текущая прямоугольная область, отображаемая в нашем объекте Bitmap:

private var bitmapScrollRect:Rectangle;


В методе init() мы задаем ей размер 32х32:

bitmapScrollRect=new Rectangle(0, 0, 32, 32);


И присваиваем эту область свойству scrollRect нашего экземпляра Bitmap:

bitmapToDisplay.scrollRect = bitmapScrollRect;


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

private var animationDelay:int = 5;
private var animationCount:int = 0;


Переменная animationCount будем увеличиваться в каждом кадре. Когда ее значение станет равным значению переменной animationDelay, мы обновим переменную bitmapScrollRect. Вот код:

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}


Когда выполняется условие animationCount == animationDelay, мы изменяем значение координаты х прямоугольника bitmapScrollRect. Значения координаты могут быть равны 0 (начало первого тайла) или 32 (начало второго тайла). Мы прыгаем между этими двумя значениями каждые 5 кадров. Это не самый сложный метод анимации, но он прекрасно работает для этого примера.

Весь вышеупомянутый код будет располагаться в теле метода runGame(). Этот метод будет вызываться каждый раз, при наступлении события Event.EnterFrame.

Вот код третьей части:

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;

/**
* ...
* @author Jeff Fulton
*/
public class Part3 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

public function Part3():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
addChild(bitmapToDisplay);

createBitmapData();

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}
}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}
}

}


А вот результат работы этого кода:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part3.swf, 200, 200[/SWF]

Урок 2. Поворот с использованием Matrix и без него.

Во втором уроке мы посмотрим на различные методы поворота объекта Bitmap из первого урока. Сначала мы просто используем свойство Bitmap.rotation. Затем мы сделаем тоже самое используя объект Matrix и наконец создадим анимацию с использованием объекта Matrix. Урок 2 мы также разделим на три части (4, 5, 6).

Часть 4. Поворот объекта Bitmap с помощью свойства rotation.

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

Проблема, как вы увидите, в том, что невозможно поворачивать содержимое BitmapData кроме как вокруг левого верхнего угла объекта Bitmap. Добавим одну строчку в метод runGame() кода третьей части:

bitmapToDisplay.rotation += 10;


Этот код просто поворачивает объект Bitmap на 10 градусов в каждом кадре. Мы также переместили объект Bitmap в новую позицию с координатами (84, 84), чтобы изображение объекта не обрезалось левой границей stage при повороте. Мы присваиваем свойству smoothing значение true, чтобы наше растровое изображение не выглядело зазубренным, когда пикселям присваиваются новые координаты. Эта строчка добавляется в метод init():

bitmapToDisplay.smoothing = true;


Вот полный код четвертой части:

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;

/**
* ...
* @author Jeff Fulton
*/
public class Part4 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

public function Part4():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = 84;
bitmapToDisplay.y = 84;
addChild(bitmapToDisplay);

createBitmapData();
//makes it look better when rotates
bitmapToDisplay.smoothing = true;

bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}

}

private function runGame(e:Event):void {

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//normal rotation will not work as it rotates on the top left corner
bitmapToDisplay.rotation += 10;
}
}
}


Вот результат работы этого кода:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part4.swf, 200, 200[/SWF]

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

Часть 5. Поворот объекта Bitmap «на лету» с помощью Matrix.

В предыдущей части мы поворачивали объект Bitmap обновлением его свойства rotation. Объект вращался вокруг своего левого верхнего угла. Теперь тоже самое мы сделаем по другому. Один из способов - поместить объект Bitmap внутри объекта Sprite и сместить его на половину ширины влево и на половину высоты вверх. Когда спрайт будет повернут, объект Bitmap будет повернут относительно центральной точки. Единственная проблема в этом методе – верхний угол видимой части Bitmap теперь имеет координаты не равные (0,0) и для их определения требуется выполнение некоторых математических вычислений в рантайме. К счастью это не единственный способ решить эту задачу. Для этого мы можем использовать операции поворота и смещения с объектом Matrix.

Для начала посмотрим на три новых переменных, которые нам понадобятся для реализации поворота с помощью объекта Matrix:

private var bitmapRotation:int = 0;
private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);


Переменная bitmapRotation будет хранить текущий угол поворота корабля. Он будет изменяться в диапазоне 0-359 и использоваться объектом Matrix для вычисления корректной позиции корабля на экране. Переменная bitmapRotationMatrix хранит в себе объект Matrix, который будет использоваться для вычисления центра вращения корабля. Переменная startLocation хранит координаты левого верхнего угла изображения корабля. Нам нужно сохранить это значение в отдельной переменной, т.к. координаты объекта Bitmap будут изменяться при преобразованиях с объектом Matrix.

В методе init() мы создаем объект Matrix:

bitmapRotationMatrix= new Matrix();


В методе runGame() мы закомментируем часть кода, которая выполняет анимацию. Сюда мы добавим код поворота нашего корабля, а анимацию добавим чуть позже, в части 6.

bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;


Пошагово разберем этот код:

1. Сначала мы изменяем переменную bitmapRotation на единицу и проверяем не превышает ли значение этой переменной 359. Если это так, то присваиваем этой переменной значение 0.
2. Мы создаем локальную переменную angleInRadians типа Number для хранения угла поворота в радианах.
3. Мы вызываем метод identity() класса Matrix, который сбрасывает значение переменной bitmapRotationMatrix и позволяет записывать в нее новые значения в каждом кадре.
4. Теперь мы добрались до самих вычислений.

Сначала мы вызываем метод translate() и передаем в него значения, равные половине ширины и высоты со знаком «-». Здесь я задал сразу -16, т.к. это быстрее вычислений, но вам возможно понадобиться вычислять значения ширины и высоты объекта bitmapToDisplay «на лету». Это заставит вращаться корабль относительно своего центра, тогда как ранее он вращался относительно левого верхнего угла.

Затем мы передаем значение угла поворота в радианах в метод rotate() класса Matrix. Этот метод повернет объект Bitmap относительно точки, которая будет центром изображения, т.к. перед этим мы вызвали метод translate().

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

Вот код пятой части целиком:

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

/**
* ...
* @author Jeff Fulton
*/
public class Part5 extends Sprite {

private var bitmapToDisplay:Bitmap;
private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
private var animationDelay:int = 5;
private var animationCount:int = 0;

private var bitmapRotation:int = 0;
private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

public function Part5():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);

bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);

createBitmapData();

bitmapRotationMatrix= new Matrix();
bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;

bitmapToDisplay.smoothing = true; //makes it look better when rotates

addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}

//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(32, 0));

//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {
bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}

}

private function runGame(e:Event):void {

/*
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
*/

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

}

}

}


Вот результат работы этого кода:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part5.swf, 200, 200[/SWF]

Часть 6. Добавление анимации

В этой части мы просто добавим анимации обратно. Для этого раскомментируем следующий код:

animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
}


Вот результат:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part6.swf, 200, 200[/SWF]

Урок 3. Масштабирование с помощью Matrix.

Часть 7. Масштабирование Bitmap с помощью Matrix.

Масштабирование объекта Bitmap с помощью Matrix очень похоже на его поворот. Сначала мы перемещаем объект на половину ширины влево и на половину высоты вверх, затем масштабируем и перемещаем обратно на прежнюю позицию.
Нам понадобятся две новые переменные:

private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;


Переменная bitmapScaleMatrix хранит экземпляр класса Matrix, который будет использоваться для применения операций масштабирования к объекту bitmapToDisplay. Переменная bitmapScale хранит текущий коэффициент масштабирования объекта bitmapToDisplay. Он будет обновляться в каждом кадре. Заметим, что мы не можем использовать для масштабирования свойства scaleX и scaleY объекта bitmapToDisplay. Т.к. это приведет к масштабирования относительно левого верхнего угла.
Внутри метода runGame() мы масштабируем объект в каждом кадре. Мы начинаем со значения bitmapScale = 1 и увеличиваем его в каждом кадре на 0,1. Если значение больше, чем 2, то мы присваиваем коэффициенту значение 0,1. Подобно повороту нам нужно очищать объект bitmapScaleMatrix с помощью метода identity() в каждом кадре перед операцией масштабирования.

bitmapScaleMatrix.identity(); //resets the matrix

bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}


Пошагово разберем этот код:

1. Сбрасываем значение Matrix методом identity().
2. Перемещаем центр объекта Bitmap из точки 16,16 в точку 0,0.
3. Вызываем метод scale() в который передаем переменную bitmapScale.
4. Переводим центр объекта bitmapToDisplay обратно в точку 16,16.
5. Присваиваем bitmapScaleMatrix свойству bitmapToDisplay.transform.
6. Увеличиваем значение bitmapScale на 0,1 и проверяем, чтобы это значение не превышало 2.

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

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

/**
* ...
* @author Jeff Fulton
*/

public class Part7 extends Sprite {
private var bitmapToDisplay:Bitmap;

private var bitmapDataToDisplay:BitmapData;

private var bitmapScrollRect:Rectangle;
//private var animationDelay:int = 5;
//private var animationCount:int = 0;

//private var bitmapRotation:int = 0;
//private var bitmapRotationMatrix:Matrix;
private var startLocation:Point = new Point(84, 84);

//*** new in part 7
private var bitmapScaleMatrix:Matrix;
private var bitmapScale:Number = 1;

public function Part7():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
bitmapDataToDisplay = new BitmapData(64, 32, true, 0x00000000);
bitmapToDisplay = new Bitmap(bitmapDataToDisplay);
bitmapToDisplay.x = startLocation.x;
bitmapToDisplay.y = startLocation.y;
addChild(bitmapToDisplay);
createBitmapData();
//*** new in part 7
bitmapScaleMatrix= new Matrix();
//bitmapRotationMatrix= new Matrix();
bitmapScrollRect=new Rectangle(0, 0, 32, 32);
bitmapToDisplay.scrollRect = bitmapScrollRect;
bitmapToDisplay.smoothing = true; //makes it look better when rotates
addEventListener(Event.ENTER_FRAME, runGame,false,0,true);
}

private function createBitmapData():void {
//draw vertical line with setPixel32
for (var ctr:int = 4; ctr <= 27; ctr++) {
bitmapDataToDisplay.setPixel32(15, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(16, ctr, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 26, 0xff0066ff);
bitmapDataToDisplay.setPixel32(ctr, 27, 0xff0066ff);
}
//copy ship to next tile
bitmapDataToDisplay.copyPixels(bitmapDataToDisplay, new Rectangle(0, 0, 32, 32), new Point(32, 0));
//add red thruster under the ship for this frame
for (ctr= 29; ctr <= 31; ctr++) {

bitmapDataToDisplay.setPixel32(47, ctr, 0xffff0000);
bitmapDataToDisplay.setPixel32(48, ctr, 0xffff0000);
}
}

private function runGame(e:Event):void {
bitmapScaleMatrix.identity(); //resets the matrix
bitmapScaleMatrix.translate(-16,-16);
bitmapScaleMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleMatrix.translate(startLocation.x+16, startLocation.y+16);
bitmapToDisplay.transform.matrix = bitmapScaleMatrix;
bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
//not needed in part 7

/*
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}
//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}
var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapRotationMatrix.identity(); //resets the matrix

bitmapRotationMatrix.translate(-16,-16);
bitmapRotationMatrix.rotate(angleInRadians);
bitmapRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );
*/
//end not needed in part 7
}
}
}


Вот результат работы этого кода:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part7.swf, 200, 200[/SWF]


Часть 8. Добавление анимации

Для добавления анимации просто раскомментируем часть кода, отвечающую за анимацию, получиться следующее:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part8.swf, 200, 200[/SWF]

Часть 9. Комбинация поворота и масштабирования

Для комбинации поворота и перемещения внутри одной операции мы создадим новый экземпляр класса Matrix bitmapScaleRotationMatrix и удалим два старых – bitmapRotationMatrix и bitmapScaleMatrix.

Метод runGame() обновиться следующим образом:

private function runGame(e:Event):void {
animationCount++;
if (animationCount == animationDelay) {
animationCount = 0;
if (bitmapScrollRect.x == 0) {
bitmapScrollRect.x = 32;
}else {
bitmapScrollRect.x = 0;
}
bitmapToDisplay.scrollRect = bitmapScrollRect;
}

//rotate with a matrix
bitmapRotation += 1;
if (bitmapRotation > 359){
bitmapRotation = 0;
}

var angleInRadians:Number = Math.PI * 2 * (bitmapRotation / 360);

bitmapScaleRotationMatrix.identity(); //resets the matrix

bitmapScaleRotationMatrix.translate(-16,-16);
bitmapScaleRotationMatrix.rotate(angleInRadians);
bitmapScaleRotationMatrix.scale(bitmapScale, bitmapScale);
bitmapScaleRotationMatrix.translate(startLocation.x+16, startLocation.y+16);

bitmapToDisplay.transform.matrix = bitmapScaleRotationMatrix;

trace(bitmapToDisplay.x + "," + bitmapToDisplay.y + "," + bitmapRotation );

bitmapScale += .1;
if (bitmapScale > 2) {
bitmapScale = .1;
}
}


1. Часть кода анимации понятна – мы ее рассмотрели ранее.
2. Аналогично с поворотом.
3. В каждом кадре вызываем метод identity() над объектом bitmapScaleRotationMatrix.
4. Перемещаем центр Bitmap.
5. Поворачиваем.
6. Масштабируем.
7. Перемещаем цент обратно.
8. Присваиваем значение bitmapScaleRotationMatrix свойству bitmapToDisplay.transform.matrix

Вот результат:

[SWF]http://coolisee.com/wordpress/wp-content/uploads/2010/09/12/part9.swf, 200, 200[/SWF]

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

  1. спасибо за идею поворота битмапа с помощью спрайта. Зря вы его пропустили, иногда гораздо проще воспользоваться им, чем заморачиваться с матрицей! :)

    ОтветитьУдалить
  2. А как оно быстрее\менее нагрузочно на процессор? Спрайтом вертеть или матрицей?

    ОтветитьУдалить
  3. я думаю спрайтом все же побыстрее и проще если объект один и с матрицей разбираться не надо :) надо пробовать

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