Статьи

Удар изображения с пользовательским эффектом ветра

Два раза в месяц мы возвращаемся к любимым постам наших читателей на протяжении всей истории Activetuts +. Этот учебник был впервые опубликован в марте 2010 года.

В этом уроке мы создадим пользовательский класс, который разбивает картинку на тысячи частей и имитирует ветер, уносящий их. Я создал этот проект исключительно с AS3 и FlashDevelop — Flash не требуется!


Давайте посмотрим на конечный результат, к которому мы будем стремиться. Идите дальше и щелкните в любом месте внутри SWF:


FlashDevelop — бесплатный редактор кода для Flash и Flex. Вы можете использовать его для редактирования ваших файлов классов при работе с программным обеспечением Flash, или вы можете создать проект AS3, который вообще не требует Flash — и это именно то, что мы будем делать в этом руководстве.

Поэтому скачайте FlashDevelop и установите его. К сожалению, FlashDevelop работает только на Windows. Альтернативы Mac включают FDT и Flex Builder , хотя ни один из них не является бесплатным. Вы можете использовать саму Flash, и я объясню, как это сделать по ходу дела.


Откройте FlashDevelop и нажмите «Проект»> «Новый проект» …


Выберите Actionscript 3> AS3 Project. Для названия проекта укажите «WindEffect». Для определения местоположения щелкните и перейдите в папку, в которую вы хотите сохранить ее. Оставьте флажок «Создать каталог для проекта» установленным и нажмите «ОК».

Если вы хотите использовать Flash CS3 / CS4, создайте новый файл Flash и установите ширину и высоту сцены равными 550×250 пикселей, установите цвет фона на черный. Назовите его «windEffect.fla» и сохраните его где угодно.


Для FlashDevelop откройте каталог проекта и скопируйте или перетащите файл windEffect.jpg из исходной загрузки (ссылка вверху страницы) в папку \ bin \.

Для Flash скопируйте или перетащите файл windEffect.jpg из исходной загрузки в ту же папку, где находится файл windEffect.fla.


Мы собираемся использовать TweenLite от Greensock для анимации. Вы можете скачать последнюю версию компонента здесь ; Я также включил его в исходный код загрузки.

Для FlashDevelop, скопируйте или перетащите greensock.swc из исходной загрузки в папку \ lib \ для этого проекта.

В FlashDevelop нажмите «Просмотр»> «Диспетчер проектов».


Находясь в FlashDevelop, щелкните значок «+» слева от папки lib, чтобы развернуть ее. Щелкните правой кнопкой мыши greensock.swc и выберите «Добавить в библиотеку».

Для Flash скопируйте или перетащите папку \ com \ из исходной загрузки в ту же папку, что и файл windEffect.fla.


Для FlashDevelop снова откройте менеджер проекта (см. Шаг 4), разверните папку \ src \ и дважды щелкните Main.as. Под импортом и прямо над определением класса добавьте следующий тег метаданных для настройки свойств этапа:

1
[SWF (width = 550, height = 250, frameRate = 30, backgroundColor = 0)]

В методе init () после комментария «точка входа» добавьте следующий код:

1
2
3
stage.scaleMode = StageScaleMode.NO_SCALE;
var effect:WindEffect = new WindEffect(‘windEffect.jpg’);
addChild (effect);

Вот и все для класса основного документа.

Для Flash создайте новый класс Main.as в той же папке, что и ваш проект. Убедитесь, что класс Main.as находится в той же папке, что и fla. & com папка. Добавьте следующие строки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package
{
    import flash.display.Sprite;
    import flash.display.StageScaleMode;
    import flash.events.Event;
 
    public class Main extends Sprite
    {
 
        public function Main():void
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        private function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            stage.scaleMode = StageScaleMode.NO_SCALE;
 
            var effect:WindEffect = new WindEffect (‘windEffect.jpg’);
            addChild (effect);
        }
     }
}

Откройте Flash и назначьте «Main» в качестве класса Document.

(Не уверен, о чем идет речь? Прочтите это краткое введение в использование класса документа .)

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


