Статьи

Управление движением частиц с помощью двигателя Stardust Particle Engine — Часть 2

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

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


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


Подобно гравитационным полям, дефлектор принимает данные о текущем движении частицы в качестве входных данных. После этого дефлектор перезаписывает движение частицы своим выходным сигналом только для того, чтобы выходные данные теперь содержали данные скорости в дополнение к данным положения. Таким образом, в двумерном пространстве выходной сигнал дефлектора представляет собой вектор 4D; первые два компонента вектора 4D представляют x- и y-компонент вектора положения (обозначены x и y ) соответственно, а последние два компонента представляют x- и y-компонент вектора скорости (обозначены vx и вы ).


Помните класс Field и действие Gravity из первой части этого урока? Ну, процедура похожа. Вы создаете объекты Deflector которые управляют движениями частиц, а затем добавляете их к действию Deflect , точно так же, как вы добавляете объекты Field к действию Gravity . Теперь давайте посмотрим на быстрый пример.


В этом примере мы будем использовать класс LineDeflector для создания эффекта отскакивания частиц от пола. Дефлектор линии по существу имитирует бесконечно длинную линию в двумерном пространстве, где одна сторона является открытым пространством, а другая — сплошной; Частицам разрешено находиться только в открытом пространстве, а не в твердом. Когда частицы выходят со стороны открытого пространства, ударяясь о линию, они отскакивают назад. Свойство Particle.collisionRadius , представляющее радиус частицы, учитывается.

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


Создайте новый документ Flash, нарисуйте круг радиусом 10, а затем преобразуйте его в символ, экспортированный для ActionScript с именем класса Circle .


Создайте файл AS для класса документа. Класс создает эмиттер и рендер. Если вам нужно освежить в памяти базовое использование Stardust, вы можете обратиться к этому руководству .

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.events.Event;
    import idv.cjcat.stardust.common.emitters.Emitter;
    import idv.cjcat.stardust.common.renderers.Renderer;
    import idv.cjcat.stardust.twoD.renderers.DisplayObjectRenderer;
     
    public class FloorEffect extends Sprite {
         
        private var emitter:Emitter;
        private var renderer:Renderer;
         
        public function FloorEffect() {
            emitter = new CircleEmitter();
            renderer = new DisplayObjectRenderer(this);
            renderer.addEmitter(emitter);
             
            addEventListener(Event.ENTER_FRAME, mainLoop);
        }
         
        private function mainLoop(e:Event):void {
            emitter.step();
        }
    }
}

Класс эмиттера показан ниже. Он в основном выбрасывает круговые частицы, и на частицы воздействует однородное гравитационное поле, направленное вниз.

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
package {
    import idv.cjcat.stardust.common.actions.Age;
    import idv.cjcat.stardust.common.actions.DeathLife;
    import idv.cjcat.stardust.common.actions.ScaleCurve;
    import idv.cjcat.stardust.common.clocks.SteadyClock;
    import idv.cjcat.stardust.common.initializers.Life;
    import idv.cjcat.stardust.common.initializers.Scale;
    import idv.cjcat.stardust.common.math.UniformRandom;
    import idv.cjcat.stardust.twoD.actions.Gravity;
    import idv.cjcat.stardust.twoD.actions.Move;
    import idv.cjcat.stardust.twoD.emitters.Emitter2D;
    import idv.cjcat.stardust.twoD.fields.Field;
    import idv.cjcat.stardust.twoD.fields.UniformField;
    import idv.cjcat.stardust.twoD.initializers.DisplayObjectClass;
    import idv.cjcat.stardust.twoD.initializers.Position;
    import idv.cjcat.stardust.twoD.initializers.Velocity;
    import idv.cjcat.stardust.twoD.zones.LazySectorZone;
    import idv.cjcat.stardust.twoD.zones.SinglePoint;
     
    public class CircleEmitter extends Emitter2D {
         
        public function CircleEmitter() {
            super(new SteadyClock(1));
             
            //initializers
            addInitializer(new DisplayObjectClass(Circle));
            addInitializer(new Life(new UniformRandom(60, 10)));
            addInitializer(new Position(new SinglePoint(320, 100)));
            addInitializer(new Velocity(new LazySectorZone(8, 4)));
            addInitializer(new Scale(new UniformRandom(1, 0.4)));
            addInitializer(new CollisionRadius(10));
             
            //actions
            addAction(new Age());
            addAction(new DeathLife());
            addAction(new Move());
            addAction(new ScaleCurve(0, 10));
             
            //gravity
            var field:Field = new UniformField(0, 0.5);
            var gravity:Gravity = new Gravity();
            gravity.addField(field);
            addAction(gravity);
        }
    }
}

