Статьи

Эффекты Starling Particle для Stage3D Shooter Games

Я уверен, что Stage3D не чужды большинству читателей; это новый API, который предоставляет программистам AS3 доступ к графическому процессору. Тем не менее, кодирование против кодов операций в Stage3D, возможно, не является предпочтительным выбором для всех, так что повезло, что есть ярлык: Starling, библиотека, разработанная для инкапсуляции этого низкоуровневого программирования, чтобы сделать его намного проще. И вместе со Starling идет расширение эффектов частиц. В этом уроке мы рассмотрим системы частиц этого фреймворка и увидим, как его приложения применяются к игре «стреляй в них».


Пропустите этот шаг, если вы работали с FlashDevelop в течение некоторого времени. Для начинающих, вот как вы устанавливаете библиотечный пакет — в данном случае Starling и расширение его частиц. Обратите внимание, что эти два предмета не входят в одну упаковку, поэтому нам придется загружать их отдельно.

Сначала загрузите фреймворк Starling и расширение его частиц из своих репозиториев. Распакуйте после успешной загрузки. Сканируйте первый разархивированный каталог на наличие папки src и вставьте библиотеку инфраструктуры Starling, выделенную на рисунке ниже, в исходную папку вашего проекта.

Установить библиотеку Starling

Сканирование второй папки для расширения частиц и объединить их вместе. Вы можете перетащить выделенную папку ниже в папку Starling . Изображение ниже — это конечный результат, к которому вы должны прийти.

Установить расширение частиц на Starling

Для получения дополнительной информации о FlashDevelop и использовании внешних библиотек см. Эти учебные руководства:


Если вы еще не знакомы со Starling и его расширением частиц, я настоятельно рекомендую посетить видеоуроки Lee Brimelow по Starling и частицам , а также руководство Matthew Chung по обработке состояний анимации в Starling .

Мы собираемся просто пройтись по основам в два этапа. Если вы уже знакомы со Starling и его расширением частиц, смело переходите к шагу 4.

На втором изображении предыдущего шага (нижняя часть) Main.as что создаются два класса: Main.as и Testing.as . Первый действует как пусковая установка для последнего. Итак, большая часть нашего кода Starling живет в Testing.as . Я выделил важный код в Main.as здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
private function init(e:Event = null):void
{
    removeEventListener(Event.ADDED_TO_STAGE, init);
    // entry point
     
    var myStarling:Starling = new Starling(Testing, stage);
    myStarling.simulateMultitouch = true;
    myStarling.start();
     
    //initiate Starling onto stage
    //allow mouse/ touch events to happen in Starling
    //turn key and start the engine!
}

… и Testing.as должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class Testing extends Sprite
{
    public function Testing() {
        addEventListener(Event.ADDED_TO_STAGE, init);
    }
    private function init(e:Event):void {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // code goes here.
         
        stage.color = 0;
         
        //Draw a little quad on stage,
        //just to make sure everything’s in place
        //Note the top left corner of sprite is aligned to middle of stage
        var q:Quad = new Quad(30, 30);
        q.color = 0xEEEEEE;
        qx = stage.stageWidth >> 1;
    }
}

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

Примечание: Testing.as расширяет Sprite от Starling.display.Sprite , а не flash.display.Sprite . Классы имеют одинаковые имена, но не совпадают.


Расширение частиц Starling содержит три важных класса. Их функциональные возможности приведены в таблице ниже.

Наименование класса функциональность
Particle.as Отдельная частица с уникальными атрибутами.
ParticleSystem.as Контролирует поток частиц: генерация, анимация и утилизация.
ParticleDesignerPS.as Расширение ParticleSystem.as позволяющее легко манипулировать системой частиц.

Мы создадим экземпляр ParticleDesignerPS.as . Это требует ввода следующих аргументов в конструктор класса:

  • XML-файл, содержащий все значения инициализации для свойств частиц (их довольно много)
  • изображение для образца для всех частиц

