В моем предыдущем уроке Shoot Out Stars с двигателем частиц Stardust я объяснил основной рабочий процесс Stardust. На этот раз мы пойдем дальше и рассмотрим несколько методов для создания настоящих эффектов трехмерных частиц!
Вступление
Мы начнем с демонстрации того, как использовать собственный 3D-движок Stardust . Затем я покажу вам, как заставить Stardust работать с Papervision3D; мы создадим эффекты трехмерных частиц с помощью класса частиц Papervision3D и класса DisplayObject3D.
Ранее …
Мы собираемся забрать, где мы остановились в первый урок. В прошлый раз мы создали частицы звезд и кругов, которые вылетали из точки, увеличиваясь до максимального размера, а затем сжимаясь до нуля, постепенно перемещаясь со временем (так называемый эффект затухания). На этот раз мы сделаем то же самое, но в 3D. Вместо частиц, движущихся по кругу, они будут двигаться в сфере.
Шаг 1. Создайте новый документ Flash
Как и прежде , сначала создайте новый документ Flash с размерами 640×400, частотой кадров 60 кадров в секунду и темным фоном (я использовал синий градиент).
Шаг 2: Нарисуйте частицы
Нарисуйте звезду и белый круг, затем конвертируйте их в символы отдельно. Это два символа, которые мы собираемся использовать в качестве частиц позже. Назовите символ звезды «Звезда» и символ круга «Круг», экспортированный для ActionScript с теми же именами классов.
(Если вы не большой артист, вы можете загрузить исходный код вверху страницы и использовать мои символы из библиотеки моего FLA.)
Шаг 3: Создать кнопку паузы
Нажмите « Окно»> «Компоненты», чтобы открыть панель «Компоненты», затем перетащите кнопку из папки интерфейса пользователя на сцену. Установите метку «Пауза» и назовите ее «pause_btn». Мы собираемся использовать эту кнопку, чтобы приостановить эффекты трехмерных частиц, что позволит пользователям вращать камеру, чтобы лучше почувствовать трехмерную среду.
Шаг 4. Создайте класс документа
Создайте новый класс документа и назовите его StarParticles3D.
1
|
package { import flash.display.Sprite;
|
Не знаете, как использовать класс документа? Прочтите этот краткий совет.
3D инициализаторы и действия
Три основных пакета в Stardust:
- idv.cjcat.stardust.common : содержит общие элементы для эффектов 2D и 3D частиц.
- idv.cjcat.stardust.twoD : содержит специальные элементы для эффектов 2D-частиц.
- idv.cjcat.stardust.threeD : содержит специальные элементы для эффектов трехмерных частиц.
В предыдущем уроке мы использовали инициализаторы и действия из общего и двумерного пакетов. В этом уроке мы все еще будем использовать элементы из общего пакета, но не из двумерного пакета. Вместо этого мы будем использовать элементы из трехмерного пакета.
Структура классов в трехмерном пакете почти такая же, как и в двумерном, за исключением того, что элементы имеют дополнительное измерение. Трехмерный элемент имеет то же имя, что и его двумерный аналог, но его имя оканчивается на «3D». Например, действие Move3D в 3D-пакете обновляет положения частиц в 3D-пространстве в соответствии со скоростями, так же как его 2D-аналог в 2D-пакете, действие Move .
Шаг 5: Расширить излучатель
Создайте новый файл AS с именем StarEmitter.as ; внутри него создайте новый класс StarEmitter , который расширяет класс Emitter3D:
01
02
03
04
05
06
07
08
09
10
11
|
package {
import idv.cjcat.stardust.threeD.emitters.Emitter3D;
public class StarEmitter extends Emitter3D {
public function StarEmitter(clock:Clock) {
//pass the clock object to the superclass’s constructor
super(clock);
}
}
}
|
Помните параметр Clock? Он используется для контроля скорости создания частиц. Нам нужно включить его в функцию конструктора, чтобы мы могли передать ему Clock позже.
Поскольку мы разрешаем пользователям приостанавливать эффекты частиц, мы собираемся упаковать все действия в один объект CompositeAction, который, по сути, представляет собой группу действий. Деактивировав это единственное составное действие, мы можем «отключить» все базовые действия. Объявите переменную для составного действия в классе эмиттера. Мы получим доступ к этой переменной в классе документа, поэтому она должна быть публичной:
1
|
public var pausibleActions:CompositeAction;
|
Шаг 6: Объявление констант частиц
Объявите константы, которые будут использоваться в качестве параметров частиц в классе эмиттера. Мы уже рассмотрели назначение этих констант в предыдущем уроке. Большинство имен говорят сами за себя. Они идут внутри класса, но за пределами функции конструктора. Не стесняйтесь возвращаться сюда позже и изменить цифры, чтобы увидеть эффект.
01
02
03
04
05
06
07
08
09
10
11
|
private static const LIFE_AVG:Number = 30;
private static const LIFE_VAR:Number = 10;
private static const SCALE_AVG:Number = 1;
private static const SCALE_VAR:Number = 0.4;
private static const GROWING_TIME:Number = 5;
private static const SHRINKING_TIME:Number = 10;
private static const SPEED_AVG:Number = 30;
private static const SPEED_VAR:Number = 10;
private static const OMEGA_AVG:Number = 0;
private static const OMEGA_VAR:Number = 5;
private static const DAMPING:Number = 0.1;
|
Шаг 7. Инициализатор переключения для экранных объектов частиц
В предыдущем уроке я продемонстрировал, как использовать SwitchInitializer для создания частиц с различными экранными объектами. Я использовал инициализатор DisplayObjectClass, который инициализирует внешний вид частиц с помощью экранных объектов. Это было для эффектов 2D частиц; здесь мы собираемся использовать его трехмерный аналог, инициализатор DisplayObject3D.
Добавьте следующий код в функцию конструктора эмиттера:
1
2
3
4
5
|
//switch initializers for star and circle particles
var doc1:DisplayObjectClass3D = new DisplayObjectClass3D(Star);
var doc2:DisplayObjectClass3D = new DisplayObjectClass3D(Circle);
var si:SwitchInitializer = new SwitchInitializer([doc1, doc2], [1, 1]);
addInitializer(si);
|
Шаг 8: добавь оставшиеся инициализаторы
То же, что и в предыдущем уроке; добавьте другие инициализаторы, показанные ниже. Обратите внимание, что некоторые из них имеют имена, похожие на названия в предыдущем уроке, но заканчиваются на «3D».
- Инициализатор Position3D инициализирует положения частиц. Как и его 2D-аналог, инициализатор положения, он принимает один параметр конструктора, объект зоны. Однако на этот раз он не принимает объект Zone, который представляет 2D-зону; вместо этого он принимает объект Zone3D, представляющий зону в трехмерном пространстве. Класс SinglePoint3D расширяет класс Zone3D и является 3D-версией класса SinglePoint.
- То же самое относится и к классу Velocity3D. Класс SphereShell по сути является 3D-версией класса SectorZone. Здесь мы устанавливаем центр оболочки сферы равным (0, 0, 0), средний радиус — SPEED_AVG, а изменение радиуса — SPEED_VAR.
- Инициализаторы Rotation3D и Omega3D работают точно так же, как и их 2D-аналоги, Rotation и Omega. Однако есть одно небольшое различие в параметрах конструктора: они принимают три объекта Random вместо одного. Это связано с тем, что теперь в трехмерном пространстве есть три оси вращения, поэтому для них требуются три случайных значения вращения. В этом примере мы создаем «двумерные рекламные щиты» в трехмерном пространстве (т. Е. Плоские объекты), поэтому очевидно только вращение оси Z; Вот почему первые два параметра, Случайные объекты для осей X и Y, имеют нулевые значения.
Этот код входит в функцию конструктора StarEmitter:
1
2
3
4
5
6
|
addInitializer(new Life(new UniformRandom(LIFE_AVG, LIFE_VAR)));
addInitializer(new Scale(new UniformRandom(SCALE_AVG, SCALE_VAR)));
addInitializer(new Position3D(new SinglePoint3D()));
addInitializer(new Velocity3D(new SphereShell(0, 0, 0, SPEED_AVG, SPEED_VAR)));
addInitializer(new Rotation3D(null, null, new UniformRandom(0, 180)));
addInitializer(new Omega3D(null, null, new UniformRandom(OMEGA_AVG, OMEGA_VAR)));
|
Шаг 9: Добавьте действия
Создайте составное действие и добавьте к нему некоторые действия. Затем добавьте это составное действие к эмиттеру; это заставит частицы выполнять действия. Вы видели эти действия в предыдущем уроке (некоторые из них в 2D-версии), поэтому я не буду объяснять их заново. Опять же, этот код входит в функцию конструктора StarEmitter:
1
2
3
4
5
6
7
8
|
pausibleActions = new CompositeAction();
pausibleActions.addAction(new Age());
pausibleActions.addAction(new DeathLife());
pausibleActions.addAction(new Move3D());
pausibleActions.addAction(new Spin3D());
pausibleActions.addAction(new Damping3D(DAMPING));
pausibleActions.addAction(new ScaleCurve(GROWING_TIME, SHRINKING_TIME));
addAction(pausibleActions);
|
Шаг 10: Вернуться к классу документов
Хорошо, мы закончили с эмиттером. Теперь пришло время построить наш класс документов.
Сначала объявите константы для радиуса орбитальной камеры, расстояния камеры от начала координат и скорости излучателя:
1
2
|
private static const CAMERA_RADIUS:Number = 250;
private static const PARTICLE_RATE:Number = 0.5;
|
(Как и прежде, константы идут внутри класса, но вне функции конструктора.)
Затем объявите переменные для эмиттера, постоянных часов и DisplayObjectRenderer3D (в том же месте, что и константы):
1
2
3
|
private var emitter:StarEmitter;
private var clock:SteadyClock;
private var renderer:DisplayObjectRenderer3D;
|
В конструкторе инициализируйте часы, эмиттер и рендер. Также установите исходное положение и направление камеры, чтобы она смотрела на начало координат:
1
|
//create the clock and emitter clock = new SteadyClock(PARTICLE_RATE);
|
Шаг 11: Запрограммируйте паузу
Создайте в классе документа функцию-обработчик для обработки события нажатия кнопки паузы:
01
02
03
04
05
06
07
08
09
10
11
|
private function togglePause(e:MouseEvent):void {
if (e.target.label == «Pause») {
e.target.label = «Resume»;
clock.ticksPerCall = 0;
emitter.pausibleActions.active = false;
} else {
e.target.label = «Pause»;
clock.ticksPerCall = PARTICLE_RATE;
emitter.pausibleActions.active = true;
}
}
|
… затем зарегистрируйте слушатель для кнопки паузы в функции конструктора:
1
|
pause_btn.addEventListener(MouseEvent.CLICK, togglePause);
|
Шаг 12: Основной цикл
Создайте обработчик для события ENTER_FRAME. Это наш основной цикл. Он обновляет положение камеры, вызывая метод updateCamera () (который мы закодируем через минуту) и вызывает метод step () эмиттера, который поддерживает действие частиц:
1
2
3
4
|
private function mainLoop(e:Event):void {
updateCamera();
emitter.step();
}
|
Снова зарегистрируйте слушателя в конструкторе:
1
|
addEventListener(Event.ENTER_FRAME, mainLoop);
|
Шаг 13: Обновите положение камеры
Теперь определите метод updateCamera (), вызванный на предыдущем шаге. Это используется для перемещения камеры в трехмерном пространстве в зависимости от положения мыши. (Если вам нужна дополнительная информация о том, как это работает, ознакомьтесь с этой статьей в Википедии .)
Магические числа, используемые для генерации тэта и фи, являются результатом проб и ошибок; не стесняйтесь пробовать свои собственные уравнения.
01
02
03
04
05
06
07
08
09
10
11
|
private function updateCamera():void {
var theta:Number = 0.02 * (mouseX — 320);
var phi:Number = 0.02 * (mouseY — 200);
phi = StardustMath.clamp(phi, -StardustMath.HALF_PI, StardustMath.HALF_PI);
var x:Number = CAMERA_RADIUS * Math.cos(theta) * Math.cos(phi);
var y:Number = CAMERA_RADIUS * Math.sin(phi);
var z:Number = CAMERA_RADIUS * Math.sin(theta) * Math.cos(phi);
renderer.camera.position.set(x, y, z);
renderer.camera.direction.set(-x, -y, -z);
}
|
Обратите внимание, что я использовал метод StardustMath.clamp (); это гарантирует, что значение phi находится между положительной и отрицательной половиной PI.
Веха: родной двигатель Stardust завершен
Хорошо, мы закончили! Это все, что нам нужно сделать, чтобы 3D-излучатель работал с собственным 3D-движком Stardust. Давайте посмотрим на результат. Вы можете нажать кнопку паузы, чтобы приостановить эффект частиц, и переместить мышь вокруг, чтобы вращать камеру:
Если вы хотите увидеть полный исходный код, посмотрите в папке «01 — Stardust Native 3D Engine» в Source.
Время для Papervision3D
Переключиться с родного 3D движка Stardust на Papervision3D легко. Нам просто нужно использовать другой рендерер и инициализатор экранного объекта.
(Никогда раньше не использовал Papervision3D? Взгляните на этот урок для начинающих .)
Сначала мы будем использовать класс частиц Papervision3D. Возможно, вы не знакомы с этим; Я покажу вам, как использовать более распространенный класс DisplayObject3D позже.
Шаг 14: измени инициализатор экранного объекта
Измените следующий код в классе эмиттера:
1
2
|
var doc1:DisplayObjectClass3D = new DisplayObjectClass3D(Star);
var doc2:DisplayObjectClass3D = new DisplayObjectClass3D(Circle);
|
к этому:
1
2
3
4
|
var mat1:MovieParticleMaterial = new MovieParticleMaterial(new Star());
var mat2:MovieParticleMaterial = new MovieParticleMaterial(new Circle());
var doc1:PV3DParticle = new PV3DParticle([mat1]);
var doc2:PV3DParticle = new PV3DParticle([mat2]);
|
Как вы, возможно, уже знаете, класс MovieParticleMaterial позволяет нам использовать экранные объекты для отображения частиц в Papervision3D. Мы создаем экземпляр Star и Circle для использования в качестве материала частиц. Инициализатор PV3DParticle занимает место инициализатора DisplayObjectClass3D; его конструктор принимает массив параметров, которые все будут добавлены в объект Particles.
Это все, что мы должны сделать в отношении излучателя. Далее мы изменим класс документа.
Шаг 15: Настройте среду Papervision3D
Целевой контейнер для нашего рендерера больше не является объектом Sprite. Вместо этого мы собираемся создать частицы в объекте Particles. Нам придется переключить тип рендерера с DisplayObjectRenderer3D на PV3DParticleRenderer.
Объявите следующие переменные для объектов, связанных с Papervision3D:
1
2
3
4
5
6
|
private var scene:SceneObject3D;
private var particles:Particles;
private var camera:Camera3D;
private var origin:DisplayObject3D;
private var renderEngine:BasicRenderEngine;
private var viewport:Viewport3D;
|
Код в конструкторе класса документа теперь:
01
02
03
04
05
06
07
08
09
10
|
initPV3D();
clock = new SteadyClock(PARTICLE_RATE);
emitter = new StarEmitter(clock);
renderer = new PV3DParticleRenderer(particles);
renderer.addEmitter(emitter);
pause_btn.addEventListener(MouseEvent.CLICK, togglePause);
addEventListener(Event.ENTER_FRAME, mainLoop);
|
Метод initPV3D () устанавливает среду Papervision3D. Вот код:
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
|
private function initPV3D():void {
//create the scene
scene = new SceneObject3D();
//create the Particles object
particles = new Particles();
//create the camera and initialize its position
camera = new Camera3D();
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = -CAMERA_RADIUS;
//create a DO3D representing the origin
origin = new DisplayObject3D();
origin.x = origin.y = origin.z = 0;
//point the camera to the origin
camera.target = origin;
scene.addChild(origin);
scene.addChild(particles);
//create the render engine and viewport
renderEngine = new BasicRenderEngine();
viewport = new Viewport3D(640, 400);
//add the viewport to the stage
addChild(viewport);
//add the pause button again so that it is on top of the viewport
addChild(pause_btn);
}
|
Шаг 16: Новый основной цикл
Теперь Stardust обновляет только свойства 3D-объектов; Рендеринг Papervision3D берет на себя ответственность за рендеринг. Вот как выглядит наш новый основной цикл:
1
2
3
4
5
|
private function mainLoop(e:Event):void {
updateCamera();
emitter.step();
renderEngine.renderScene(scene, camera, viewport);
}
|
Шаг 17: Обновление камеры
Теперь, когда мы используем камеру Papervision3D, нам также придется изменить метод updateCamera ():
01
02
03
04
05
06
07
08
09
10
11
12
|
private function updateCamera():void {
var theta:Number = 0.02 * (mouseX — 320);
var phi:Number = 0.02 * (mouseY — 200);
phi = StardustMath.clamp(phi, -StardustMath.HALF_PI, StardustMath.HALF_PI);
var x:Number = CAMERA_RADIUS * Math.cos(theta) * Math.cos(phi);
var y:Number = -CAMERA_RADIUS * Math.sin(phi);
var z:Number = CAMERA_RADIUS * Math.sin(theta) * Math.cos(phi);
camera.x = x;
camera.y = y;
camera.z = z;
}
|
Веха: Papervision3D с завершенными частицами
Хорошо, мы успешно переключились с родного 3D движка Stardust на Papervision3D. Теперь давайте проверим результат. Обратите внимание на эффект пикселизации частиц. Это потому, что Papervision3D сначала рисует векторные объекты в растровые изображения, прежде чем использовать их в качестве материалов частиц.
Вы можете найти весь исходный код для этого в папке «02 — Papervision3D Particles».
Класс DisplayObject3D Papervision3D
До сих пор мы работали с «2D рекламными щитами» — плоскими объектами, такими как бумага. Можно создавать «настоящие» объекты 3D-частиц, например объекты DisplayObject3D Papervision3D. Нам просто нужно использовать другой инициализатор. Теперь давайте перейдем к заключительной части этого урока. Мы создадим частицы красного и синего куба.
Шаг 18: снова измени инициализатор экранного объекта
Мы собираемся изменить инициализатор относительно внешнего вида частиц в последний раз.
Перед этим объявите переменную LightObject3D в классе эмиттера. Мы будем использовать FlatShadeMaterial для объектов DisplayObject3D, для которых требуется источник света. Кроме того, объявите следующие константы — мы будем использовать их в качестве параметров для FlastShadeMaterial и для определения размеров кубов:
1
2
3
4
5
6
7
|
public var light:LightObject3D;
private static const LIGHT_COLOR_1:uint = 0xCC3300;
private static const LIGHT_COLOR_2:uint = 0x006699;
private static const AMBIENT_COLOR_1:uint = 0x881100;
private static const AMBIENT_COLOR_2:uint = 0x002244;
private static const CUBE_SIZE:Number = 15;
|
Теперь измените следующий код в классе эмиттера:
1
2
3
4
|
var mat1:MovieParticleMaterial = new MovieParticleMaterial(new Star());
var mat2:MovieParticleMaterial = new MovieParticleMaterial(new Circle());
var doc1:PV3DParticle = new PV3DParticle([mat1]);
var doc2:PV3DParticle = new PV3DParticle([mat2]);
|
к этому:
1
2
3
4
5
6
7
8
9
|
light = new LightObject3D();
var mat1:FlatShadeMaterial = new FlatShadeMaterial(light, LIGHT_COLOR_1, AMBIENT_COLOR_1);
var mat2:FlatShadeMaterial = new FlatShadeMaterial(light, LIGHT_COLOR_2, AMBIENT_COLOR_2);
var matList1:MaterialsList = new MaterialsList({all:mat1});
var matList2:MaterialsList = new MaterialsList({all:mat2});
var params1:Array = [matList1, CUBE_SIZE, CUBE_SIZE, CUBE_SIZE];
var params2:Array = [matList2, CUBE_SIZE, CUBE_SIZE, CUBE_SIZE];
var doc1:PV3DDisplayObject3DClass = new PV3DDisplayObject3DClass(Cube, params1);
var doc2:PV3DDisplayObject3DClass = new PV3DDisplayObject3DClass(Cube, params2);
|
Внешний вид новой частицы будет инициализирован как красный и синий трехмерные кубы. Первый параметр конструктора для инициализатора PV3DDisplayObject3DClass — это класс, который мы хотим создать для частиц (поэтому здесь это класс Cube), а второй параметр — это массив параметров конструктора для этого класса Cube.
Шаг 19: Три оси вращения
Раньше, поскольку мы работали с «2D билбордами», имело значение только вращение вокруг оси Z. Теперь, когда мы работаем с настоящими трехмерными объектами, нам нужно передать три ссылки на объекты Random в конструкторы Rotation3D и Omega3D, по одной для каждой оси.
Измените следующий код в классе эмиттера:
1
2
|
addInitializer(new Rotation3D(null, null, new UniformRandom(0, 180)));
addInitializer(new Omega3D(null, null, new UniformRandom(OMEGA_AVG, OMEGA_VAR)));
|
к этому:
1
2
3
4
|
var rotationRandom:UniformRandom = new UniformRandom(0, 180);
var omegaRandom:UniformRandom = new UniformRandom(OMEGA_AVG, OMEGA_VAR);
addInitializer(new Rotation3D(rotationRandom, rotationRandom, rotationRandom));
addInitializer(new Omega3D(omegaRandom, omegaRandom, omegaRandom));
|
Шаг 20: измените класс документа
На этот раз вместо использования объекта Particles в качестве контейнера для частиц мы используем DisplayObject3D в качестве контейнера. Объявите переменную для этого контейнера в классе документа:
1
|
private var container:DisplayObject3D;
|
Также нам понадобится рендерер другого типа для создания частиц в новом контейнере. Измените тип рендерера с PV3DParticleRenderer на PV3DDisplayObject3DRenderer. Код в конструкторе класса документа теперь должен выглядеть так:
01
02
03
04
05
06
07
08
09
10
|
initPV3D();
clock = new SteadyClock(PARTICLE_RATE);
emitter = new StarEmitter(clock);
renderer = new PV3DDisplayObject3DRenderer(container);
renderer.addEmitter(emitter);
pause_btn.addEventListener(MouseEvent.CLICK, togglePause);
addEventListener(Event.ENTER_FRAME, mainLoop);
|
Шаг 21: Измените метод initPV3D ()
В функции initPV3D () нам теперь нужно инициализировать переменную контейнера и добавить ее в сцену. Добавьте эти две строки в конец этой функции:
1
2
|
container = new DisplayObject3D();
scene.addChild(container);
|
Шаг 22: модифицируйте метод updateCamera ()
В методе updateCamera () мы хотим, чтобы свет следовал за камерой, поэтому у нас будет иллюзия, что свет всегда «выстреливает» из наших глаз. Измените следующий код:
1
2
3
|
camera.x = x;
camera.y = y;
camera.z = z;
|
к этому:
1
2
3
|
emitter.light.x = camera.x = x;
emitter.light.y = camera.y = y;
emitter.light.z = camera.z = z;
|
Теперь источник света всегда находится в той же точке, что и камера.
Веха: Papervision3D с завершенным DisplayObject3D
Да, мы наконец-то закончили с этим уроком. Нет больше кодирования. Давайте посмотрим на наш конечный результат с причудливыми красными и синими 3D кубами!
Исходный код для этого можно найти в папке «Papervision3D DisplayObject3D».
Вывод
Рабочий процесс создания эффектов 3D-частиц с помощью Stardust практически такой же, как и для 2D-эффектов. Вы просто выбираете другой набор инициализаторов, действий и средств визуализации. Stardust также поддерживает другие 3D-движки, включая ZedBox и ND3D . Использование практически одинаково. Вам просто нужно использовать другой набор инициализаторов и средств визуализации. Вы даже можете расширить классы Initializer, Action и Renderer для работы с любыми трехмерными движками, которые вам нравятся!
Теперь у вас есть основы, почему бы не вернуться к константам, созданным в шаге 6, и поиграть с ними, чтобы увидеть эффекты?
Я надеюсь, что этот учебник поможет вам лучше понять Stardust и сделает вас более знакомыми с рабочим процессом Stardust. Спасибо за прочтение!