最近、素敵な反射効果を取り入れているWebサイトやオンライン広告、そしてソフトウェアをよく見かけるようになりました。いわゆるWeb 2.0的デザイン要素で、アルバムのカバー写真やビデオプレーヤの下側に、あたかも床があって反射しているかのような表現です。この記事では、そうした反射効果をムービークリップに適用できるActionScript 3.0ベースのカスタムクラスを作る方法を解説します。そのクラスは、Reflectクラスと名付けましょう。
このReflect クラスは、手軽にインパクトのある要素をデザインに取り入れたい、そう考えている人たちのために作成しました。反射効果を一から作るとなるとかなりの努力が必要でしょう。しかも、反射させる素材を変更すると、それに合わせて反射効果の部分もやり直さなければなりません。静止画ならまだしも、アニメーションやビデオを手作業で反射させるとなると、不可能としか思えません。ところが、この記事を読めば、ムービークリップにコピー&ペーストして、反射具合を調整するだけで反射効果を実現することが可能です。アニメーションやビデオにも同じように反射を適用できます。しかも、Reflect クラスを一度適用させてしまえば、ムービクリップの内容を変更しても、それに合わせて反射効果部分も自動的に変更されるのです。
この記事やサンプルは、Flashコミュニティに対する私の愛の証でもあります。Flashコミュニティは、他のコミュニティとは違います。コードや機能について分からないことがあれば、誰かが進んで助けてくれます。実際、私は自分のブログでActionScript 2.0ベースの反射効果コードを紹介したとき、Flashコミュニティの親切なメンバーたちから、参考となるコメントや機能の要望などすばらしいフィードバックをたくさん頂きました。中には、Reflectクラスを改善するためのコードを提案してくれた人もいました。ここで紹介しているサンプルは、自分たちの時間を割いてまで私を助けてくれたFlashコミュニティメンバーたちの協力が込められたものなのです。
この記事では、以下のソフトウェアとサンプルを使用しています。
この記事は、ActionScript 2.0やActionScript 3.0、そしてActionScriptのクラスに関して中・上級レベルの知識を持っている人を対象としています。
Reflectクラスの解説に入る前に、反射効果がどのようなものかを実際に見ておいた方が理解しやすいでしょう。図1の反射効果エクスプローラには、いろいろとスライダーがあります。Reflection Codeボックス内には、プレビューの反射効果を実現するために必要なコードが表示されるようになっています。スライダーをドラッグすると、反射効果が変わり、右下のReflection Codeボックス内のコードもダイナミックに内容が変化しています。この反射効果エクスプローラを利用するには、Adobe Flash Player 9のバージョン9,0,45,0以降が必要です。なお、このプレビュー部分のムービクリップのインスタンス名は「ref_mc」としています。
To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
Download the free Flash Player now!
図1: 反射効果エクスプローラ。スライダーをドラッグすると反射効果が変化します
上部にある「Show Video」ボタンをクリックすると、反射させる素材が写真からビデオに切り替わります(ビデオを表示させているときは、写真に切り替わります)。気づいた人もいるかもしれませんが、写真を素材としているときは、コード内の「update time」を-1に設定しています。写真は静止画ですから、ビデオの場合と違い、定期的に反射効果部分をアプデートする必要がありません。そのため、「update time」を-1に設定して、プロセッサの負担にならないようにしているのです。他のパラメータについては、後ほど解説します。
クラスを作成する際は、必ずパッケージを指定しなければなりません。以下の行が、Reflect クラスのパッケージのパスです。
package com.pixelfumes.reflect
Reflectクラスのファイル名を「Reflect.as」とし、そのクラスファイルは複雑なフォルダ構成の中に置いています。クラスファイルを置くフォルダ構成は自由に決めることができますが、必ずパッケージと同じフォルダ構成にしなければなりません。たとえば、この記事のダウンロードサンプルを開くと、Reflect.asファイルはパッケージと同様に、comフォルダ内のpixelfumesフォルダ内のreflectフォルダ内に入っています。今回、ここまで複雑なフォルダ構成にしたのは、自分のコードが他のReflectクラスのコードライブラリと衝突しないようにするためです。
Reflectクラスでは、他のクラスも数多く利用しています。後に続くコードを見ればわかるように、これらのクラスのオブジェクトを使用します。Reflect クラスでそれらのオブジェクトを使用するには、まずは以下のコードのように利用するクラスをインポートします。
package com.pixelfumes.reflect{
import flash.display.MovieClip;
import flash.display.DisplayObject;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.geom.Matrix;
import flash.display.GradientType;
import flash.display.SpreadMethod;
import flash.utils.setInterval;
import flash.utils.clearInterval;
必要な他のクラスをインポートしたところで、Reflectクラスを書いていきましょう。今回作成するReflectクラスは、MovieClipクラスを拡張したものです。まずは、Reflectクラスに関連する情報をトラッキングするための変数を設定してます。
public class Reflect extends MovieClip{
//Created By Ben Pritchard of Pixelfumes 2007
//Thanks to Mim, Jasper, Jason Merrill and all the others who
//have contributed to the improvement of this class
//static var for the version of this class
private static var VERSION:String = "4.0";
//反射させるムービークリップ(被反射ムービークリップ)を参照
private var mc:MovieClip;
//ムービークリップのビジュアルコピーを保持するBitmapDataオブジェクト
private var mcBMP:BitmapData;
//反射像を保持するBitmapオブジェクト
private var reflectionBMP:Bitmap;
//グラデーションマスクとして機能するムービークリップ
private var gradientMask_mc:MovieClip;
//反射を更新する頻度(ビデオやアニメーションの場合)
private var updateInt:Number;
//反射領域
private var bounds:Object;
//ムービークリップと反射画像との距離
private var distance:Number = 0;
クラス内で参照される変数を定義したら、次にコンストラクタ関数を記述します。ReflectクラスをFLAファイルで使用するには、コンストラクタ関数を使って そのオブジェクトインスタンスを生成する必要があります。今回のサンプルでは、Reflectクラスのオブジェクトインスタンスをargsオブジェクトとし、ユーザーが定義した反射効果の設定情報をReflectクラスに渡す役割を担っています。たとえば、以下のようにReflectクラスをオブジェクトインスタンス化します。
import com.pixelfumes.reflect.*;
var r1:Reflect = new Reflect({mc:ref_mc, alpha:50, ratio:50,
distance:0, updateTime:0, reflectionDropoff:1});
お気づきの通り、ReflectクラスをFLAファイルで使用するには、既に定義しているパッケージパス、つまり「com.pixelfumes.reflect」を使用してReflectクラスをインポートする必要があります。インポートした後は、Reflectクラスのオブジェクトインスタンスを参照するための変数を定義して、オブジェクトに情報を渡します。本記事のサンプルでは、反射させるムービークリップに「alpha(反射の透明度)」「ratio(グラデーションマスクの割合)」「distance(ムービークリップと反射の距離)」「updateTime(更新頻度)」「reflectionDropoff(反射の欠け具合)」の情報を引数として渡しています。
Targsオブジェクトから受け継いだ引数をReflectクラスの変数に代入します。他にも、これらの引数をもとにムービークリップの高さや幅、反射範囲に関する変数を定義します。
function Reflect(args:Object){
/*argsオブジェクトから以下の変数に引き渡します。
/we set the values of our internal vars to math the args*/
//被反射ムービークリップ
mc = args.mc;
//反射像の透明度
var alpha:Number = args.alpha/100;
//グラデーションマスクの割合
var ratio:Number = args.ratio;
//更新頻度
var updateTime:Number = args.updateTime;
//反射の欠け具合
var reflectionDropoff:Number = args.reflectionDropoff;
//ムービークリップと反射像の距離
var distance:Number = args.distance;
//ムービークリップの高さと幅を保存
var mcHeight = mc.height;
var mcWidth = mc.width;
//反射範囲を保存
bounds = new Object();
bounds.width = mcWidth;
bounds.height = mcHeight;
お膳立てが終わったところで、いよいよReflection クラスのメイン部分です。Reflectクラスでは、BitmapDataクラスを多用しています。BitmapDataクラスにはドローメソッドがあり、ムービークリップのビジュルスナップショットを取得できます。このドローメソッドを使って、Reflectクラスに引き渡したムービークリップのスナップショットを取得し、そのスナップショットデータをBitmapオブジェクトに入れます。Bitmapオブジェクトを作成したら、上下を逆さまにして、Display Objectに追加します。以下のコードでは、BitmapDataオブジェクトやBitmapオブジェクトを作成し、その位置を設定しています。
//ムービークリップのスナップショットを保持するBitmapDataオブジェクトを作成
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
//反射像を保持するBitmapオブジェクトを作成
reflectionBMP = new Bitmap(mcBMP);
//反射像の上下を逆さまにする
reflectionBMP.scaleY = -1;
//反射像をムービークリップの下に移動
reflectionBMP.y = (bounds.height*2) + distance;
//ムービークリップのDisplay Stackに反射像を追加
var reflectionBMPRef:DisplayObject = mc.addChild(reflectionBMP);
reflectionBMPRef.name = "reflectionBMP";
ここまでのコードでコンストラクタとパッケージを閉じると、ムービークリップは図2のように表示されます。

図2:BitmapDataオブジェクトやBitmapオブジェクトを作成して位置を調節したあとの写真
ご覧の通り、Reflectクラスでは、ムービークリップのビジュル層をコピーして、その上下を逆さまにし、ムービークリップの下に配置しています。
反射像を適切な位置に置いた後は、次にグラデーションを保持するムービークリップを作成します。このムービクリップをグラデーションマスクに使用します。
//グラデーションマスク用の空のムービークリップを追加
var gradientMaskRef:DisplayObject = mc.addChild(new
MovieClip());
gradientMaskRef.name = "gradientMask_mc";
//ムービークリップとして戻るDisplayObjectを参照
that is returned as a MovieClip
gradientMask_mc = mc.getChildByName("gradientMask_mc")
as MovieClip;
グラデーションマスク用のムービークリップを追加したところで、実際のグラデーションに取りかかります。今回のグラデーションでは、様々な値を使用します。グラデーションを作成するには、受け取ったalphaとratio変数を使用します。これらの変数と、既に定義している反射範囲と合わせて、gradientMask_mc内にグラデーションボックスを構築します。
以下は、線状のgradientを作成するコードです。fillTypeやspreadMethodインスタンスを変えると、最終的なグラデーションマスク見た目が変わってきます。いろいろと試してみてください。
//グラデーションの塗りを設定
var fillType:String = GradientType.LINEAR;
var colors:Array = [0xFFFFFF, 0xFFFFFF];
var alphas:Array = [alpha, 0];
var ratios:Array = [0, ratio];
var spreadMethod:String = SpreadMethod.PAD;
//マトリックスとグラデーションボックスを作成
var matr:Matrix = new Matrix();
//グラデーションマスクのマトリックスの高さを設定
var matrixHeight:Number;
if (reflectionDropoff<=0) {
matrixHeight = bounds.height;
} else {
matrixHeight = bounds.height/reflectionDropoff;
}
matr.createGradientBox(bounds.width, matrixHeight,
(90/180)*Math.PI, 0, 0);
Wグラデーションマスク用の変数を設定したところで、gradientMask_mcムービークリップのグラフィクプロパティを使用して、GradientFill fill(塗り)を作成します。ムービークリップ内に塗りを作成したら、reflectionBMPにgradientMask_mcを重ねます。
//グラデーションの塗りを作成
gradientMask_mc.graphics.beginGradientFill(fillType, colors,
alphas, ratios, matr, spreadMethod);
gradientMask_mc.graphics.drawRect(0,0,bounds.width,bounds.height);
//反射像の上にグラデーションマスクを配置
gradientMask_mc.y = mc.getChildByName("reflectionBMP").y
- mc.getChildByName("reflectionBMP").height;
反射像がちゃんとグラデーションマスクでマスクされるようにするためには、グラデーションマスクとreflectionBMPの両方に「cacheAsBitmap
= true」を設定しなければなりません。
//グラデーションマスクを機能させるにはビットマップキャッシュを有効化する
gradientMask_mc.cacheAsBitmap = true;
mc.getChildByName("reflectionBMP").cacheAsBitmap =
true;
//反射像のマスクをグラデーションマスクとして設定
mc.getChildByName("reflectionBMP").mask =
gradientMask_mc;
コンストラクタ関数の最後のパートでは、ムービークリップのスナップショットを保持しているBitmapDataオブジェクトのリフレッシュが適切な更新頻度で行われているかをチェックします。動かない、アニメーションのないムービークリップであれは、BitmapDataオブジェクトをリフレッシュする必要はありません。その場合は「-1」を指定します。そうすることで余計なリフレッシュを行わずに済むので、プロセッサのオーバーヘッドを抑えることができます。もしムービークリップにビデオやロールオーバーに反応するボタンなどが含まれている場合は、常に反射像が適切なイメージとなるように更新頻度を設定します。
//スナップショットの更新
if(updateTime > -1){
updateInt = setInterval(update, updateTime, mc);
}
Reflectクラスの大部分ができました。あとは、いくつかのメソッドを使用して反射を調節します。setBoundsメソッドは、反射が有効なエリアを定義します。このメソッドで定義されるエリアのスナップショットを撮ることになるので、大きいな範囲をエリアとするとそれだけメモリを使うことになるので注意してください。setBoundsメソッドは、水平方向のタイムラインアニメーションを含むムービークリップに最適です。
図3を見てください。「Start Demo」をクリックすると反射像が表示され、ムービークリップが水平方向に移動します。「Stop Demo 」をクリックするとすべての効果が止まります。もし、実行時にムービークリップにはReflectクラスが適用され、タイムラインアニメーションで元のムービークリップの範囲の外へとムービークリップが移動するようになっていれば、反射効果は見えなくなります。それは、実行時のムービークリップのサイズによって定義された反射範囲から外に出てしまうからです。
To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
Download the free Flash Player now!
図3: setBoundsを使ったアニメーション
そこで、setBoundssetBoundsメソッドが活躍します。setBoundsメソッドを使えば、必要に応じて反射範囲を拡大して、アニメーションによる反射領域の変化に対応できるのです。
public function setBounds(w:Number,h:Number):void{
//ユーザーが反射範囲のエリアを設定できるようにする
bounds.width = w;
bounds.height = h;
gradientMask_mc.width = bounds.width;
redrawBMP(mc);
}
redrawBMPメソッドは、現状のBitmapDataオブジェクトを破壊し、新しいBitmapDataオブジェクトを作成することで、新しく定義された反射範囲に対応するためのものです。
public function redrawBMP(mc:MovieClip):void {
// 反射像を再描画- Mim Gamiet [2006]
mcBMP.dispose();
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
}
updateメソッドは、BitmapDataオブジェクト内のスナップショットのデータをリフレッシュすます。updateメソッドを実行する頻度が高くなれば、それだけプロセッサへの負担も大きくなります。ただ、その分、なめらかな反射像となります。更新頻度を設定するときは、無理な数字は設定せず、ビジュアルパフォーマンスに応じて設定しましょう。
private function update(mc):void {
//ビジュアルパフォーマンスに応じて更新頻度を設定
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
reflectionBMP.bitmapData = mcBMP;
}
反射効果を付けたら、その効果を解除する方法も必要です。解除の方法として、destroyメソッドを用意しています。このメソッドを使用すれば、DisplayObjectから反射のアイテムを削除し、インターバルをクリアにして、反射の要素をすべて取り除いてくれます。
public function destroy():void{
//反射の要素をすべて取り除く
mc.removeChild(mc.getChildByName("reflectionBMP"));
reflectionBMP = null;
mcBMP.dispose();
clearInterval(updateInt);
mc.removeChild(mc.getChildByName("gradientMask_mc"));
}
Reflectクラスを静止画に適用する場合は非常に簡単ですが、ビデオに適用する場合は少し工夫が必要です。ビデオを含むムービークリップを反射させるときは、ビデオが完全にロードされてからReflectクラスを適用させるようにしましょう。
Reflectクラスは、まだまだ発展の余地があります。みなさん、いろいろと試してみてください。私への連絡はブログ「pixelfumes.com/blog」経由でできます。Flashコミュニティにいる私たちがお互いを支えて助け合えば、プラットフォームとしてのFlashもどんどん大きくなることでしょう。
Ben Pritchard
ペンシルバニアのピッツバーグにある制作会社New PerspectiveのFlashデベロッパー。Pixelfumesの設立者。7 年以上に渡りWeb業界で活躍しており、American Advertising Federation and Communication Artsを含む各種アワードでの受賞歴を持つ。Pittsburgh
Flash Users Group (PittMFUG) の共同運営者でもある。