Эффект растворения

Пару дней назад ко мне обратился комрад horror812 с вопросом о реализации эффекта смерти персонажа в игре WarMagic. Его интересовало, как именно был реализован этот эффект – анимацией или с помощью кода.
На самом деле ничего сложного там не было, эффект был сделан из анимированной маски, постепенно скрывающей персонажа и скрипта который добавлял частицы. Недостатков у этого способа было много. Во первых приходилось каждый раз для каждого персонажа создавать анимированную маску и вставлять скрипт в кадр, что было бы достаточно трудоемко и неэффективно, если бы персонажей было не 3, а намного больше. Во вторых частицы появлялись по всей ширине анимированной маски, т.е. частица могла появиться за пределами формы персонажа, что смотрелось не очень красиво. Поэтому я решил довести эффект до «ума» и написать класс, с помощью которого можно было бы без особых усилий создавать эффект растворения и который в будущем облегчил бы мне жизнь.

Пользоваться классом достаточно просто, создаем экземпляр класса в который передаем один обязательный параметр aTarget – это наш персонаж который будет растворяться, и четыре параметра по желанию
Animated — булевый параметр — true если МувиеКлип анимирова, иначе false
aSideCut — направление растворения (снизу вверх и сверху вниз)
aParticleSprite — linkage name мувика частицы
aParticlCount – количество частиц
AnimationDeley – продолжительность анимации растворения
aWind – скорость ветра

package  
{
        import com.greensock.TweenLite;
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.DisplayObject;
        import flash.display.DisplayObjectContainer;
        import flash.display.MovieClip;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.geom.ColorTransform;
        import flash.geom.Matrix;
        import flash.geom.Rectangle;
        import flash.system.System;
        /**
         * ...
         * @author evilMax
         */
        public class DeathEffect extends Sprite
        {
                
                private var _mask:Sprite;
                private var _particles:Array = [];
                private var _particleSprite:Class;
                private var _particleCount:int;
                private var _heightSign:int;
                private var _bitmapDataTarget:BitmapData
                private var _bitmapTarget:Bitmap;
                private var _target:DisplayObjectContainer;
                private var _windX:Number;
                private var _speedY:Number;
                private var _step:Number;
                private var _percent:Number;
                private var _maxMaskHeight:Number;
                private var _isDrawn:Boolean;
                private var _animated:Boolean;
                private var _completeAnimation:Boolean = false;
                private var _cutSide:Boolean;
                public function DeathEffect(aTarget:DisplayObjectContainer, Animated:Boolean = false,aSideCut:Boolean=true, aParticleSprite:Class = null, aParticlCount:int = 18, AnimationDeley:Number = 1.5, aWind:Number = 0):void 
                {
                        _target = aTarget;
                        _animated = Animated;
                        _particleSprite = aParticleSprite;
                        _particleCount = aParticlCount;
                        _windX = aWind;
                        _speedY = 3;
                        _percent = 100;
                        _isDrawn = false;
                        _step = _percent / (AnimationDeley * 30);
                        _cutSide = aSideCut;
                        setHeightSign();
                        addEventListener(Event.ENTER_FRAME, update);
                        addEventListener(Event.REMOVED_FROM_STAGE, destruct);
                }
                private function drawDefParticle():MovieClip
                {
                        var particle:MovieClip = new MovieClip();
                        particle.graphics.beginFill(0x00FF44);
                        particle.graphics.moveTo( -5, -6);
                        particle.graphics.lineTo(2, -4);
                        particle.graphics.lineTo(6, 1);
                        particle.graphics.lineTo(6, 6);
                        particle.graphics.lineTo(0, 4);
                        particle.graphics.lineTo( -4, 1);
                        particle.graphics.lineTo( -5, -6);
                        return particle;
                }
                private function copyTarget(aTarget:DisplayObjectContainer):void 
                {
                        var r:Rectangle=aTarget.getBounds(aTarget);
                        var m:Matrix=new Matrix();
                        m.identity();
                        m.translate(-r.x,-r.y);
                        m.scale(aTarget.scaleX,aTarget.scaleY);
                        _bitmapDataTarget = new BitmapData(aTarget.width, aTarget.height, true, 0x000000);
                        _bitmapDataTarget.draw(aTarget,m);
                        _bitmapTarget = new Bitmap(_bitmapDataTarget);
                        _bitmapTarget.x = aTarget.x+r.x;
                        _bitmapTarget.y = aTarget.y+r.y;
                        aTarget.parent.addChild(_bitmapTarget);
                        aTarget.visible = false;
                }
                
                private function drawMask(aTarget:DisplayObjectContainer,aWidth:Number, aHeight:Number):void 
                {
                        var r:Rectangle=aTarget.getBounds(aTarget);
                        _mask = new Sprite();
                        _mask.graphics.lineStyle(1);
                        _mask.graphics.beginFill(0x00FF44);
                        _mask.graphics.drawRect(0, 0, aWidth, aHeight);
                        _mask.x = aTarget.x + r.x;
                        _mask.y = aTarget.y + r.y;
                        aTarget.parent.addChild(_mask);
                }
                private function addParticle():void
                {
                        
                        if (_completeAnimation)
                                return;
                        
                        for (var i:int=0;i<_particleCount;i++)
                        {
                                var segments:Array = new Array();
                                var geted:Boolean = false;
                                for (var j:int=_bitmapDataTarget.width;j>=0;j--)
                                {
                                        var AlphaValue:Number = _bitmapDataTarget.getPixel32(j-1, getYPos())>>24&0xFF;
                                        if (AlphaValue!= 0 && !geted)
                                        {
                                                var obj:Object = new Object();
                                                obj.sP = j;
                                                geted = true;
                                        }
                                        else if (AlphaValue == 0 && geted)
                                        {
                                                obj.fP = j;
                                                geted = false;
                                                segments.push(obj);
                                        }
                                }
                                if (segments.length == 0)
                                        return;
                                var rnd:int = int(Math.random() * segments.length);
                                var pixelXPos:Number = segments[rnd].sP - Math.random() * Math.abs(segments[rnd].fP - segments[rnd].sP);
                                var pixelYPos:Number = getYPos();
                                var color:ColorTransform = new ColorTransform();
                                var pixelColor:uint = _bitmapDataTarget.getPixel32(pixelXPos, pixelYPos);
                                var speedY:Number = Math.random() * _speedY + _speedY/2;
                                color.color = pixelColor;
                                var particle:MovieClip; 
                                if (_particleSprite == null)
                                        particle = drawDefParticle();
                                else    
                                        particle = new _particleSprite();
                                _target.parent.addChild(particle);
                                particle.x = _mask.x + (pixelXPos);
                                particle.y = _mask.y +3;
                                if (!_cutSide)
                                        particle.y = _mask.y +_mask.height;
                                particle.transform.colorTransform = color;
                                particle.rotation = Math.random() * 180;
                                particle.scaleX = particle.scaleY = Math.random() * .5 + .5;
                                _particles.push( { t:(Math.random() * 5), mc:particle, speed:speedY} );
                        }
                }
                private function update(e:Event=null):void
                {
                        if (_animated && !_completeAnimation)
                        {
                                clearing();
                                drawMask(_target, _target.width, _target.height);
                                copyTarget(_target);
                                _bitmapTarget.mask = _mask;
                                _maxMaskHeight = _mask.height;
                        }
                        else if(!_animated && !_isDrawn)
                        {
                                drawMask(_target, _target.width, _target.height);
                                copyTarget(_target);
                                _bitmapTarget.mask = _mask;
                                _maxMaskHeight = _mask.height;
                                _isDrawn = true;
                        }
                        cuttingMask();
                        addParticle();
                        for (var i:int=0;i<_particles.length;i++)
                        {
                                var item:Object = _particles[i];
                                item.t+=.19;
                                item.mc.x+=Math.sin(item.t)*1.2+_windX;
                                item.mc.y-=item.speed;
                                item.mc.alpha-=.02;
                                item.mc.scaleX = item.mc.scaleY -= .02;
                                if(item.mc.alpha<=0)
                                {
                                        _particles[i].mc.parent.removeChild(item.mc);
                                        _particles[i]=null;
                                        _particles.splice(i,1);
                                        i--;
                                }
                        }
                        if (_particles.length == 0 && _completeAnimation)
                                destruct();
                }
                
                private function cuttingMask():void
                {
                        var value:Number;
                        _percent -= _step;
                        if (_percent > 0)
                        {
                                if(_animated)
                                        value = _maxMaskHeight - _maxMaskHeight * _percent / 100;
                                else    
                                        value = _maxMaskHeight * _step / 100;
                                _mask.height -= value;
                                _mask.y += value*_heightSign;
                        }
                        if (_percent <= 0)
                        {
                                _completeAnimation = true;
                                _mask.height = 0;
                        }
                }
                private function setHeightSign():void
                {
                        _heightSign = 0;
                        if (_cutSide)
                                _heightSign = 1;
                }
                private function getYPos():Number
                {
                        var yPos:Number=_mask.height;
                        if(_cutSide)
                                 yPos = _target.height - _mask.height;
                        return yPos;
                }       
                private function clearing():void
                {
                        if (_mask != null)
                        {
                                _mask.parent.removeChild(_mask);
                                _mask = null;
                        }
                        if (_bitmapTarget != null)
                        {
                                _bitmapTarget.parent.removeChild(_bitmapTarget);
                                _bitmapTarget = null;
                        }
                }
                private function destruct(e:Event=null):void 
                {
                        removeEventListener(Event.REMOVED_FROM_STAGE, destruct);
                        removeEventListener(Event.ENTER_FRAME, update);
                        for (var i:int=0;i<_particles.length;i++)
                        {
                                _particles[i].parent.removeChild(_particles[i]);
                                _particles[i] = null;
                                _particles.splice(i, 1);
                                i--;
                        }
                        clearing();
                        //System.gc();
                }
        }

}