Теперь вы создали эффект, когда частицы выбрасываются из центра сцены и притягиваются гравитацией. Вот как это выглядит:


Добавьте следующий код в конструктор эмиттера. Он создает линейный дефлектор, добавляет его к действию «Отражатель», а затем добавляет действие к эмиттеру, тем самым активируя эффект дефлектора. Первые два параметра конструктора для класса LineDeflector — это координаты точки на линии, а последние два параметра — это x- и y-компоненты нормального вектора линии. Свойство Deflector.bounce определяет «бодрость» линии, 1 вызывает полный отскок, а 0 означает отсутствие отскока вообще.

1
2
3
4
5
6
//create a line deflector passing through point (320, 320) and normal (0, -1)
var deflector:Deflector = new LineDeflector(320, 320, 0, -1);
deflector.bounce = 0.6;
var deflect:Deflect = new Deflect();
deflect.addDeflector(deflector);
addAction(deflect);

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

Хорошо, мы закончили с этим примером. Теперь давайте посмотрим на наш окончательный результат.


В этом примере мы собираемся использовать дефлектор BoundingBox для ограничения частиц внутри прямоугольной области.


Класс документа остается таким же, как и в предыдущем примере, но мы собираемся изменить класс эмиттера. Это базовый класс эмиттера этого примера. По сравнению с классом эмиттера в предыдущем примере SteadClock заменен на ImpulseClock для мгновенного создания 20 частиц в начале, зона положения изменяется с одной точки на прямоугольную зону, которая соответствует размеру сцены, инициализатор Velocity замедлен немного ниже, инициализатор Life удален, потому что мы хотим, чтобы частицы постоянно оставались на сцене, действия Age и DeathLife , в свою очередь, не требуются и удаляются, а ScaleCurve также удаляется. Некоторые виды импорта также добавляются и удаляются; Вы можете просто скопировать код ниже для удобства.

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 idv.cjcat.stardust.common.clocks.ImpulseClock;
    import idv.cjcat.stardust.common.initializers.CollisionRadius;
    import idv.cjcat.stardust.common.initializers.Scale;
    import idv.cjcat.stardust.common.math.UniformRandom;
    import idv.cjcat.stardust.twoD.actions.Deflect;
    import idv.cjcat.stardust.twoD.actions.Move;
    import idv.cjcat.stardust.twoD.deflectors.BoundingBox;
    import idv.cjcat.stardust.twoD.deflectors.Deflector;
    import idv.cjcat.stardust.twoD.emitters.Emitter2D;
    import idv.cjcat.stardust.twoD.initializers.DisplayObjectClass;
    import idv.cjcat.stardust.twoD.initializers.Position;
    import idv.cjcat.stardust.twoD.initializers.Velocity;
    import idv.cjcat.stardust.twoD.zones.LazySectorZone;
    import idv.cjcat.stardust.twoD.zones.RectZone;
    import idv.cjcat.stardust.twoD.zones.SinglePoint;
     
    public class CircleEmitter extends Emitter2D {
         
        private var impulseClock:ImpulseClock;
         
        public function CircleEmitter() {
            super(impulseClock = new ImpulseClock(20));
            impulseClock.impulse();
             
            //initializers
            addInitializer(new DisplayObjectClass(Circle));
            addInitializer(new Position(new RectZone(0, 0, 640, 400)));
            addInitializer(new Velocity(new LazySectorZone(3, 2)));
            addInitializer(new Scale(new UniformRandom(1, 0.4)));
            addInitializer(new CollisionRadius(10));
             
            //actions
            addAction(new Move());
        }
    }
}