Для FlashDevelop нажмите «Просмотр»> «Диспетчер проектов», щелкните правой кнопкой мыши папку \ src \ и выберите «Добавить»> «Новый класс».


Назовите класс WindEffect, нажмите кнопку обзора базового класса и введите flash.display.Sprite. Нажмите ОК, чтобы завершить.


Добавьте весь необходимый импорт в скобки пакета прямо под ‘import flash.display.Sprite;’ и до определения класса. Нажмите Сохранить.

01
02
03
04
05
06
07
08
09
10
import com.greensock.easing.Strong;
import com.greensock.TweenLite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;

Для Flash создайте новый файл ActionScript, назовите его «WindEffect.as» и сохраните в том же каталоге, который вы использовали. Это должно быть прямо рядом с фла. файл, папка com и Main.as.

Добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package
{
    import com.greensock.easing.Strong;
    import com.greensock.TweenLite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.net.URLRequest;
 
    public class WindEffect extends Sprite
    {
        public function WindEffect ()
        {
 
        }
 
    }
 
}

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

Добавьте следующую строку кода в скобках
класса:

1
2
3
4
5
public class WindEffect extends Sprite
{
    //this will house all the pieces of the picture we will animate.
    private var _pictureArray:Array;
}

Добавьте следующие строки после объявления _pictureArray:

01
02
03
04
05
06
07
08
09
10
11
public class WindEffect extends Sprite
{
    //this will house all the pieces of the picture we will animate.
    private var _pictureArray:Array;
 
    public function WindEffect ($url:String)
    {
        //we just call the load picture in the constructor
        loadPicture ($url);
    }
}

Внутри метода loadPicture (), вызываемого методом конструктора, мы создаем загрузчик для загрузки файла windEffect.jpg. Мы также добавляем прослушиватель события COMPLETE для прослушивания после завершения загрузки.

Добавьте следующие строки кода после метода WindEffect (). (Обратите внимание, что параметр «$ url» — это путь к загружаемой картинке, переданной из Main.as.)

1
2
3
4
5
6
7
8
private function loadPicture ($url:String):void
{
    //we create a loader with listeners to load the source picture we are using.
    //and then we load the image.
    var loader:Loader = new Loader;
    loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);
    loader.load (new URLRequest ($url));
}

После правильного импорта изображения вызывается этот метод. Добавьте следующие строки кода после метода loadPicture () и сохраните файл.

1
2
3
4
5
private function onLoadComplete (e:Event):void
{
    //for testing
    addChild (e.target.content);
}

Идите вперед и нажмите CTRL + Enter на клавиатуре. Это должно работать, и изображение должно быть в левом верхнем углу сцены.

Теперь, когда мы проверили правильность загрузки, удалите метод addChild и замените его следующим кодом:

1
createEffect (e.target.content);

Ваш класс WindEffect должен выглядеть примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package
{
    import com.greensock.easing.Strong;
    import com.greensock.TweenLite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.net.URLRequest;
 
    [SWF (width = 550, height = 250, frameRate = 30, backgroundColor = 0)]
 
    public class WindEffect extends Sprite
    {
        private var _pictureArray:Array;
 
        public function WindEffect ($url:String)
        {
            loadPicture ($url);
        }
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function loadPicture ($url:String):void
        {
            var loader:Loader = new Loader;
            loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);
            loader.load (new URLRequest ($url));
        }
 
        private function onLoadComplete (e:Event):void
        {
            createEffect (e.target.content);
        }
 
    }
 
}

Метод createEffect () примет параметр изображения, который по сути является растровым изображением, и разбит его на 1250 частей.

Сначала мы рассчитываем x- и y-позиции, чтобы центрировать изображение на сцене. Мы сохраняем их в локальные переменные, называемые centerWidth и centerHeight.

Поскольку размер изображения, которое мы используем, составляет 300×100, я решил разделить изображение 50 раз по горизонтали и 25 раз по вертикали. Эти значения дали довольно приличный результат с оптимальной производительностью. Мы сохраняем их в локальных переменных, которые я назвал «numberOfColumns» и «numberOfRows».

