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 (); } получаем картинку, подобную этой. Крупные стрелки сливаются с мелкими — существуют параллельно

1c0d605f9e61f66b5a575fe1efc74fad.png

огибание в пути медленных стрелок быстрыми для того, чтоб мелочь огибала крупные стрелки, нужно им дать команду. Добавляем строчку в MainWaylines_2.setup (), где Antigravities — это еще один стандартный action из библиотеки системы частиц (классная библиотека, да?). override protected function setup (e: Event=null): void { super.setup (); // создаем новый эмиттер для крупных и медленных setupEmitterForMonsterArrows (); // добавляем новый action к эмиттеру «для самых маленьких» // обратите внимание (!) эмиттер УЖЕ запущен, и его не надо перезапускать — поведение частиц можно менять на лету emitterWaylines.addAction (new Antigravities (emitterWaylinesForMonsterArrows, -400000)); } и результат начинает походить вот на такую картинку. Мелкие стрелки уже огибают крупные, но уж очень много их скапливается позади. Эти пробки совсем некрасиво смотрятся.

25a0ab79c5c95d7fa8033c6de530a657.png

это происходит из-за следующего «конфликта». 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))))); теперь пробки за крупными стрелками стали значительно меньше

b0795980ea224259bb8967389b818774.png

взрывы (с разбрасыванием тел) Создаем новый эмиттер — для анимации разбрасывания тел

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

abe638e421144e1684e5f4649474e246.png

Суть необходимых изменений проста — по истечении определенного времени надо «возвращать» частицу в прежний поток.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

4f35733ce303431497c766316bba867c.png

Итог:

два типа юнитов: мелкие и крупные мелкие юниты огибают крупные взрывы действют на мелкие юниты (пусть это будет шрапнель, которая не действует на танки — крупные стрелки) после того, как мелкие оправятся от «кантузии», они снова возвращаются в общий поток Очевидные минусы

не-эпично высокая скорость стрелок низкий FPS Если для решения проблемы с п.1. можно продолжить играться с настройками эмиттеров (а сегодняшний мой способ использования системы частиц не самый совершенный), то что же с п.2.(FPS)? Есть ли потенциал для оптимизации? Ведь надо же еще графику нормальную прикручивать, еще кучу игрового кода…

Думаю, потенциал для оптимизации есть, и немалый

Запрет на столкновения между мелкими стрелками, при текущих масштабах — на самом деле чистая блажь — можно число юнитов увеличить в 2–5 раз, и в образовавшейся каше вообще ничего не разглядеть будет (а если проекция на поле не top-down, как сейчас, а изометрическая?). Да и не будет «полной каши» — ведь мелкие стрелки, не забывайте, двигаются по индивидуально заданным маршрутам (у каждой имеется свое положение относительно перпендикуляра к касательной). Попробуйте отключить action MinimumDistance, предупреждающий взаимные столкновения — особой разницы не заметите (только при обгоне крупных). А прирост в производительности — существенный (можете глянуть в код action-а и увидеть, СКОЛЬКО там расчетов). Просто отключил «родной» рендер — и FPS сразу подрос в более, чем в полтора раза (а если на Starling).e98f897893bf49debb5be36b29cc045b.png Теперь о сложности подхода вообще — работе с Системой Частиц.Надеюсь, он не показался излишне сложным — «кучи» эмиттеров, настроек к ним, передача частиц между ними…На самом деле, при data-oriented подходе вся логика поведения сотен частиц заключена именно в эмиттерах. А у нас их сейчас только три (из которых эмиттеры для мелких и крупных стрелок вообще близнецы-братья).Еще эмиттеры можно представлять в качестве состояний (State) — следование по маршруту и поражение взрывной волной. А «передача» частиц между эмиттерами — ни что иное, как переход между состояниями.

Код доступен на google code. Класс MainWaylines_2

PS: В следующей части добавлю гибель стрелок (ведь взрывы убивают)поиграюсь с настройками эмиттеров — хочется эпичности.

PPS: Вопрос. Хочу освоить легкий способ создания sprite sheet из анимированных 3D персонажей. Как я для себя это вижу:

имеется анимированный персонаж хочу в некоем программном продукте задать примерно следующие параметры: размер угол камеры число фреймов на выходе — sprite sheet Не подскажете, в какую сторону смотреть? Может есть ПОДРОБНОЕ описание подобной РАБОЧЕЙ методики? Заранее спасибо.

© Habrahabr.ru