波紋のエフェクトを作成することになりました。
先のエントリーでやった「水面のようなエフェクト」はその習作だったのですが、クライアントがやっぱり波紋でやりたいということだったので作成することに。
「DisplacementMapFilter」を利用することは変わらないので、いかに置き換えマップを作成するのかがカギなのですが、計算をなるべく省きたい。そこで、検索で見つけた鮭さんのブログのやり方を参考にして、円形グラデーションでマップを作成することにしました。
※枠内クリックで実行
鮭さんのところでは、ひとつのグラデーション定義で波紋を表現されていたのですが、要望によっては同心円でない場合もあるし、減衰もさせたいとなると波紋ごとの管理がややこしくなりそう...。
そこで、グラデーション定義ではひとつの波紋だけを表現し、それを複数枚作成して重ねてしまおうと思い立ちました。これなら簡単に出来そうです。
ソースはこんな感じ
package jp.norisuke {
import flash.display.*;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.filters.DisplacementMapFilter;
import flash.filters.DisplacementMapFilterMode;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.utils.Timer;
public class Ripple extends MovieClip {
//ソースイメージ、ノイズイメージ、ビットマップ
public var sourceImage:BitmapData;
private var ripplesImage:BitmapData;
private var softLightBitmap:Bitmap;
private var bitmap:Bitmap;
//波紋を保持する配列
private var ripples:Array;
//波紋の回数
public var times:uint;
//波紋の間隔
public var interval:Number;
//フェードするか否か?
public var fadeFlag:Boolean;
//置き換えマップの移動強度
public var filterScale:uint;
//全ての波紋を生成したか否かのブール
private var timerCompleteFlag:Boolean = false;
//コンストラクタ
public function Ripple(g:BitmapData, _fs:uint = 20, _t:uint = 8 , _interval:Number = 200 , _fade:Boolean = true, _softlightFlag:Boolean = true )
{
sourceImage = g;
filterScale = _fs;
times = _t;
interval = _interval;
fadeFlag = _fade;
//
ripples = new Array();
bitmap = new Bitmap(sourceImage);
addChild(bitmap);
softLightBitmap = new Bitmap();
if(_softlightFlag) {
softLightBitmap.blendMode = BlendMode.HARDLIGHT;
softLightBitmap.alpha = 0.5;
addChild(softLightBitmap);
}
}
//実行
public function start():void
{
if(!timerCompleteFlag){
//タイマーを設定。引数で与えられた間隔と回数をそのまま定義
var timer:Timer = new Timer(interval, times);
timer.addEventListener(TimerEvent.TIMER, timerCountHandler)
timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler)
timer.start()
//波紋の進行はENTER_FRAMEでおこなう
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
}
//指定された回数が終われば、フラグをたてる。
private function timerCompleteHandler(e:TimerEvent):void
{
timerCompleteFlag = true;
}
//タイマーがカウントされたらObjectに波紋の中心点と現在のカウントを記録して、波紋管理用の配列へpush。
private function timerCountHandler(e:TimerEvent):void
{
var r:Object = new Object;
r.tx = Math.floor(Math.random() * sourceImage.width - sourceImage.width/2);
r.ty = Math.floor(Math.random() * sourceImage.height - sourceImage.height / 2);
r.r = 0;
r.count = e.target.currentCount;
ripples.push(r);
}
//波紋を更新する。
private function enterFrameHandler(e:Event):void
{
updateRipplesArray()
updateRipples();
updateBitmap();
}
//波紋管理用の配列の更新(規定値を超えたら配列から削除)
private function updateRipplesArray():void
{
var i:uint = 0;
for each(var r:Object in ripples) {
if (r.r > 255) {
ripples.splice(i, 1);
}
i++
}
}
//波紋管理用の配列からオブジェクトを取り出し、波紋をグラデーションで生成
private function updateRipples():void
{
var sprite:Sprite = new Sprite();
if (ripples.length == 0 && timerCompleteFlag) {
removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
timerCompleteFlag = false;
return;
}
for (var i:uint = 0; i < ripples.length; i++) {
var r:Object = ripples[i];
var shape:Shape = new Shape();
var g:Graphics = shape.graphics;
var matrix:Matrix = new Matrix()
matrix.createGradientBox(sourceImage.width, sourceImage.height, 0, r.tx, r.ty);
var _alpha:Number;
if(fadeFlag) {
_alpha = 0.5 * ((255 - r.r) / 255) * ((times - (r.count - 1))/times);
} else {
_alpha = 0.5 * ((255 - r.r) / 255) ;
}
g.beginGradientFill(GradientType.RADIAL, [0xffffff, 0xffffff, 0xffffff], [0,_alpha,0], [r.r, r.r+20, r.r+40], matrix, SpreadMethod.PAD);
g.drawRect(0,0,sourceImage.width,sourceImage.height)
//生成した波紋をスプライトに加えていく。
sprite.addChild(shape);
//半径
r.r += 8;
ripples[i] = r;
}
ripplesImage = new BitmapData(sourceImage.width,sourceImage.height,true,0xff7f7f7f)
//スプライトをBitmapDataにして、置き換えマップの完成。
ripplesImage.draw(sprite);
softLightBitmap.bitmapData = ripplesImage;
}
//ソースイメージにフィルタをかける
private function updateBitmap():void
{
var filter:DisplacementMapFilter = getBitmapFilter();
bitmap.filters = [filter]
}
//置き換えフィルタを得る
private function getBitmapFilter():DisplacementMapFilter {
var mapPoint:Point = new Point(0, 0);
var channels:uint = 2
var componentX:uint = channels;
var componentY:uint = channels;
var scaleX:Number = filterScale;
var scaleY:Number = filterScale;
var mode:String = DisplacementMapFilterMode.CLAMP;
var color:uint = 0;
var alpha:Number = 0;
return new DisplacementMapFilter(ripplesImage,
mapPoint,
componentX,
componentY,
scaleX,
scaleY,
mode,
color,
alpha);
}
}
}
使い方は
obj:WaterSurface = new Ripple(BitmapData, FilterScale, Times, Interval(ms));
addChild(obj);
obj.start()
といった感じ。引数は(ビットマップデータ、エフェクトの強さ、波紋の数、波紋の間隔(ms)、フェードの有無(bool)、水面のテカリの有無(bool))
Leave a comment