Мы сохраняем результат деления ширины изображения на numberOfColumns на «sizeWidth» и результат деления высоты изображения на numberOfRows на «sizeHeight».

Переменная «numberOfBoxes» содержит числоOfColumns, умноженное на числоOfRows.

Затем мы создаем экземпляр _pictureArray, чтобы начать добавлять в него небольшие спрайты. Добавьте следующие строки кода после метода onLoadComplete ():

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function createEffect ($bitmap:Bitmap):void
{
    //center the image horizontally.
    var centerWidth:Number = (stage.stageWidth — $bitmap.width) * .5;
 
    //center the image vertically.
    var centerHeight:Number = (stage.stageHeight — $bitmap.height) * .5;
 
    var numberOfColumns:uint = 50;
    var numberOfRows:uint = 25;
    var sizeWidth:uint = $bitmap.width / numberOfColumns;
    var sizeHeight:uint = $bitmap.height / numberOfRows;
    var numberOfBoxes:uint = numberOfColumns * numberOfRows;
    _pictureArray = [];
 
}

После создания экземпляра _pictureArray мы добавим два цикла, один внутри другого. Первый цикл будет обрабатывать перемещение по x-позиции и будет проходить по всем столбцам, в то время как второй цикл будет перемещаться по y-позиции и будет проходить по всем строкам.

Добавьте следующие строки кода в метод createEffect () сразу после создания экземпляра _pictureArray, затем сохраните файл:

1
2
3
4
5
6
7
8
9
for (var i:uint = 0; i < numberOfColumns; i++)
{
    //these loops are what splits the image into 1250 pieces.
    for (var j:uint = 0; j < numberOfRows; j++)
    {
        //let’s see what it does.
        trace (‘i:’ + i, ‘j:’ + j);
    }
}

Проверьте фильм, нажав CTRL + Enter.

Как видите, для каждого i есть полный цикл j . Это называется «вложенными циклами». Это означает, что i, представляющая ось x, остается на одном значении, в то время как второй цикл повторяется для оси y.

Проще говоря, мы начинаем с x = 0, y = 0; тогда следующая итерация — x = 0, y = 1; тогда x = 0, y = 2 и так далее.

Когда у достигает конца, первый цикл увеличивается на 1, а затем снова проходит через 2-й цикл: x = 1, y = 0; x = 1, y = 1, x = 1, y = 2 и т. д. Это продолжается до завершения 1-го цикла.

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


Из второго цикла, продолжайте и удалите функцию трассировки, которую мы использовали для тестирования. Каждый раз, когда мы выполняем цикл, нам нужно создать маленькое изображение, имеющее ширину sizeWidth и высоту sizeHeight.

На этом небольшом снимке будет сделан снимок небольшой части изображения, начиная с левого верхнего угла и заканчивая правым нижним. «TempBitmapData» — это место, где мы нарисуем небольшую часть изображения. «SourceRect» — это прямоугольник, который мы будем использовать, чтобы указать, какая часть изображения будет скопирована.

Добавьте следующие строки во 2-й цикл и сохраните файл:

1
2
3
4
5
6
7
8
//1 temporary bitmapdata
var tempBitmapData:BitmapData = new BitmapData (sizeWidth, sizeHeight);
 
//1 temporary rectangle (x,y,width,height)
//we pass i * sizeWidth for the x parameter & i * sizeHeight for the y parameter
//and the sizeWidth & sizeHeight for the width and height parameters.
var sourceRect:Rectangle = new Rectangle (i * sizeWidth, j * sizeHeight, sizeWidth, sizeHeight);
trace (sourceRect);//for testing

Проверьте фильм. Теперь он создает прямоугольник, который корректирует его x- и y-положения на каждой итерации.

Как видите, в первом примере показано x = 0, y = 0, а следующее — x = 0, y = 4. Это то, что мы используем для границ снимка, взятого из исходного изображения. Удалите функцию проверки, когда будете готовы двигаться дальше.


Затем мы используем метод BitmapData.copyPixels (), чтобы скопировать небольшой фрагмент изображения на основе sourceRect. Параметры для этого метода: растровое изображение для копирования, прямоугольник для копирования и точка назначения, куда мы его скопируем.