Не беспокойтесь, onebyonedesign.com поможет вам с этим. Посетите их страницу дизайнера частиц и подправьте все эти ценности инициации к своему сердцу. Затем экспортируйте все данные в формат ZIP. Этот ZIP-файл будет содержать XML-файл и изображение эффекта частиц, который вы только что создали на их веб-странице!

Сохранить детали частиц.

Разархивируйте и извлеките все это в исходную папку в FlashDevelop. Смотрите выделенные элементы на изображении ниже.

Интеграция с исходной папкой.

Создайте операторы импорта, чтобы получить эти два элемента в свой класс Testing . Вам также нужно будет переписать метод init в разделе « Testing . Они все ниже.

1
2
3
4
5
[Embed(source=»particle.pex», mimeType=»application/octet-stream»)]
private var InitValues:Class
 
[Embed(source = «texture.png»)]
private var Sample:Class
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function init(e:Event):void {
    removeEventListener(Event.ADDED_TO_STAGE, init);
    // code goes here.
     
    stage.color = 0;
    var flow1:ParticleDesignerPS =
    new ParticleDesignerPS(
            XML(new InitValues()),
            Texture.fromBitmap(new Sample())
        );
    addChild(flow1);
    flow1.emitterX = stage.stageWidth >> 1;
    flow1.start();
    Starling.juggler.add(flow1);
}

Вот результат, к которому вы должны прийти. Довольно просто, правда?


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

Частицы — это просто спрайты, которые излучаются по координате. После рождения они будут анимироваться по определенной схеме. Эта модель может быть уникальной для каждой частицы или общей для всех частиц. Но будьте уверены, их физические свойства со временем изменятся. Например:

  • Расстояние от координаты рождения
  • Направление и скорость
  • Цвет и альфа-уровень
  • Размер

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

(Это пул объектов , и вы можете посмотреть, как применить его к своим собственным проектам, не относящимся к Starling Flash.)

Итак, как это связано с нашим огнем? Ну, мы можем использовать функции замедления, чтобы оживить свойства этого пожара с течением времени. В структуре Starling ParticleDesignerPS располагается в конце этой иерархии:

ParticleDesignerPS > ParticleSystem > DisplayObject > EventDispatcher > Object

Чтобы соблюсти баланс, мы просто проследим унаследованные свойства от ParticleSystem . Давайте посмотрим на это следующий шаг …


Ниже приведены свойства ParticleSystem .

Свойство Описание
capacity Максимум частиц, которые система может нести в любой момент. Увеличивается с шагом 500, как только количество частиц превышает его текущую емкость. Только для чтения.
numParticles Количество частиц в системе в данный момент. Только для чтения.
emissionRate Количество частиц, генерируемых при рождении, координируется каждую секунду.
emitterX , emitterY Контрольная точка для контейнера, в котором живут все частицы.

blendFactorSource , blendFactorDestination

Определение Context3DBlendFactor для источника и назначения. Назначение относится к цвету пикселя из последнего рендеринга, а источник относится к новому цвету пикселя для рисования в месте назначения.
texture Текущее изображение выбрано как текстура частиц. Только для чтения.

Те из ParticleDesignerPS сведены в таблицу в следующей таблице. Большинство из этих свойств могут быть точно настроены его начальным и конечным состоянием. Например, все генерируемые частицы будут начинаться с размера 1,0 и заканчиваться на 0,1. Тем не менее, поток частиц будет скучным, если все частицы инициируются и заканчиваются в таких похожих состояниях, поэтому ParticleDesignerPS обеспечивает дисперсию начального значения, а иногда и дисперсию в значении завершения.

Цитируя их пример, если мы дадим дисперсию 0,2 от начального размера частицы, последовательные частицы, рожденные или рециркулирующие в систему, начнут свой размер где-то между 0,8 ~ 1,2 и заканчиваются на 0,1.

Свойство Описание
emitterXVariance , emitterYVariance Изменение координаты рождения.
startSize , startSizeVariance Начальный размер и дисперсия
endSize , endSizeVariance Размер завершения и дисперсия
emitAngle , emitAngleVariance Начальное направление и дисперсия частицы
speed , speedVariance Начальная скорость и дисперсия частиц
gravityX Ускорение по оси x на начальной скорости всех частиц
gravityY Ускорение вдоль оси y на начальной скорости всех частиц
tangentialAcceleration , tangentialAccelerationVariation Скорость вращения на скорости частицы и дисперсия

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

