Это учебник от начального до среднего уровня, в котором я объясню основы системы частиц FLiNT. Мы также рассмотрим, как создать курсор мыши с игристой бомбой. Это то, что я создал и использовал в игре памяти, которую вы можете проверить здесь, если вам интересно.
Я предполагаю, что вы знакомы с ActionScript 3.0, базовым объектно-ориентированным программированием и настройкой пути к классам. Это все знания, которые легко можно найти на Activetuts +, однако, так что не расстраивайтесь, если вы еще не знаете всего этого.
С учетом сказанного, давайте начнем делать частицы!
Что такое FLINT?
Система частиц FLiNT — это библиотека, написанная на ActionScript 3 Ричардом Лордом . Он выпущен под лицензией MIT и является открытым исходным кодом. Другими словами, мистер Лорд оказывает нам всем огромную услугу.
Веб-страница проекта находится здесь: http://www.flintparticles.org/ .
ASDocs можно найти здесь: http://flintparticles.org/docs/ . Эта страница должна быть. Я предлагаю вам пойти туда несколько раз, чтобы немного лучше узнать план всех классов. Серьезно, используйте эту страницу . Будут времена, когда вы сможете хорошо следовать этому учебнику без него, но как только вы захотите узнать точные детали конструктора класса или чего-то подобного, вы должны пойти сюда и прочитать его.
Итак, что это делает? Это просто способ создания частиц. Частицы могут быть чем угодно от пикселя — или, возможно, доли пикселя — до больших растровых изображений. Самое замечательное в FLiNT — это многочисленные способы создания и управления этими частицами. FLiNT даже поддерживает рендеринг частиц в 3D, поддерживая Papervision 3D и Away3D.
В конечном счете, FLiNT — это инструмент программного дизайна. На мой взгляд, наилучшие результаты достигаются, когда вы визуализируете результат эффекта, который хотите создать, возможно, рисуя его в фотошопе. После этого вы просто конвертируете его в код. Чтобы сделать это, однако, вы, конечно, должны знать, с чего начать.
Проверьте страницу примеров FLiNT для царапины на поверхности того, что возможно с FLiNT: http://flintparticles.org/examples .
Кроме того, я хочу, чтобы вы ознакомились с потрясающим примером того, как успешно использовать его в качестве инструмента дизайна. Французская MMORPG под названием «Dofus» с неизвестным статусом производства имеет это в своем блоге, используя FLiNT: http://devblog.dofus.com/fr/billets/52-lumiere.html . Так просто, но потрясающе красиво!
Подготовка и настройка
Я буду работать над кодом для этого урока, используя FlashDevelop . Если вы не знаете, что это такое или еще не пробовали, я предлагаю вам сделать это. Это не только удивительный инструмент, но и бесплатный, поэтому вам действительно нечего терять. Если вы используете Flex / Flash Builder или Powerflasher FDT, я полагаю, что у вас не возникнет проблем с выполнением этого урока.
Если вы хотите работать только с Flash IDE и, возможно, с любым другим текстовым редактором по вашему выбору, вы все равно можете следовать без каких-либо проблем. Просто убедитесь, что вы понимаете, как все работает, когда вы устанавливаете класс документа вместо того, чтобы работать с кодом на временной шкале.
Поскольку мы имеем дело с библиотекой кода, вам необходимо правильно настроить ваш путь к классам.
Загрузите FLiNT здесь: http://code.google.com/p/flint-particle-system/downloads/list . На момент написания данного руководства последняя версия — 2.1.2. Загрузите либо файлы библиотеки swc, либо исходный код. Я буду использовать файлы SWC и импортировать их в свой проект FlashDevelop. Однако использование SWC-файлов с чистым кодом (без обычных «компонентов» Flash) поддерживается только в Flash CS4, поэтому убедитесь, что вы используете обычный исходный код, если используете CS3.
Если эта страница когда-нибудь изменится, вы всегда найдете ссылки на последние загрузки на главной странице проекта FLiNT.
Шаг 1: Создайте новый проект
Прежде всего, создайте новый проект Flash AS3 в Flash IDE. Сделайте это 600×400 пикселей, 30 кадров в секунду и установите цвет фона на что-то темно-серое, как # 4A4A4A. Установите класс документа в Main.
Сохраните этот проект в папке, в которой вы будете работать.
В FlashDevelop создайте новый проект Flash IDE. Введите имя пакета, если вы склонны, однако, этот учебник будет включать только несколько классов, поэтому нет необходимости в аккуратной организации.
Сохраните этот проект в той же папке, что и только что созданный вами fla, чтобы проект Flash нашел класс Main.
В FlashDevelop создайте новый класс Main, пусть он расширяет Sprite. Вот как выглядит мой класс Main.as в начале:
01
02
03
04
05
06
07
08
09
10
11
12
|
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
public function Main()
{
}
}
}
|
Если вы еще этого не сделали, сейчас самое время настроить путь к классам и поместить в него файлы org.flintparticles.
Шаг 2: Создание нашей кнопки включения / выключения
Есть веская причина для создания этой кнопки. Скорее всего, вы сталкиваетесь с ситуациями, когда хотите контролировать, где и когда вы начинаете и останавливаете эффекты частиц. Возможно, вы играете в стрелялку, и вам нужно, чтобы в стволе ружья было несколько частиц, когда они стреляют. Если я покажу вам, как управлять им с помощью кнопки, вы определенно сможете решить ее так, как вам больше нравится в вашем собственном проекте.
Во Flash выделите панель компонентов и перетащите компонент Button на сцену, поместив его в верхнюю середину, например:
Дайте ему имя экземпляра «кнопки». Используйте инспектор компонентов, чтобы изменить метку на «Показать / скрыть эффект».
Внутри нашего Main.as мы должны импортировать класс MouseEvent, добавить слушатель щелчка к кнопке и написать функцию слушателя, которая реагирует на щелчок. Наш Main.as должен выглядеть так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite
{
public function Main()
{
button.addEventListener(MouseEvent.CLICK, onButtonClick);
}
private function onButtonClick(e:MouseEvent):void
{
trace(«Button was clicked!»);
}
}
}
|
Проверьте фильм и убедитесь, что нажатие на кнопку отслеживает сообщение.
Шаг 3: Создание класса переключения
Создайте новый класс с именем MyParticles.as в том же каталоге, что и основной класс. Пусть это продлит Sprite. В классе Main создайте переменную myParticles типа MyParticles и создайте ее экземпляр, а затем добавьте ее на stage. Также установите для свойства mouseEnabled значение false. В противном случае средство визуализации в классе частиц будет блокировать щелчки на нашей кнопке переключения.
Наш план состоит в том, чтобы класс MyParticles содержал все элементы FLiNT, связанные с этим учебным пособием. Он также будет иметь открытую функцию запуска и остановки, а также булеву переменную isRunning, которая может быть истинной или ложной в зависимости от того, живы частицы или нет. Наша функция onButtonClick может затем проверить, работает ли она, и попросить ее запустить или остановить в зависимости от ее состояния. 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
25
26
27
28
29
30
31
32
33
|
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite
{
private var myParticles:MyParticles;
public function Main()
{
button.addEventListener(MouseEvent.CLICK, onButtonClick);
myParticles = new MyParticles();
myParticles.mouseEnabled = false;
addChild(myParticles);
}
private function onButtonClick(e:MouseEvent):void
{
//Check to see if particles are active
if (!myParticles.isRunning)
{
//Particles were not active, start them
myParticles.start();
} else
{
//Particles were already active, stop them
myParticles.stop();
}
}
}
}
|
Давайте также не забудем настроить функции start и stop в нашем классе MyParticles. Также мы должны установить публичный геттер isRunning. Вот так:
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
|
package
{
import flash.display.Sprite;
public class MyParticles extends Sprite
{
private var _isRunning:Boolean;
public function MyParticles()
{
_isRunning = false;
}
public function start():void
{
trace(«Particles started»);
_isRunning = true;
}
public function stop():void
{
trace(«Particles stopped»);
_isRunning = false;
}
public function get isRunning():Boolean
{
return _isRunning;
}
}
}
|
Протестируйте фильм, чтобы увидеть, что переключение работает как надо.
Анатомия системы частиц FLiNT, часть 1
Давайте немного сосредоточимся на теории и посмотрим, как работает система FLiNT.
Абсолютный минимум элементов, необходимых для отображения некоторых частиц:
- Рендер
- Эмитент
- Счетчик
- Инициализатор частиц
- Инициализатор позиции
Рендерер
Рендерер — это то, что рисует частицы на экране. На момент написания этого руководства текущая версия FLiNT имела пять различных 2D-рендеров:
- BitmapRenderer
- DisplayObjectRenderer
- PixelRenderer
- BitmapLineRenderer
- VectorLineRenderer
Они работают по-разному, но то, как они работают по-разному, я не буду подробно освещать. Достаточно сказать, что BitmapRenderer рисует в одном растровом изображении, а затем обновляет его, что, как правило, быстрее, чем DisplayObjectRenderer, который позволяет Flash обновлять каждую частицу отдельно как различные объекты. Для этого урока мы будем использовать только BitmapRenderer.
Рендерер расширяет SpriteRendererBase, который в свою очередь расширяет Sprite. Это подводит нас к визуальному потоку нашего урока:
- Основной класс документов — наша сцена
- Мы добавляем наши sprit-расширяющие «myParticles» на сцену, используя addChild
- Класс MyParticles добавляет средство визуализации в собственный список отображения с помощью addChild.
- В MyParticles мы позволим рендереру добавлять наш эмиттер к себе (рендереру)
- Излучатель испускает частицы внутри рендерера, который, в свою очередь, добавляет их в список отображения.
Эмитент
Есть только два излучателя, один для 2D и один для 3D. Излучатель — именно то, на что это похоже; объект, который испускает частицы на свой родительский рендер. Вы можете добавить действия , действия и инициализаторы для эмитента. Подробнее об этом позже.
Эмиттер добавляется к рендереру для отображения. Рендерер может иметь несколько эмиттеров, а эмиттер может быть добавлен к нескольким рендерерам.
Счетчик
Эмитенту нужен счетчик для излучения частиц. Это то, что решает, когда и сколько частиц испускать. Например:
1
|
emitter.counter = new Steady(50);
|
Это добавило бы постоянный счетчик к излучателю. Постоянный счетчик выбрасывает x количество частиц (в нашем случае 50) каждую секунду. Частицы испускаются равномерно в течение секунды. Счетчик импульсов будет излучать частицы с заданным интервалом. Другой полезный счетчик — Взрыв, который испускает частицы, но только один раз. Полезно, когда вам нужен взрывной эффект.
Инициализатор частиц
Нам нужно добавить инициализатор частиц в эмиттер, чтобы он что-то излучал. Инициализаторы частиц:
- SharedImage
- SharedImages
- ImageClass
- ImageClasses
SharedImage и SharedImages наиболее эффективны при использовании с BitmapRenderer. Другие лучше подходят для DisplayObjectRenderer. Разница между SharedImage и SharedImages заключается в том, что второй инициализатор поддерживает несколько изображений. Вы добавляете изображения в массив, который идет в конструктор инициализаторов, и вы можете добавить необязательный массив «weight», который решает, какой приоритет отдавать изображениям при их рандомизации. То же самое с ImageClass и ImageClasses.
Инициализатор позиции — зоны
Эмитент должен знать, где разместить частицы. Это делается путем добавления инициализатора позиции. Инициализатор положения берет Зону в своем конструкторе. Затем излучатель будет добавлять частицы в случайных положениях в пределах зоны. Есть несколько типов зон, я упомяну несколько здесь:
- PointZone
- LineZone
- RectangleZone
- DiscZone
- мультизональные
- BitmapDataZone
Точечная зона — именно то, на что это похоже; он берет объект Point в своем конструкторе и заставляет частицы появляться только в этой точке. LineZone берет два объекта Point и создает частицы вдоль линии, созданной между двумя точками. RectangleZone испускает частицы внутри прямоугольника, DiscZone испускает частицы внутри круга. MultiZone объединяет несколько разных зон и делает их похожими на одну.
BitmapDataZone — моя любимая зона из-за ее особой способности: он принимает объект BitmapData и заставляет частицы появляться в любом пикселе этого BitmapData, который не является прозрачным. Другими словами, вы можете взять изображение и использовать его в качестве чертежа того, где вы хотите, чтобы пиксели появлялись.
Конечно, есть и другие способы добиться этого, например, с помощью GreyscaleZone, который действует аналогично BitmapDataZone. Разница в том, что он дает пикселям различный вес; другая вероятность появления частицы в этом пикселе. Черные пиксели никогда не порождают какие-либо частицы, но другие делают, и чем светлее они, тем выше вероятность появления пикселя там.
Надеемся, вы сможете увидеть невероятную гибкость и мощь системы частиц FLiNT, и это только царапает поверхность! Теперь похлопайте себя по спине, чтобы справиться с таким количеством информации. Давайте вернемся к работе!
Шаг 4: Импорт необходимых классов
На этом коротком шаге мы просто импортируем следующие классы:
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
|
import flash.display.Sprite;
import flash.geom.Point;
import flash.geom.Rectangle;
import org.flintparticles.common.actions.Age;
import org.flintparticles.common.actions.Fade;
import org.flintparticles.common.counters.Steady;
import org.flintparticles.common.displayObjects.Dot;
import org.flintparticles.common.displayObjects.RadialDot;
import org.flintparticles.common.displayObjects.Rect;
import org.flintparticles.common.energyEasing.Exponential;
import org.flintparticles.common.initializers.Lifetime;
import org.flintparticles.common.initializers.SharedImage;
import org.flintparticles.common.initializers.SharedImages;
import org.flintparticles.twoD.actions.Accelerate;
import org.flintparticles.twoD.actions.Collide;
import org.flintparticles.twoD.actions.MouseGravity;
import org.flintparticles.twoD.actions.Move;
import org.flintparticles.twoD.actions.RandomDrift;
import org.flintparticles.twoD.actions.RotateToDirection;
import org.flintparticles.twoD.actions.ScaleAll;
import org.flintparticles.twoD.emitters.Emitter2D;
import org.flintparticles.twoD.initializers.Position;
import org.flintparticles.twoD.initializers.Velocity;
import org.flintparticles.twoD.renderers.BitmapRenderer;
import org.flintparticles.twoD.zones.LineZone;
import org.flintparticles.twoD.zones.PointZone;
import org.flintparticles.twoD.zones.RectangleZone;
|
Проще, если мы импортируем их все сейчас, а не по одному, пока мы продвигаемся.
Предупреждение: если вы используете FlashDevelop, у вас могут возникнуть проблемы с автоматическим импортом. У меня возникли проблемы, когда я набираю определенный тип инициализатора или рендерера, который существует как в 2D, так и в 3D версиях, и позволяет FlashDevelop импортировать этот класс. Убедитесь, что FlashDevelop импортировал 2D-версию, если вы используете 2D, и наоборот с 3D! Причина этого заключается в том, что многие классы имеют одинаковые имена в пакетах 2D и 3D.
Шаг 5: Создание рендерера
Нам нужно создать рендер для отображения наших частиц в нашем классе MyParticles. Добавьте приватную переменную ‘renderer’ типа BitMapRenderer.
Создайте это так:
1
|
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
|
Конструктор BitmapRenderer принимает прямоугольник в качестве холста. Это холст, на который визуализатору разрешено рисовать. Частицы могут существовать (и потреблять мощность процессора) как на холсте, так и за его пределами, но будут отображаться только в пределах холста. Определяя прямоугольник как 600 на 400, а х / у как 0/0, мы по существу создаем холст размером и положением нашей сцены. Чем больше холст, тем выше производительность, поэтому обычно старайтесь поддерживать размер как можно меньше для достижения желаемого результата.
В конструкторе есть второй необязательный параметр ‘сглаживание’, который представляет собой тот же тип сглаживания, который вы можете распознать из класса Bitmap, или свойства изображения, импортированные в библиотеку в IDE Flash. По умолчанию для сглаживания установлено значение false, и в этом уроке мы больше не будем с ним разбираться, но полезно знать, где к нему обращаться.
В завершение добавьте средство визуализации в список отображения, вызвав addChild. Наш класс MyParticles теперь должен выглядеть так:
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
|
package
{
// IMPORTS HERE — NOT SHOWN //
public class MyParticles extends Sprite
{
private var _isRunning:Boolean;
private var renderer:BitmapRenderer;
public function MyParticles()
{
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
}
public function start():void
{
trace(«Particles started»);
_isRunning = true;
}
public function stop():void
{
trace(«Particles stopped»);
_isRunning = false;
}
public function get isRunning():Boolean
{
return _isRunning;
}
}
}
|
Шаг 6: Создание эмиттера, счетчика и инициализаторов
Создайте частный var ’emitter’ типа Emitter2D и создайте его экземпляр. Также установите свойство счетчика эмиттера на новый Steady с частотой 50 частиц в секунду, например:
1
2
|
emitter = new Emitter2D();
emitter.counter = new Steady(50);
|
Теперь посмотрим на инициализатор SharedImage. Этот инициализатор определяет, как выглядят реальные частицы, которые мы будем генерировать. Я покажу вам код, чтобы написать сначала:
1
|
emitter.addInitializer(new SharedImage(new Dot()));
|
Как видите, у эмиттера есть функция addInitializer, которая принимает объект «Initializer» в качестве параметра. SharedImage расширяет класс InitializerBase, который реализует интерфейс Initializer.
Инициализатор SharedImage принимает DisplayObject в качестве параметра конструктора. Здесь мы даем ему новую точку. Класс Dot является частью библиотеки FLiNT и представляет собой просто небольшой экранный объект с точками, расширяющий Shape. Конструктор Dot принимает Number, который является радиусом точки, и по умолчанию он равен 1. FLiNT поставляется с несколькими различными классами экранных объектов, которые во многих случаях очень полезны. Обратите внимание, что есть возможность добавлять другие изображения из вашей флеш-библиотеки. Можно даже добавить анимированные фрагменты ролика в виде частиц и сделать их анимированными по мере их появления. Возможности практически безграничны!
Далее мы добавим инициализатор положения и зону, подобную этой:
1
2
3
|
var lineZone:LineZone = new LineZone(new Point(100, 300), new Point(500, 300));
var position:Position = new Position(lineZone);
emitter.addInitializer(position);
|
Мы определяем LineZone с двумя объектами Point, и значения x / y этих точек определяют линию.
Наконец, очень важный шаг — добавить эмиттер в рендерер :
1
|
renderer.addEmitter(emitter);
|
Наш класс должен выглядеть следующим образом (показаны только члены класса и конструктор):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
private var _isRunning:Boolean;
private var renderer:BitmapRenderer;
private var emitter:Emitter2D;
public function MyParticles()
{
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
emitter = new Emitter2D();
emitter.counter = new Steady(50);
emitter.addInitializer(new SharedImage(new Dot()));
var lineZone:LineZone = new LineZone(new Point(100, 300), new Point(500, 300));
var position:Position = new Position(lineZone);
emitter.addInitializer(position);
renderer.addEmitter(emitter);
}
|
Шаг 7: Запуск нашего двигателя частиц
Поскольку мы уже настроили общедоступные функции запуска и остановки, теперь мы можем легко подключить их к системе частиц, чтобы мы могли наконец начать видеть некоторые частицы.
В функцию запуска просто добавьте emitter.start (); — и в функцию остановки добавьте emitter.stop (); , Может ли быть легче?
Теперь у вас должна быть система частиц, которая может активироваться и деактивироваться нажатием кнопки переключения:
Хотя это не так интересно, не так ли? У нас есть линия, где эмиттер излучает точки бесконечно до конца времени или до того, как ваш процессор заработает, в зависимости от того, что наступит раньше. Говоря о мощности процессора, попробуйте временно установить постоянный счетчик на 5000 вместо 50. Когда вы попробуете это, вы должны заметить чрезмерную загрузку процессора. Или попробуйте это, или поверьте мне на слово и подумайте, что вы должны спланировать, как будут использоваться ваши частицы, чтобы они не закончили работу вашего флэш-приложения.
Хорошо, достаточно скучного и ответственного разговора, давайте подумаем о некоторых быстрых улучшениях и сделаем их
- Мы могли бы использовать некоторое движение на частицах, чтобы оживить
- Нам нужно, чтобы частицы вымерли через некоторое время, а не жили бесконечно долго
- Мы могли бы заставить частицы выглядеть отличными друг от друга, чтобы добавить больше разнообразия
- Чтобы добавить еще больше разнообразия, мы могли бы заставить частицы изменяться со временем
Для выполнения всех этих изменений нам нужно добавить как инициализаторы, так и действия. Давайте посмотрим на еще немного теории. Не волнуйся, на этот раз будет короче.
Анатомия системы частиц FLiNT, часть 2
Когда мы хотим добавить или изменить что-то в нашей системе частиц, это вопрос добавления инициализаторов и действий. Цитирование документации FLiNT:
Система частиц во Флинте создается путем объединения излучателей, счетчиков, инициализаторов, действий, действий и средств визуализации.
действия
Вы уже знаете об эмиттерах, счетчиках, инициализаторах и рендерерах. Инициализаторы контролируют, как инициализируются частицы. Действия влияют на частицы с течением времени. Посмотрите на список 2D-действий в документации, есть множество различных видов действий. Есть также несколько в общей упаковке. Вот несколько важных, о которых стоит знать:
- Ускорение — добавляет постоянное ускорение частиц.
- Age — делает возраст частиц, об этом позже.
- BoundingBox — используйте это, чтобы удерживать частицы внутри определенной области, чтобы они не выходили из нее.
- Fade — заставляет частицу исчезать со временем.
- Трение — как это звучит. Используйте это, чтобы замедлить частицу при ее движении.
- Linear / QuadraticDrag — Они также замедляют частицы, но с силой, пропорциональной скорости частицы. Сравните с трением, которое замедляет частицу с постоянной силой, а не пропорционально.
- Переместить — это важное действие. Он должен быть добавлен для обновления положения частиц. Без этого ваши частицы никогда не будут двигаться!
- RandomDrift — действие для особых случаев использования, но одно из моих любимых. Это заставляет частицу случайно дрейфовать во всех направлениях в пределах значений, которые вы определяете сами.
- ScaleImage — Используется для масштабирования частиц по размеру, как увеличивая, так и уменьшая.
мероприятия
Действия подобны действиям, но для самого эмитента. Там только три 2D-мероприятия; FollowMouse, который заставляет излучатель следовать за мышью, MoveEmitter, который перемещает излучатель с заданной скоростью, и RotateEmitter, который просто вращает излучатель. Ничего слишком сложного в этом отделе!
ZonedAction; умное действие
Действие ZonedAction принимает как Действие, так и Зону в своем конструкторе. Что он делает, это действительно здорово — он применяет Действие к частицам, но только если они находятся внутри Зоны. Хотите создать водопад? Сделайте так, чтобы ваши частицы двигались к капле, а на капле вы выполняете зонированное действие, чтобы вода ускорялась вниз. С этим действием вы становитесь чем-то вроде режиссера.
Давайте приступим к этим изменениям!
Шаг 8: Заставить частицы двигаться
Это должно быть просто, как пирог. Так как мы знаем, что частицы должны будут двигаться, мы начнем с добавления действия перемещения. Мы можем установить начальную скорость с помощью инициализатора, добавить некоторое отрицательное ускорение и завершить его случайным дрейфом, например так:
1
2
3
4
5
6
7
8
|
emitter.addAction(new Move());
var velocityPointZone:PointZone = new PointZone(new Point(0, -150));
emitter.addInitializer(new Velocity(velocityPointZone));
emitter.addAction(new Accelerate(0, 45));
emitter.addAction(new RandomDrift(50, 50));
|
Мы уже очень сильно нагружаем процессор, поэтому не стесняйтесь снижать счетчик Steady до 10 или 20 или более, если ваш компьютер готов к выполнению этой задачи.
Объяснение кода, который мы только что написали:
Движение Move говорит само за себя.
Инициализатор Velocity берет Зону в своем конструкторе. Мы задаем ему точку с отрицательными 150 пикселями по оси Y, которая становится скоростью каждого отдельного пикселя. Инициализатор Velocity использует случайную точку в пределах зоны, которую вы определяете. В случае точки это может быть только эта точка. И здесь очень важно отметить, что определенная Зона находится в перспективе частицы, а не излучателя, средства визуализации или чего-либо еще. Если вы хотите контролировать точную скорость частиц, используйте PointZone. Для более случайного поведения используйте что-то большее. LineZone часто может быть достаточным для некоторой случайности, но RectangleZone включит еще более случайное поведение.
Действие «Ускорение» также как бы говорит само за себя — мы говорим, что оно должно постоянно ускоряться со значением, равным 45, по оси Y, поэтому оно идет вниз.
RandomDrift также легко понять — вы определяете максимальную величину смещения как по оси x, так и по оси y.
Наш конструктор MyParticle.as теперь должен выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
emitter = new Emitter2D();
emitter.counter = new Steady(20);
emitter.addInitializer(new SharedImage(new Dot()));
var lineZone:LineZone = new LineZone(new Point(100, 300), new Point(500, 300));
var position:Position = new Position(lineZone);
emitter.addInitializer(position);
renderer.addEmitter(emitter);
emitter.addAction(new Move());
var velocityPointZone:PointZone = new PointZone(new Point(0, -150));
emitter.addInitializer(new Velocity(velocityPointZone));
emitter.addAction(new Accelerate(0, 45));
emitter.addAction(new RandomDrift(50, 50));
|
Прежде чем перейти к следующему шагу, не стесняйтесь попробовать и закомментировать различные действия / инициализатор, чтобы увидеть, как они действуют индивидуально.
Шаг 9: Убить наших частиц
Поскольку мы знаем, что частицы потребляют мощность процессора, если они никогда не удаляются, нам нужен способ их удаления. Есть несколько способов сделать это:
- Использование действия DeathZone. Создайте зону и подайте ее объекту DeathZone Action, и она убьет любую частицу, попадающую в эту зону.
- Использование действия DeathSpeed. Вы можете определить ограничение скорости, которое убивает частицы, когда они пересекают его.
- Мой предпочтительный способ, в данном случае — инициализатор LifeTime и действие Age. LifeTime просто устанавливает время жизни частицы. Подобно тому, как действие Move необходимо для реального перемещения частиц, так и Age необходим, чтобы частицы старели и, наконец, умирали в конце своей жизни. Это также необходимо, когда вы используете действие Fade.
Давайте перейдем к инициализатору LifeTime и действию Age, например:
1
2
|
emitter.addInitializer(new Lifetime(5));
emitter.addAction(new Age());
|
Это просто удаляет частицы через 5 секунд жизни. Эффект не такой красивый, поэтому давайте добавим действие Fade:
1
|
emitter.addAction(new Fade());
|
Уже выглядит намного лучше! Если вы выполняли какую-либо анимацию, вы можете подумать: «Я бы хотел применить другое смягчение к этому исчезновению!». Ладно, может быть, вы не совсем так думаете, но я думаю, потому что я знаю, насколько крутой FLiNT — вы можете применить различные упрощения к действию Age в его конструкторе! Измените нашу предыдущую строку кода Age на это:
1
|
emitter.addAction(new Age(Exponential.easeIn));
|
Это ослабление удерживает частицу в «хорошем здоровье» намного дольше и в конце быстро исчезает. Если вы просматриваете пакеты, вы могли заметить, что это часть пакета energyEasing. Эти ослабления являются модифицированными формами смягчающих уравнений Роберта Пеннера специально для действия Age. Существует также пакет, называемый просто easing, который представляет собой замедления, сделанные для счетчика TimePeriod.
Шаг 10: Создание разных частиц
Прямо сейчас у нас есть частицы, которые движутся по определенной схеме с некоторой случайной вариацией, и в конечном итоге они исчезают и умирают. Давайте добавим в наш излучатель различные виды частиц разных цветов!
Что мы сделаем, это создадим четыре разных типа частиц и используем инициализатор SharedImages вместо инициализатора SharedImage. Обратите внимание на отсутствие символа «с» на конце. Крутая вещь об этом — дополнительный массив весов, который я упомянул в первом разделе теории в этом уроке. Это просто массив, из которого инициализатор читает. Например, первый элемент в массиве изображений относится к первому элементу в массиве весов. Если первый элемент в массиве весов имеет значение 10, а второй — значение 1, то первое изображение в массиве изображений будет иметь в 10 раз больший шанс появления, чем второе изображение.
Здесь мы будем использовать только собственные классы экранных объектов FLiNT. Кроме того, прошу прощения за мой плохой выбор цветов. Идите вперед и перейдите к строке, где у вас есть инициализатор SharedImage и удалите его. Вместо этого введите следующее:
01
02
03
04
05
06
07
08
09
10
11
|
var imageArray:Array = new Array();
var weightArray:Array = new Array();
imageArray.push(new Dot(2, 0xFFEEDD));
weightArray.push(10);
imageArray.push(new Dot(5, 0xAAEEDD));
weightArray.push(10);
imageArray.push(new RadialDot(5, 0xAAFFFF));
weightArray.push(1);
imageArray.push(new Rect(8, 3, 0xFFAAFF));
weightArray.push(1);
emitter.addInitializer(new SharedImages(imageArray, weightArray));
|
Так что мы здесь делаем? Мы создаем два массива, один для хранения изображений, а другой — для весов, соответствующих изображениям. Я мог бы разделить их в коде, но я поместил их в чередующиеся строки, чтобы быстро увидеть, какой вес относится к какому изображению. Прямо сейчас Rect и RadialDot не будут появляться так же, как два других.
Вот как выглядит конструктор на данный момент:
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
|
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
emitter = new Emitter2D();
emitter.counter = new Steady(20);
var imageArray:Array = new Array();
var weightArray:Array = new Array();
imageArray.push(new Dot(2, 0xFFEEDD));
weightArray.push(10);
imageArray.push(new Dot(5, 0xAAEEDD));
weightArray.push(10);
imageArray.push(new RadialDot(5, 0xAAFFFF));
weightArray.push(1);
imageArray.push(new Rect(20, 10, 0xFFAAFF));
weightArray.push(1);
emitter.addInitializer(new SharedImages(imageArray, weightArray));
var lineZone:LineZone = new LineZone(new Point(100, 300), new Point(500, 300));
var position:Position = new Position(lineZone);
emitter.addInitializer(position);
renderer.addEmitter(emitter);
emitter.addAction(new Move());
var velocityPointZone:PointZone = new PointZone(new Point(0, -150));
emitter.addInitializer(new Velocity(velocityPointZone));
emitter.addAction(new Accelerate(0, 45));
emitter.addAction(new RandomDrift(50, 50));
emitter.addInitializer(new Lifetime(5));
emitter.addAction(new Age(Exponential.easeIn));
emitter.addAction(new Fade());
|
Шаг 11: изменение частиц с течением времени
Теперь давайте попробуем изменить частицы с течением времени, чтобы добавить еще больше разнообразия. Мы будем использовать действие ScaleAll, чтобы частицы со временем меняли размер. Существует действие ScaleImage, которое мы могли бы использовать, но ScaleAll также масштабирует радиус столкновения и массу. Есть два действия — ChangeCollisionRadius и ChangeMass — которые используются для этого отдельно для этой цели, если это необходимо. Мы будем использовать Collide, чтобы частицы сталкивались друг с другом. Однако обратите внимание, что это не физический движок, и он не даст вам схожей точности, скорее он будет сосредоточен на производительности, а не на точном моделировании. Мы будем использовать RotateToDirection, чтобы убедиться, что наши прямоугольники указывают, куда они идут. Наконец, мы добавим что-то действительно классное, MouseGravity. Давайте кратко рассмотрим смехотворно небольшое количество кода:
1
2
3
4
|
emitter.addAction(new ScaleAll(1, 5));
emitter.addAction(new Collide(3));
emitter.addAction(new RotateToDirection());
emitter.addAction(new MouseGravity(200, renderer));
|
Некоторые вещи здесь заслуживают объяснения. ScaleAll следует за старением частицы, определенной в инициализаторе LifeTime. Прямо сейчас мы просим его масштабировать от 100% при рождении до 500% в конце жизни. Параметр Collide — это количество энергии, сохраняемой после столкновения, 1 составляет 100%. Установка этого значения в 3 приведет к результатам, когда частицы приобретают больше энергии после столкновения. Наконец, MouseGravity имеет два параметра, которые вы должны установить, и один необязательный. Во-первых, насколько сильна гравитация. Второй, «рендерер» — это объект DisplayObject, в координаты которого преобразуется позиция мыши.
Это последний шаг, который мы выполняем с этой системой частиц, поэтому я хотел бы упомянуть одну очень полезную функцию в эмиттерах — функцию runAhead. Вы просто вызываете эту функцию на эмиттере и определяете время для запуска эмиттера. Это полезно, когда у вас есть излучатель, испускающий частицы, которые покрывают большую площадь, и вы не хотите ждать, пока частицы заполнят эту область. В этом случае вы заставите его работать с этой функцией. Функция принимает два параметра, время и FPS. Добавьте эту строку кода в функцию emitter.start () в нашей публичной функции запуска в нашем классе MyParticles:
1
|
emitter.runAhead(5, 30);
|
Это заставит наш излучатель запускаться вперед на 5 секунд при 30 кадрах в секунду при каждом его запуске, поэтому экран уже заполнен частицами, когда мы его запускаем!
Теперь взгляните на то, что выглядит как межгалактическая битва, которую вы можете направить с помощью мыши.
Вот как выглядит MyParticles.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
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
package
{
import flash.display.Sprite;
import flash.geom.Point;
import flash.geom.Rectangle;
import org.flintparticles.common.actions.Age;
import org.flintparticles.common.actions.Fade;
import org.flintparticles.common.counters.Steady;
import org.flintparticles.common.displayObjects.Dot;
import org.flintparticles.common.displayObjects.RadialDot;
import org.flintparticles.common.displayObjects.Rect;
import org.flintparticles.common.energyEasing.Exponential;
import org.flintparticles.common.initializers.Lifetime;
import org.flintparticles.common.initializers.SharedImage;
import org.flintparticles.common.initializers.SharedImages;
import org.flintparticles.twoD.actions.Accelerate;
import org.flintparticles.twoD.actions.Collide;
import org.flintparticles.twoD.actions.MouseGravity;
import org.flintparticles.twoD.actions.Move;
import org.flintparticles.twoD.actions.RandomDrift;
import org.flintparticles.twoD.actions.RotateToDirection;
import org.flintparticles.twoD.actions.ScaleAll;
import org.flintparticles.twoD.emitters.Emitter2D;
import org.flintparticles.twoD.initializers.Position;
import org.flintparticles.twoD.initializers.Velocity;
import org.flintparticles.twoD.renderers.BitmapRenderer;
import org.flintparticles.twoD.zones.LineZone;
import org.flintparticles.twoD.zones.PointZone;
import org.flintparticles.twoD.zones.RectangleZone;
public class MyParticles extends Sprite
{
private var _isRunning:Boolean;
private var renderer:BitmapRenderer;
private var emitter:Emitter2D;
public function MyParticles()
{
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
emitter = new Emitter2D();
emitter.counter = new Steady(20);
var imageArray:Array = new Array();
var weightArray:Array = new Array();
imageArray.push(new Dot(2, 0xFFEEDD));
weightArray.push(10);
imageArray.push(new Dot(5, 0xAAEEDD));
weightArray.push(10);
imageArray.push(new RadialDot(5, 0xAAFFFF));
weightArray.push(1);
imageArray.push(new Rect(20, 10, 0xFFAAFF));
weightArray.push(1);
emitter.addInitializer(new SharedImages(imageArray, weightArray));
var lineZone:LineZone = new LineZone(new Point(100, 300), new Point(500, 300));
var position:Position = new Position(lineZone);
emitter.addInitializer(position);
renderer.addEmitter(emitter);
emitter.addAction(new Move());
var velocityPointZone:PointZone = new PointZone(new Point(0, -150));
emitter.addInitializer(new Velocity(velocityPointZone));
emitter.addAction(new Accelerate(0, 45));
emitter.addAction(new RandomDrift(50, 50));
emitter.addInitializer(new Lifetime(5));
emitter.addAction(new Age(Exponential.easeIn));
emitter.addAction(new Fade());
emitter.addAction(new ScaleAll(1, 5));
emitter.addAction(new Collide(3));
emitter.addAction(new RotateToDirection());
emitter.addAction(new MouseGravity(200, renderer));
}
public function start():void
{
trace(«Particles started»);
emitter.start();
emitter.runAhead(10, 30);
_isRunning = true;
}
public function stop():void
{
trace(«Particles stopped»);
emitter.stop();
_isRunning = false;
}
public function get isRunning():Boolean
{
return _isRunning;
}
}
}
|
И вот как выглядит наш 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
25
26
27
28
29
30
31
32
33
|
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite
{
private var myParticles:MyParticles;
public function Main()
{
button.addEventListener(MouseEvent.CLICK, onButtonClick);
myParticles = new MyParticles();
myParticles.mouseEnabled = false;
addChild(myParticles);
}
private function onButtonClick(e:MouseEvent):void
{
//Check to see if particles are active
if (!myParticles.isRunning)
{
//Particles were not active, start them
myParticles.start();
} else
{
//Particles were already active, stop them
myParticles.stop();
}
}
}
}
|
Конец введения — Начало создания основного эффекта
То, что мы сделали до сих пор, это просто играем вокруг и добавляем материал в систему частиц, чтобы узнать, как ее использовать. Это может быть не очень полезно или красиво, но если вы прошли через это, вы должны лучше понять отношения между эмиттерами, средствами визуализации, инициализаторами и действиями.
Есть еще много вещей, которые нужно изучить, поэтому убедитесь, что вы используете документацию и экспериментируйте.
Итак, помните начало этого урока? Вам обещали научиться превращать курсор мыши в бомбу с горящим взрывателем, который использует FLiNT для частиц. Мы наберем темп для этой части, теперь, когда вы прочли это длинное вступление, вы стали опытными программистами FLiNT.
Шаг 12: отключаем курсор мыши
Сначала мы создадим новый класс под названием AnimatedCursor. Пусть ваш Main.as указывает на это вместо класса Myparticles. Кроме того, нам не нужны мышиные дети, так как это сделает нашу бомбу графической, блокируя любой щелчок мышью. Нам также нужно обновить наш класс курсора с помощью позиции мыши. Код будет выглядеть так:
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
|
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite
{
private var animatedCursor:AnimatedCursor;
public function Main()
{
button.addEventListener(MouseEvent.CLICK, onButtonClick);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
animatedCursor = new AnimatedCursor();
animatedCursor.mouseEnabled = false;
animatedCursor.mouseChildren = false;
addChild(animatedCursor);
}
private function onMouseMove(e:MouseEvent):void
{
animatedCursor.updateCursorPosition(mouseX, mouseY);
}
private function onButtonClick(e:MouseEvent):void
{
if (!animatedCursor.isRunning)
{
animatedCursor.start();
} else
{
animatedCursor.stop();
}
}
}
}
|
Во-вторых, мы позволим классу AnimatedCursor позаботиться о переключении курсора мыши с изображением бомбы. Импортируйте изображение бомбы в ваш файл Flash и сделайте его MovieClip, который вы экспортируете для ActionScript как «Бомба», как Sprite. Вы должны найти файл smallbomb.png в zip-файле этого руководства.
Код для AnimatedCursor.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
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
|
package
{
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.ui.Mouse;
import org.flintparticles.common.counters.Steady;
import org.flintparticles.twoD.emitters.Emitter2D;
import org.flintparticles.twoD.renderers.BitmapRenderer;
public class AnimatedCursor extends Sprite
{
private var _isRunning:Boolean;
private var renderer:BitmapRenderer;
private var emitter:Emitter2D;
private var bombImage:Sprite;
public function AnimatedCursor()
{
bombImage = new Bomb();
addChild(bombImage);
bombImage.visible = false;
_isRunning = false;
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
addChild(renderer);
emitter = new Emitter2D();
}
public function updateCursorPosition(x:int, y:int):void
{
bombImage.x = x;
bombImage.y = y;
}
public function start():void
{
Mouse.hide();
bombImage.visible = true;
emitter.start();
_isRunning = true;
}
public function stop():void
{
Mouse.show();
bombImage.visible = false;
emitter.stop();
_isRunning = false;
}
public function get isRunning():Boolean
{
return _isRunning;
}
}
}
|
Шаг 13: Добавление и дизайн частиц
Вот что мы будем делать:
- Добавьте BlurFilter к нашему рендереру.
- Добавьте постоянный счетчик со значением 30
- Создайте массив изображений и массив весов для помещения в инициализатор SharedImages
- Создайте три объекта Line и поместите их в массив, затем добавьте веса в массив весов.
- Добавьте ColorInit, чтобы получить желтые / красноватые частицы огня
- Добавить скорость, основанную на DiscSectorZone
- Добавить LifeTime
- Добавьте Позицию с небольшим смещением, чтобы убедиться, что частицы появляются в конце предохранителя на изображении бомбы.
- Заставьте частицы стареть с кубической легкостью.
- Добавить Move, Fade и RotateToDirection
- Добавьте нашу первую активность — FollowMouse
- При остановке наших частиц очистите bitmapData нашего рендерера, чтобы избежать появления огненных пятен
Вот код:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
|
package
{
import flash.display.Sprite;
import flash.filters.BlurFilter;
import flash.filters.BitmapFilterQuality;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Mouse;
import org.flintparticles.common.actions.Age;
import org.flintparticles.common.actions.Fade;
import org.flintparticles.common.counters.Steady;
import org.flintparticles.common.displayObjects.Line;
import org.flintparticles.common.energyEasing.Cubic;
import org.flintparticles.common.initializers.ColorInit;
import org.flintparticles.common.initializers.Lifetime;
import org.flintparticles.common.initializers.SharedImages;
import org.flintparticles.twoD.actions.Move;
import org.flintparticles.twoD.actions.RotateToDirection;
import org.flintparticles.twoD.activities.FollowMouse;
import org.flintparticles.twoD.emitters.Emitter2D;
import org.flintparticles.twoD.initializers.Position;
import org.flintparticles.twoD.initializers.Velocity;
import org.flintparticles.twoD.renderers.BitmapRenderer;
import org.flintparticles.twoD.zones.DiscSectorZone;
import org.flintparticles.twoD.zones.PointZone;
public class AnimatedCursor extends Sprite
{
private var _isRunning:Boolean;
private var renderer:BitmapRenderer;
private var emitter:Emitter2D;
private var bombImage:Sprite;
private var blurFilter:BlurFilter;
public function AnimatedCursor()
{
bombImage = new Bomb();
addChild(bombImage);
bombImage.visible = false;
_isRunning = false;
blurFilter = new BlurFilter(6, 6, BitmapFilterQuality.HIGH);
renderer = new BitmapRenderer(new Rectangle(0, 0, 600, 400));
renderer.addFilter(blurFilter);
addChild(renderer);
emitter = new Emitter2D();
renderer.addEmitter(emitter);
emitter.counter = new Steady(30);
var imageArray:Array = new Array();
imageArray.push(new Line(1));
imageArray.push(new Line(2));
imageArray.push(new Line(3));
var imageWeights:Array = [4, 6, 3];
emitter.addInitializer( new SharedImages(imageArray, imageWeights));
emitter.addInitializer( new ColorInit( 0xFFFFCC00, 0xFFF9F7BF ) );
emitter.addInitializer( new Velocity( new DiscSectorZone( new Point( 0, 0 ), 90, 30, -1.5, 0.5 ) ) );
emitter.addInitializer( new Lifetime( 0.15, 0.5 ) );
emitter.addInitializer( new Position(new PointZone(new Point(8, -6))));
emitter.addAction( new Age(Cubic.easeIn) );
emitter.addAction( new Move() );
emitter.addAction( new Fade() );
emitter.addAction( new RotateToDirection() );
emitter.addActivity( new FollowMouse( renderer ) );
}
public function updateCursorPosition(x:int, y:int):void
{
bombImage.x = x;
bombImage.y = y;
}
public function start():void
{
Mouse.hide();
bombImage.visible = true;
emitter.start();
_isRunning = true;
}
public function stop():void
{
Mouse.show();
bombImage.visible = false;
emitter.stop();
renderer.bitmapData.fillRect(renderer.bitmapData.rect, 0);
_isRunning = false;
}
public function get isRunning():Boolean
{
return _isRunning;
}
}
}
|
Попробуйте закомментировать функцию fillRect в функции stop, чтобы увидеть, как размытый огонь задерживается после остановки системы частиц.
Вы должны понимать большинство элементов, которые рассматриваются здесь, после нашего длинного вступления, и если есть что-то неясное, вы можете обратиться к документации, чтобы проверить специфику для каждого конструктора и т. Д.
Одна вещь, которая может показаться вам незнакомой, — это DiscSectorZone или его конструктор. Параметры являются следующими:
- center: Point — Здесь я просто передаю 0,0 Point, так как я не хочу менять центр
- outerRadius: Number — Внешний радиус диска
- innerRadius: Number — Внутренний радиус диска
- minAngle: Number — Минимальный угол в радианах
- maxAngle: Number — Максимальный угол в радианах
Зона определяется между внешним и внутренним радиусом, в пределах минимального и максимального угла. Я не делал никакого преобразования радиана в коде, вместо этого просто присматривал его к радианам, так как он отлично работает. Стоит отметить, что угол 0 равен горизонтали вправо.
Угадай, что? Мы закончили! Это конечный результат, который вы уже видели вверху:
Конец урока: дополнительный вызов
Если вы зашли так далеко, вы должны знать больше о FLiNT. Еще есть чему поучиться. Хороший способ узнать больше — бросить вызов себе. Будучи добрым писателем, я представляю вам дополнительную задачу — дымящуюся трубу !
Задача состоит в том, чтобы попытаться скопировать этот эффект или, возможно, улучшить его. Это то, что я сделал, когда просто играл с FLiNT, так что это отнюдь не идеально. Скорее, это отличная демонстрация того, как вы можете быстро сделать что-то, что оживляет ваш дизайн, используя FLiNT.
Чтобы дать вам начальную идею, я нарисовал внутри Flash маску, которая гарантирует, что дым поднимается только в трубу. Остальное все запрограммировано. Как только кто-то придумает хороший контрпример, я опубликую источник этого примера. Поэтому убедитесь, что вы вернетесь!
Графика дома бесплатна для использования, среди прочего, в блоге Lost Garden , посвященном играм и игровому искусству.
Помните, используйте FLiNT как инструмент программного дизайна. Визуализируйте эффект в своей голове, затем разбейте его на более мелкие части и закодируйте каждую часть. С некоторой практикой вы можете создавать удивительные эффекты!