Добавьте следующую строку кода под объявлением sourceRect.

1
tempBitmapData.copyPixels ($bitmap.bitmapData, sourceRect, new Point);

Затем мы создаем одно временное растровое изображение для размещения только что скопированных данных BitmapData и одно временное значение Sprite для размещения этого растрового изображения.

Затем мы помещаем ссылку каждого Sprite на _pictureArray для доступа позже. После этого мы добавляем спрайт на сцену с той же координатой, с которой мы его скопировали, воссоздавая исходное изображение.

Затем мы смещаем изображение на centerWidth и centerHeight, чтобы правильно центрировать его на сцене.

Добавьте следующие строки кода и еще раз сохраните файл:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
//we then create 1 temporary bitmap to house the bitmapdata we just copied.
var tempBitmap:Bitmap = new Bitmap (tempBitmapData);
 
//and 1 temporary sprite to house the bitmap to enable interactivity.
var tempSprite:Sprite = new Sprite;
 
//we just add each box inside it’s own sprite to enable interactivity since bitmaps by themselves are not interactive.
tempSprite.addChild (tempBitmap);
 
//each sprite is added into the _pictureArray array for access later.
_pictureArray.push (tempSprite);
 
//then position each of them onto the stage.
//We add the center width & center height so image centers on the stage.
tempSprite.x = i * sizeWidth + centerWidth;
tempSprite.y = j * sizeHeight + centerHeight;
addChild (tempSprite);

Идите и проверьте это снова. Вы должны увидеть правильно выложенное изображение на сцене. Это даже не будет выглядеть, как будто оно разделено на 1250 частей.

Сразу после закрывающей скобки второго цикла, прежде чем мы закроем метод, добавьте следующую строку кода:

1
stage.addEventListener (MouseEvent.CLICK, blowWind);

Мы добавляем прослушиватель событий на сцену для прослушивания MouseEvent.CLICK. Это запустит анимацию, запустив функцию blowWind (), которую мы создадим на следующем шаге.

Ваш класс WindEffect должен выглядеть примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package
{
    import com.greensock.easing.Strong;
    import com.greensock.TweenLite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.net.URLRequest;
 
    public class WindEffect extends Sprite
    {
        private var _pictureArray:Array;
     
        public function WindEffect ($url:String)
        {
            loadPicture ($url);
        }
        private function loadPicture ($url:String):void
        {
            var loader:Loader = new Loader;
            loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);
            loader.load (new URLRequest ($url));
        }
 
        private function onLoadComplete (e:Event):void
        {
            createEffect (e.target.content);
        }
 
        private function createEffect ($bitmap:Bitmap):void
        {
            var centerWidth:Number = (stage.stageWidth — $bitmap.width) * .5;
            var centerHeight:Number = (stage.stageHeight — $bitmap.height) * .5;
 
            var numberOfColumns:uint = 50;
            var numberOfRows:uint = 25;
            var sizeWidth:uint = $bitmap.width / numberOfColumns;
            var sizeHeight:uint = $bitmap.height / numberOfRows;
            var numberOfBoxes:uint = numberOfColumns * numberOfRows;
            _pictureArray = [];
 
            for (var i:uint = 0; i < numberOfColumns; i++)
            {
                for (var j:uint = 0; j < numberOfRows; j++)
                {
                    var tempBitmapData:BitmapData = new BitmapData (sizeWidth, sizeHeight);
 
                    var sourceRect:Rectangle = new Rectangle (i * sizeWidth, j * sizeHeight, sizeWidth, sizeHeight);
     
                    tempBitmapData.copyPixels ($bitmap.bitmapData, sourceRect, new Point);
                    var tempBitmap:Bitmap = new Bitmap (tempBitmapData);
                    var tempSprite:Sprite = new Sprite;
                    tempSprite.addChild (tempBitmap);
                    _pictureArray.push (tempSprite);
                    tempSprite.x = i * sizeWidth + centerWidth;
                    tempSprite.y = j * sizeHeight + centerHeight;
                    addChild (tempSprite);
                }
            }
            stage.addEventListener (MouseEvent.CLICK, blowWind);
        }
    }
}

