Particles System в моделировании толпы (2)
Продолжаем разговор от 07.04.2014 (Particles System в моделировании толпы).В этой части добавляю:
медленные персонажи (это будут крупные стрЕлки) огибание в пути медленных стрелок быстрыми взрывы (с разбрасыванием тел) Короткая ремарка о стиле написания кода (для читателей первой части): не забывайте, код пишется а-ля псевдокод — не смотря на то, что он рабочий, во главу угла поставлена наглядность, а не функциональность и «правильность» расширяю базовый класс приложения (MainWailines_1) через класс (MainWailines_2) — опять же ради наглядности, и чтоб не смешивать комментарии из разных статей (т.е. — в реальности, конечно, я бы не игрался с наследованием в этом случае) комментарии из первой части во второй части удаляю — все ради того же, да-да, ради наглядности картинок много — проиллюстрировать эволюцию решения и показать, что нет предела совершенству медленные персонажи Пишем метод MainWaylines_2.setupEmitterForMonsterArrows (). Фактически это copy-paste прежнего MainWaylines_1.setupEmitter (). Я только удалил старые комментарии, и оставил их лишь там, где есть изменения. protected function setupEmitterForMonsterArrows (): void { var emitter: Emitter2D = new Emitter2D (); // это счетчик — устанавливаем на 1 Чудовищную Стрелку в секунду emitter.counter = new Steady (1); var wayline: Wayline = _waylines[0]; emitter.addInitializer (new Position (new LineZone (new Point (wayline.x — wayline.radius*Math.cos (wayline.rotation), wayline.y — wayline.radius*Math.sin (wayline.rotation)), new Point (wayline.x + wayline.radius*Math.cos (wayline.rotation), wayline.y + wayline.radius*Math.sin (wayline.rotation))))); // сообщаем, какую картинку использовать рендеру при отрисовке частицы // делаем юнитов покрупнее emitter.addInitializer (new ImageClass (Arrow, [10])); emitter.addAction (new DeathZone (new RectangleZone (-30, -30, stage.stageWidth+60, stage.stageHeight + 60), true)); emitter.addAction (new Move ()); emitter.addAction (new RotateToDirection ()); // если юнитов этого типа будет мало, и между ними будет большое расстояние, // то можно было бы вообще исключить этот action // emitter.addAction (new MinimumDistance (7, 600)); // делаем юнитов помедленнее emitter.addAction (new ActionResistance (.1)); emitter.addAction (new FollowWaylines (_waylines)); var renderer: DisplayObjectRenderer = new DisplayObjectRenderer (); addChild (renderer); renderer.addEmitter (emitter); // командуем старт emitterWaylinesForMonsterArrows = emitter; emitterWaylinesForMonsterArrows.start (); } теперь расширяем и запускаем MainWaylines_2.setup ()
override protected function setup (e: Event=null): void { super.setup (); // создаем новый эмиттер для крупных и медленных setupEmitterForMonsterArrows (); } получаем картинку, подобную этой. Крупные стрелки сливаются с мелкими — существуют параллельно
огибание в пути медленных стрелок быстрыми для того, чтоб мелочь огибала крупные стрелки, нужно им дать команду. Добавляем строчку в MainWaylines_2.setup (), где Antigravities — это еще один стандартный action из библиотеки системы частиц (классная библиотека, да?). override protected function setup (e: Event=null): void { super.setup (); // создаем новый эмиттер для крупных и медленных setupEmitterForMonsterArrows (); // добавляем новый action к эмиттеру «для самых маленьких» // обратите внимание (!) эмиттер УЖЕ запущен, и его не надо перезапускать — поведение частиц можно менять на лету emitterWaylines.addAction (new Antigravities (emitterWaylinesForMonsterArrows, -400000)); } и результат начинает походить вот на такую картинку. Мелкие стрелки уже огибают крупные, но уж очень много их скапливается позади. Эти пробки совсем некрасиво смотрятся.
это происходит из-за следующего «конфликта». Antigravities заставляет мелкие стрелки огибать крупные. Одновременно с этим гонит их вперед FollowWaylines — каждая стрелка стремится к определенной точке на перпендикуляре пути, помните? Мелкие стрелки просто не успевают воврмя обогнуть крупную из-за того что слишком быстро приближаются к узловым точкам на пути. Одно из решений (и мне кажется, самое простое) — это увеличение длины отрезков пути (расстояния между узлами маршрута).
переписываем MainWaylines_2.setupWaylines () ради одной строчки
override protected function setupWaylines (): void
{
_waylines = [];
var w: Number = stage.stageWidth;
var h: Number = stage.stageHeight;
var points: Array = [new Point (-9, h*.4), new Point (w*.3, h*.4), new Point (w*.5, h*.1), new Point (w*.8, h*.1), new Point (w*.8, h*.9), new Point (w*.5, h*.9), new Point (w*.3, h*.8), new Point (-40, h*.8)];
var fitline: FitLine = new FitLine (points);
var path: Path = new Path (fitline.fitPoints);
/*
* переписываем одно число. Было 40, станет 25
*
* более красивым решением, было бы написание метода, который расчитывал бы число шагов в зависимости от длины пути
* ну, это надо лишь, если мы планируем автоматически создавать много разных маршрутов
*/
var step: Number = path.length / 25;
var strength: Number = 100;
for (var i: int=0; i редактируем MainWaylines_2.setupEmitterForMonsterArrows (), уменьшив LineZone эмиттера на 20 (по 10 пикселей с каждой стороны)
emitter.addInitializer (new Position (new LineZone (new Point (wayline.x — (wayline.radius-10)*Math.cos (wayline.rotation), wayline.y — (wayline.radius-10)*Math.sin (wayline.rotation)), new Point (wayline.x + (wayline.radius-10)*Math.cos (wayline.rotation), wayline.y + (wayline.radius-10)*Math.sin (wayline.rotation)))));
теперь пробки за крупными стрелками стали значительно меньше взрывы (с разбрасыванием тел)
Создаем новый эмиттер — для анимации разбрасывания тел
protected function setupEmitterForExplosion (): void
{
var emitter: Emitter2D = new Emitter2D ();
// чтоб частицы двигались — это уже знакомо
emitter.addAction (new Move ());
// чтоб не играться с соотношениями сил, чтоб не очень быстро разбрасывались частицы — проще тупо ограничить скорость
emitter.addAction (new SpeedLimit (40));
// это чтоб частицы постепенно замедлялись — трение
emitter.addAction (new Friction (40));
// на всякий случай — вдруг вылетят (хотя можно было на другие эмиттеры оставить)
emitter.addAction (new DeathZone (new RectangleZone (-30, -10, stage.stageWidth+40, stage.stageHeight + 20), true));
// новый рендер
var renderer: DisplayObjectRenderer = new DisplayObjectRenderer ();
addChild (renderer);
renderer.addEmitter (emitter);
// командуем старт
emitterExplosion = emitter;
emitterExplosion.start ();
}
Подписываемся на MouseEvent.MOUSE_DOWN в MainWaylines_2.setup () — по этим событиям будем генерировать взрывы
stage.addEventListener (MouseEvent.MOUSE_DOWN, handleMouseDown);
почему не сразу вызываем explosion (e);? Туда можно анимацию самого взрыва добавить, по окончании которой сгенерить последствия
private function handleMouseDown (e: MouseEvent): void
{
explosion (e);
}
Теперь сам взрыв
private function explosion (e: MouseEvent): void
{
if (emitterWaylines == null){ return; }
if (emitterExplosion == null){ return; }
// радиус взрыва
var explRadius: int = 30;
// ради оптимизации заводим локальные переменные
// (внутри больших циклов обращение к данным не на прямую, а через dot-синтаксис начинает существенно потреблять процессорное время)
var particleOrigin: Particle2D;
var particleClone: Particle2D;
var particlePoint: Point = new Point ();
// произошел взрыв в точке…
var explPoint: Point = new Point (e.stageX, e.stageY);
// готовимся к длинному циклу
var particles: Array = emitterWaylines.particlesArray;
var length: int = particles.length;
// перебор всех частиц в эмиттере
for (var p: int=0; p Суть необходимых изменений проста — по истечении определенного времени надо «возвращать» частицу в прежний поток.1. Сначала вносим изменения в MainWaylines_2.setupEmitterForExplosion ():
protected function setupEmitterForExplosion (): void
{
var emitter: Emitter2D = new Emitter2D ();
…
// этот action отсчитывает «возраст» частицы. По истечению возраста, частица удаляется.
// соотв. надо подписаться на событие, чтоб вернуть частицу в прежний эмиттер
emitterExplosion.addAction (new Age ());
…
// подписываемся на «смерть частицы от старости», чтоб перенести ее обратно в «родной» эмиттер
emitterExplosion.addEventListener (ParticleEvent.PARTICLE_DEAD, handleParticleDeadFromEmitterExplosion);
}
2. теперь добавляем изменения в MainWaylines_2.explosion ()
private function explosion (e: MouseEvent): void
{
…
// перебор всех частиц в эмиттере
for (var p: int=0; p Итог: два типа юнитов: мелкие и крупные
мелкие юниты огибают крупные
взрывы действют на мелкие юниты (пусть это будет шрапнель, которая не действует на танки — крупные стрелки)
после того, как мелкие оправятся от «кантузии», они снова возвращаются в общий поток
Очевидные минусы не-эпично высокая скорость стрелок
низкий FPS
Если для решения проблемы с п.1. можно продолжить играться с настройками эмиттеров (а сегодняшний мой способ использования системы частиц не самый совершенный), то что же с п.2.(FPS)? Есть ли потенциал для оптимизации? Ведь надо же еще графику нормальную прикручивать, еще кучу игрового кода… Думаю, потенциал для оптимизации есть, и немалый Запрет на столкновения между мелкими стрелками, при текущих масштабах — на самом деле чистая блажь — можно число юнитов увеличить в 2–5 раз, и в образовавшейся каше вообще ничего не разглядеть будет (а если проекция на поле не top-down, как сейчас, а изометрическая?). Да и не будет «полной каши» — ведь мелкие стрелки, не забывайте, двигаются по индивидуально заданным маршрутам (у каждой имеется свое положение относительно перпендикуляра к касательной). Попробуйте отключить action MinimumDistance, предупреждающий взаимные столкновения — особой разницы не заметите (только при обгоне крупных). А прирост в производительности — существенный (можете глянуть в код action-а и увидеть, СКОЛЬКО там расчетов).
Просто отключил «родной» рендер — и FPS сразу подрос в более, чем в полтора раза (а если на Starling).
Теперь о сложности подхода вообще — работе с Системой Частиц.Надеюсь, он не показался излишне сложным — «кучи» эмиттеров, настроек к ним, передача частиц между ними…На самом деле, при data-oriented подходе вся логика поведения сотен частиц заключена именно в эмиттерах. А у нас их сейчас только три (из которых эмиттеры для мелких и крупных стрелок вообще близнецы-братья).Еще эмиттеры можно представлять в качестве состояний (State) — следование по маршруту и поражение взрывной волной. А «передача» частиц между эмиттерами — ни что иное, как переход между состояниями. Код доступен на google code. Класс MainWaylines_2 PS: В следующей части добавлю гибель стрелок (ведь взрывы убивают)поиграюсь с настройками эмиттеров — хочется эпичности. PPS: Вопрос. Хочу освоить легкий способ создания sprite sheet из анимированных 3D персонажей. Как я для себя это вижу: имеется анимированный персонаж
хочу в некоем программном продукте задать примерно следующие параметры: размер
угол камеры
число фреймов
на выходе — sprite sheet
Не подскажете, в какую сторону смотреть? Может есть ПОДРОБНОЕ описание подобной РАБОЧЕЙ методики? Заранее спасибо.