Как и в предыдущем примере, мы теперь добавляем следующий код в конструктор эмиттера, чтобы использовать действие Deflect , только на этот раз мы используем дефлектор BoundingBox для ограничения частиц внутри прямоугольной области, которая соответствует размеру рабочей области.

1
2
3
4
5
//deflector
var deflector:Deflector = new BoundingBox(0, 0, 640, 400);
var deflect:Deflect = new Deflect();
deflect.addDeflector(deflector);
addAction(deflect);

Вот и все. Ничего особенного не изменилось, и теперь у нас есть ограниченные частицы в ограничительной рамке. Протестируйте фильм, и вы увидите результат.


Теперь мы собираемся создавать собственные дефлекторы самостоятельно. Давайте сначала разберемся с классом Deflector мы собираемся расширить.

Класс Deflector является базовым классом для всех дефлекторов. Для создания пользовательских дефлекторов вы должны расширить этот класс и переопределить метод MotionData4D calculateMotionData4D() , а затем вернуть объект MotionData4D представляющий векторный вывод 4D дефлектора. Введенные вами данные, как и класс Field , включены в объект Particle2D переданный в метод в качестве параметра. Вы можете использовать этот объект Particle2D для определения вашего вывода. Кстати, если этот метод возвращает null значение, действие « Deflect предполагает, что вы не хотите изменять движение текущей частицы, игнорируете частицу и затем переходите к обработке следующей частицы.

Например, следующий дефлектор будет вращать вектор скорости каждой частицы на один градус по часовой стрелке.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package {
    import idv.cjcat.stardust.twoD.geom.Vec2D;
    import idv.cjcat.stardust.twoD.particles.Particle2D;
    import idv.cjcat.stardust.twoD.deflectors.Deflector;
    import idv.cjcat.stardust.twoD.geom.MotionData4D;
     
    public class Rotator extends Deflector {
         
        override protected function calculateMotionData4D(particle:Particle2D):MotionData4D {
            var velocity:Vec2D = new Vec2D(particle.vx, particle.vy);
            velocity.rotateThis(1);
            return new MotionData4D(particle.x, particle.y, velocity.x, velocity.y);
        }
    }
}

В этом примере мы собираемся расширить класс Deflector и создать наш собственный дефлектор, имитирующий трубку, которая по сути представляет собой два линейных дефлектора, образующих свободное пространство в форме трубки.