Свойство Описание
maxRadius , maxRadiusVariance Максимальный радиус от центра и дисперсия
minRadius Минимальный радиус от центра
rotationPerSecond , rotationPerSecondVariance Скорость вращения на скорости частицы
startColor , startColorVariance Начальный цвет и дисперсия
endColor , endColorVariance Окончание цвета и дисперсии

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

1
stage.addEventListener(TouchEvent.TOUCH, track);

И слушатель как ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function track(e:TouchEvent):void {
    var touch:Touch = e.getTouch(stage);
    //when user presses mouse and moves
    if (touch.phase == TouchPhase.MOVED) {
         
        //calculate the angle to point particle flow to
        var distX:Number = touch.globalX — flow1.emitterX;
        var distY:Number = touch.globalY — flow1.emitterY;
        var angle:Number = Math.atan2(distY, distX);
         
        t = new Tween(flow1, 1.5, Transitions.EASE_OUT_BACK);
        t.animate(«emitAngle», angle);
        Starling.juggler.add(t);
    }
}

Для выполнения анимации определен экземпляр Tween . Его манипуляции во многом похожи на популярные движки Tween . Затем мы добавляем его в жонглер текущего экземпляра Starling. Этот объект жонглера поможет постепенно обновлять экземпляр Tween течением времени.

Результат ниже. Нажмите и перетащите мышку по сцене.


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

Мы начнем заново с другого класса, TestingShip.as . Сначала импортируйте изображение космического корабля «boss1.png».

1
2
[Embed(source = «boss1.png»)]
private var Ship:Class

… с небольшой настройкой для инициализации в методе init :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//setup the graphical appearance
var shipBMP:Bitmap = new Ship() as Bitmap;
var shipTEX:Texture = Texture.fromBitmap(shipBMP);
var shipIMG:Image = new Image(shipTEX);
 
//setup ship’s orientation & position
shipIMG.rotation -= Math.PI*0.5;
shipIMG.x -= shipIMG.width >> 1;
shipIMG.y += shipIMG.height >> 1;
theShip = new Sprite();
theShip.addChild(shipIMG);
addChildAt(theShip, 0);
 
//navigational properties of ship
loc = new Vector2D(stage.stageWidth >> 1, stage.stageHeight >> 1);
lof = new Vector2D(0, 10);
 
updateShip();

Обновите его положение и ориентацию в соответствии с loc (местоположение) и lof (линия обзора).

1
2
3
4
5
private function updateShip():void {
    theShip.x = loc.x;
    theShip.y = loc.y;
    theShip.rotation = lof.getAngle();
}

Снова нажмите и перетащите в пределах сцены, чтобы увидеть эффект:


Хорошо, выхлоп корабля находится сверху самого корабля, и космический корабль не реагирует на события мыши. Мы исправим это сейчас. Просто emitterX и emitterY потока частиц на некоторое расстояние от космического корабля и обновите вращение космического корабля, используя lof .

(Обратите внимание, что lof обновляется при событиях мыши. Вы увидите сценарий следующим шагом.)

01
02
03
04
05
06
07
08
09
10
11
private function updateShip():void {
    theShip.x = loc.x;
    theShip.y = loc.y;
    theShip.rotation = lof.getAngle();
     
    //update particle trail
    offset = new Vector2D(60, 0);
    offset.setAngle(lof.getAngle());
    flow1.emitterX = loc.x — offset.x;
    flow1.emitterY = loc.y — offset.y;
}

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

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
private function track(e:TouchEvent):void {
    var touch:Touch = e.getTouch(stage);
    if (touch.phase == TouchPhase.MOVED) {
        var distX:Number = touch.globalX — flow1.emitterX;
        var distY:Number = touch.globalY — flow1.emitterY;
        angle = Math.atan2(distY, distX);
         
        t = new Tween(flow1, 1.5, Transitions.EASE_OUT_BACK);
        t.animate(«emitAngle», angle + Math.PI);
         
        t2 = new Tween(theShip, 1.5, Transitions.EASE_OUT);
        t2.moveTo(touch.globalX, touch.globalY);
        t2.onUpdate = refresh //call upon this function whenever tween engine runs
         
        Starling.juggler.add(t);
        Starling.juggler.add(t2);
    }
}
 