Начните с удаления прослушивателя событий MouseEvent.CLICK, поскольку нам нужно, чтобы это произошло только один раз. Добавьте следующие строки кода после метода createEffect ():

1
2
3
4
private function blowWind (e:MouseEvent):void
{
    stage.removeEventListener (MouseEvent.CLICK, blowWind);
}

Нам нужно пройти через все спрайты, которые мы назначили в _pictureArray, и анимировать их по отдельности.

TweenLite применяется для анимации всех частей вправо, как будто на них дует ветер.

Параметры: цель для анимации, продолжительность анимации, переменный объект, который содержит все свойства, а также значения, к которым вы хотите применить анимацию.

Например: TweenLite.to (цель, длительность, {x: 100, y: 100, вращение: 30, замедление: Strong.easeIn, onComplete: trace, onCompleteParams: [‘hello’]}) .

Два последних параметра в вышеприведенном примере используются для завершения анимации. Параметр onComplete вызывает функцию трассировки, а параметр onCompleteParams отправляет массив, содержащий строку ‘hello’, в функцию трассировки.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
for (var i:uint = 0; i < _pictureArray.length; i++)
{
    TweenLite.to (
    _pictureArray[i],
    getRandomInRange (.25, 2, false),
    {
        x: stage.stageWidth + 100,
        y:_pictureArray[i].y + getRandomInRange (-100, 100, false),//
        rotation: getRandomInRange (-90, 90),
        ease:Strong.easeIn,
        onComplete:removeSprite,
        onCompleteParams:[_pictureArray[i]]
    }
    );
}

В реальной реализации, когда мы вызываем TweenLite из цикла, мы назначаем цель как _pictureArray [текущая итерация].

Для продолжительности мы присваиваем значение для длины анимации случайному времени от 0,25 до 2 секунд.

Переменный объект содержит 5 свойств:

  • x: stage.stageWidth + 100, который будет анимировать свойство x спрайта.
  • y: _pictureArray [i] .y + getRandomRange (-100,100, false), который получит позицию y текущего спрайта и добавит случайное число от -100 до 100, чтобы дать анимации эффект расширения.
  • Вращение: getRandomRange (-90,90) поворачивает текущий спрайт в диапазоне от -90 до 90 градусов.
  • непринужденность: Strong.easeIn, которая заставляет подростка начинать медленно и внезапно ускоряться.
  • onComplete: removeSprite, который вызывает метод removeSprite, когда анимация завершена, а Sprite выключен.
  • onCompleteParams, который отправляет массив [_pictureArray [текущая итерация]] в качестве параметра для removeSprite.

Этот метод вызывается из TweenLite, когда анимация для конкретной анимации завершена. Мы просто удаляем Sprite из списка отображения, чтобы не было беспорядка. Добавьте следующие строки кода после метода blowWind ():

1
2
3
4
private function removeSprite ($sprite:Sprite):void
{
    removeChild ($sprite);
}

Я уверен, что вы знакомы с этим (если нет, Карлос Янез написал краткий совет по этому вопросу .) Моя версия имеет возможность возвращать либо целые числа (int, uint) или плавающие (дроби).

Добавьте следующие строки кода. Если вы используете FlashDevelop, вы можете сохранить его как пользовательский фрагмент, чтобы его можно было легко добавить в любой класс / проект. Я объявил его как открытый статический метод для полной доступности.

1
2
3
4
5
public static function getRandomInRange ($min:Number, $max:Number, $rounded:Boolean = true):Number
{
    if ($rounded) return Math.round (Math.random () * ($max — $min) + $min);
    else return Math.random () * ($max — $min) + $min;
}

Это оно! Запустите фильм. Если что-то не так, проверьте ваш код по классу WindEffect, который я включил в исходную загрузку.


Ключом к созданию классных эффектов является изучение и освоение классов манипулирования изображениями и анимации анимации, таких как TweenLite. Пожалуйста, не стесняйтесь оставлять заметки для любых комментариев, проблем или предложений. Спасибо за прочтение!