Скопируйте документ Flash вместе с символом Circle и документ из первого примера. Здесь мы собираемся создать еще один класс эмиттеров, еще называемый CircleEmitter . На этот раз излучатель испускает частицы из центра сцены, и гравитационное поле не применяется.

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
package {
    import idv.cjcat.stardust.common.actions.Age;
    import idv.cjcat.stardust.common.actions.DeathLife;
    import idv.cjcat.stardust.common.actions.ScaleCurve;
    import idv.cjcat.stardust.common.clocks.SteadyClock;
    import idv.cjcat.stardust.common.initializers.CollisionRadius;
    import idv.cjcat.stardust.common.initializers.Life;
    import idv.cjcat.stardust.common.initializers.Scale;
    import idv.cjcat.stardust.common.math.UniformRandom;
    import idv.cjcat.stardust.twoD.actions.Deflect;
    import idv.cjcat.stardust.twoD.actions.Move;
    import idv.cjcat.stardust.twoD.deflectors.Deflector;
    import idv.cjcat.stardust.twoD.emitters.Emitter2D;
    import idv.cjcat.stardust.twoD.initializers.DisplayObjectClass;
    import idv.cjcat.stardust.twoD.initializers.Position;
    import idv.cjcat.stardust.twoD.initializers.Velocity;
    import idv.cjcat.stardust.twoD.zones.LazySectorZone;
    import idv.cjcat.stardust.twoD.zones.SinglePoint;
     
    public class CircleEmitter extends Emitter2D {
         
        public function CircleEmitter() {
            super(new SteadyClock(1));
             
            //initializers
            addInitializer(new DisplayObjectClass(Circle));
            addInitializer(new Life(new UniformRandom(60, 10)));
            addInitializer(new Position(new SinglePoint(320, 200)));
            addInitializer(new Velocity(new LazySectorZone(8, 4)));
            addInitializer(new Scale(new UniformRandom(1, 0.4)));
            addInitializer(new CollisionRadius(10));
             
            //actions
            addAction(new Age());
            addAction(new DeathLife());
            addAction(new Move());
            addAction(new ScaleCurve(0, 10));
        }
    }
}

Теперь мы собираемся создать наш класс дефлекторов труб. Подробности объясняются в комментариях.

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
package {
    import idv.cjcat.stardust.twoD.particles.Particle2D;
    import idv.cjcat.stardust.twoD.deflectors.Deflector;
    import idv.cjcat.stardust.twoD.geom.MotionData4D;
     
    public class TubeDeflector extends Deflector {
         
        private var y1:Number;
        private var y2:Number;
         
        public function TubeDeflector(y1:Number, y2:Number) {
            //y2 should be larger than y2
            if (y1 > y2) {
                //swap y1 and y2 if y1 is larger
                var temp:Number = y1;
                y1 = y2;
                y2 = temp;
            }
             
            this.y1 = y1;
            this.y2 = y2;
        }
         
        override protected function calculateMotionData4D(particle:Particle2D):MotionData4D {
            //output components, initialized to the particle’s original motion data
            var x:Number = particle.x;
            var y:Number = particle.y;
            var vx:Number = particle.vx;
            var vy:Number = particle.vy;
             
            //caluculate actual collsion radius
            var radius:Number = particle.collisionRadius * particle.scale;
             
            //flag for whether the deflector takes effect
            var deflected:Boolean = false;
             
            if (particle.y < (y1 + radius)) {
                //particle y-coordinate is less than lower limit
                //set proper new y-coordinate
                y = y1 + radius;
                 
                //set flag
                deflected = true;
            } else if (particle.y > (y2 — radius)) {
                //particle y-coordinate is greater than upper limit
                //set proper new y-coordinate
                y = y2 — radius;
                 
                //set flag
                deflected = true;
            }
             
            if (deflected) {
                return new MotionData4D(x, y, vx, vy);
            } else {
                //ignore the particle and not update its motion data
                return null;
            }
        }
    }
}

Вы должны знать, что мы собираемся делать дальше очень хорошо сейчас. Это верно, мы собираемся добавить дефлектор к действию Deflect а затем добавить действие к эмиттеру. Добавьте следующий код в конструктор эмиттера.

1
2
3
4
5
//create a tube deflector
var deflector:Deflector = new TubeDeflector(100, 300);
var deflect:Deflect = new Deflect();
deflect.addDeflector(deflector);
addAction(deflect);

Теперь вы можете проверить фильм. Опять же, вы также можете нарисовать некоторое визуальное представление дефлектора на сцене для лучшего вида.


Это конец всего урока. В первой части вы узнали о гравитационных полях. Во второй части вы узнали о концепции дефлекторов и фактическом использовании действия « Deflect . Кроме того, вы узнали, как расширить класс Deflector для создания пользовательских дефлекторов. Теперь вы можете выполнять расширенные манипуляции с движением частиц в Stardust.

Большое спасибо за чтение!