private function refresh():void {
    loc.x = theShip.x;
    loc.y = theShip.y;
    lof.setAngle(angle);
     
    updateShip();
}

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


Давайте точно настроим наш выхлоп. Когда корабль движется, выхлоп будет дуть сильнее, верно? Мы можем повысить emissionRate и speed emissionRate при перемещении корабля, а также уменьшить скорость при остановке корабля. Вот событие, выделенное:

1
2
3
4
5
t2 = new Tween(theShip, 1.5, Transitions.EASE_OUT);
t2.moveTo(touch.globalX, touch.globalY);
t2.onUpdate = refresh //call upon this function whenever tween engine runs
t2.onStart = beginState //when ship starts moving
t2.onComplete = endState //when ship animation stops

А вот и вызовы функций для этих событий.

1
2
3
4
5
6
7
8
9
private function beginState():void {
    flow1.emissionRate = 250
    flow1.speed = 100;
}
 
private function endState():void {
    flow1.emissionRate = 50
    flow1.speed = 10;
}

Нажмите и перетащите снова, и обратите внимание на длину выхлопа.


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

Настройка этого эффекта относительно проста в приложении, демонстрируемом onebyonedesign.com . Однако нам нужно будет написать код ActionScript, чтобы изменить его во время выполнения, и для этого понадобятся следующие несколько шагов.


Инициирование частиц происходит в формате, аналогичном предыдущему примеру. Вы можете настроить свой эффект с помощью приложения onebyonedesign.com и импортировать его на свою сцену. Я просто пишу код прямо в ActionScript для простоты.

01
02
03
04
05
06
07
08
09
10
11
12
envr = new ParticleDesignerPS( XML(new InitValues()),
    Texture.fromBitmap(new Sample())
    );
addChildAt(envr,0);
envr.blendFactorSource = Context3DBlendFactor.ONE
envr.blendFactorDestination = Context3DBlendFactor.ONE
envr.speed = 10;
envr.startSize = 15;
envr.endSize = 20;
envr.lifespan = 5.0;
envr.emissionRate = 10
envr.start();

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

1
2
envrLoc = new Vector2D(100, 0);
envrLoc.setAngle(angle);

И обновите этот вектор во время выполнения.

1
2
3
4
5
//update the environment
envr.gravityX = -40*lof.x;
envr.gravityY = -40*lof.y;
envr.emitterX = loc.x + envrLoc.x;
envr.emitterY = loc.y + envrLoc.y;

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

Распространение вопроса в эмиттере

Теперь проверьте вектор для прямой видимости. Это всегда перпендикулярно линии спреда (тонкая темная линия). Мы можем соответственно масштабировать этот вектор и смешивать его x и y с дисперсией эмиттера в начальной точке. Проверьте демо ниже. Нажмите и перетащите мышку вокруг. Вы увидите поток частиц более ярким.

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

1
2
envrLoc = new Vector2D(200, 0);
envrLoc.setAngle(angle);
1
2
3
4
5
//update the spread
spread = envrLoc.clone();
spread.scale(0.5);
envr.emitterXVariance = spread.y;
envr.emitterYVariance = spread.x;