Исходник

Ну и результат:

UPD:
Как и обещал, доработал эффект. Теперь его можно применять и к анимированным МувиеКлипам и добавлена возможность выбора направления растворения — сверху вниз или снизу вверх.
К сожалению погонять класс в боевых условиях не было времени, а посему вполне возможны баги и недоработки. Да, и от ваших предложений по улучшению кода не откажусь)

Результат

Исходник
  • +33

Комментарии (13)

+2
кто то промазал по кнопке :)
+1
Выглядит круто! Заюзаем.
  • Vel
  • Vel
0
Делал похожее в Robots Can't Think, но у меня все было куда проще и гораздо менее эффектней. Почитаем-с.
  • z3lf
  • z3lf
+2
habrahabr.ru/post/153227/ недавно интересная статья проскакивала
0
Получилось здорово. Спасибо.
+2
Предчуствую появление этого эффекта в десятках разных игр))
  • ggman
  • ggman
0
Буду только рад, если мой класс кому то пригодится)
0
Зачетно!
0
отложил себе в коробочку. Спасибо!
0
Добротный эффект!
0
Спасибо всем за отзывы. На днях постараюсь улучшить эффект — сделать растворение не только статичного, но и анимрованного МувиеКлипа. Думаю растворение персонажа во время бега будет смотреться эпичнее)
0
Офигенно смотрится. Спасибо.
0
Спасибо. Очень выручил! Я не мог придумать финал персонажу. Эффект решил вопрос. Добавлю благодарность в Credits.
  • Plov
  • Plov
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.