Наконец, когда корабль ускоряется, давайте увеличим соответственно значения силы gravityX и gravityY gravityX gravityY , а также gravityY .

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
if (touch.phase == TouchPhase.MOVED) {
    var distX:Number = touch.globalX — flow1.emitterX;
    var distY:Number = touch.globalY — flow1.emitterY;
    angle = Math.atan2(distY, distX);
     
    //animate the exhaust
    t = new Tween(flow1, 1.5, Transitions.EASE_OUT_BACK);
    t.animate(«emitAngle», angle + Math.PI);
    Starling.juggler.add(t);
     
    //control the exhaust
    flow1.speed = 350;
    flow1.endSize = 70;
     
    //orient the ship & parallax’s angle
    lof.setAngle(angle);
    lof.setMagnitude(10);
    envrLoc.setAngle(angle);
}
if (touch.phase == TouchPhase.ENDED) {
     
    //control the exhaust
    flow1.speed = 100;
    flow1.endSize = 10;
     
    lof.setMagnitude(5);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
envr = new ParticleDesignerPS( XML(new InitValues()),
    Texture.fromBitmap(new Sample())
    );
addChildAt(envr,2);
envr.blendFactorSource = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA
envr.blendFactorDestination = Context3DBlendFactor.ONE;
envr.emitterXVariance = theShip.width >> 2;
envr.emitterYVariance = theShip.height >> 2;
envr.emitAngle = 0;
envr.speed = 0;
envr.startSize = 40;
envr.endSize = 10;
envr.lifespan = 5.0;
envr.emissionRate = 10;
envr.start();

Тяжесть повреждения определяется площадью и интенсивностью ожога. Увеличьте и уменьшите emissionRate чтобы смоделировать это. Я добавил элементы управления на клавиатуре «A» и «S», чтобы подражать этому.

01
02
03
04
05
06
07
08
09
10
private function controlBurn(e:KeyboardEvent):void {
    if (e.keyCode == Keyboard.A) {
        if(envr.emissionRate < 150) envr.emissionRate += 10;
        if (envr.lifespan < 8) envr.lifespan += 0.5;
    }
    if (e.keyCode == Keyboard.S) {
        if(envr.emissionRate > 10) envr.emissionRate -= 10;
        if (envr.lifespan > 5) envr.lifespan -= 0.5;
    }
}

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


Нажмите клавишу «А», чтобы увидеть, как огонь горит интенсивнее, и клавишу «S», чтобы немного погасить его. Цвет выхлопа был изменен, чтобы отличать его от горения:


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

Теперь давайте закодируем это.


Этот поток частиц немного отличается от тех, которые мы видели. Ранее мы использовали тип потока частиц 0 (гравитация), а это тип 1 (радиальный). Частицы фактически движутся к середине с постоянной скоростью.

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

01
02
03
04
05
06
07
08
09
10
11
explosion = new ParticleDesignerPS(
        XML(new InitValues()),
        Texture.fromBitmap(new Sample())
    );
addChild(explosion);
explosion.emitterX = stage.stageWidth >> 1;
explosion.emitterY = stage.stageHeight >> 1;
explosion.emitterType = 1;
explosion.emitAngle = 0;
explosion.maxRadius = 10;
explosion.minRadius = 0;

Вот код для выполнения анимации.

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
private function track(e:TouchEvent):void {
    var touch:Touch = e.getTouch(stage);
    if (touch.phase == TouchPhase.BEGAN) {
        explosion.emitterX = touch.globalX;
        explosion.emitterY = touch.globalY;
        explosion.start();
        t = new Tween(explosion, 1.0, Transitions.EASE_IN);
        t.animate(«maxRadius», 150);
        t.animate(«minRadius», 130);
        t.onStart = freeze
        t.onComplete = reset;
        Starling.juggler.add(t);
    }
}
 
private function freeze():void {
    stage.removeEventListener(TouchEvent.TOUCH, track);
}
 
private function reset():void {
    stage.addEventListener(TouchEvent.TOUCH, track);
    explosion.stop();
    explosion.maxRadius = 10;
    explosion.minRadius = 0;
}

Так что это был длинный урок. Давайте сделаем небольшое резюме здесь. Мы прошли через:

  • Настройка Starling и расширение его частиц
  • Свойства системы частиц
  • Примеры манипуляций с имуществом по сценарию шутера.

Здесь мы немного рассмотрели. Тем не менее, один важный аспект, который я не прошел, это расширение ParticleSystem . Это действительно даст вам возможность кодировать ваши собственные следы частиц вместо того, чтобы полагаться на ParticleDesignerPS . Мне придется отложить это в другой учебник.

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