datchの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【Wonderfl移植計画】LightningEffect 解説編[2作目]

wonderflのFavoriteTop100にランクインしている作品をCreateJSを使って移植しようという計画。

ライセンスはMITなのを確認していますが、
制作者様の方から何かアクションがあった場合は削除させていただきます。

66位 LightningEffect

移植元のFlashソースコード

CreateJSのデモ

jsdo.itのページ

それにしても、この移植は"これじゃない感"が漂ってますね。

結論はCreateJSでのGlow表現をBitmapDataに反映させることが無理でした。

自分で実装するのも大変なのでこれは公式のGlowFilter実装待ちですね。

前回はソースコードを載せただけなので、今回はその解説を行いたいと思います。


説明する前に今回使用するcreatejs.BitmapDataのコンストラクタにバグ?があったので注釈

createjs.BitmapDataの注意点

下記のコードでは、実際は一行で透明な W x H サイズのBitmapDataが作成されるはずだが、

なぜか結果を見ると透明度の部分が反映されていなかったので、clearRect()で透明にしている。

バグなのか?

_canvas = new createjs.BitmapData(null, W, H, 0x00000000);
// BitmapDataのバグで初期化時の透明度が反映されないため、初期化
_canvas.clearRect(0, 0, W, H);
残像表現の作り方

Flashではお馴染みの残像表現。

CreateJSでどのように作成すればいいのか、と悩んでいましたがとても良いライブラリを見つけました。

それがこいつ (ピクセル職人に捧ぐ – BitmapData for EaselJS – | kudox.jp)

BitmapData for EaselJS

Flashでの残像表現は一般的に、BitmapDataの値をColorTransformというメソッドを使用して行っていました。

これがCreateJSに標準で存在しなかった為、非常に困っていたんですが有志の方が作ってくれていました。

本当に感謝感謝です!

では、さっそく残像表現を行っているコードに注目してみよう。

/**
* ([redMultiplier=1]  [greenMultiplier=1]  [blueMultiplier=1]  [alphaMultiplier=1]  [redOffset=0]  [greenOffset=0] [blueOffset=0]  [alphaOffset=0])
* 新しい値 = (古い値 * Multiplier) + Offset
* ここでは、赤緑の色を徐々に薄めながら、
* 透明度を上げる色変換オブジェクトを作成している。
* alphaOffsetを-1に設定しているのは、
* 乗算で完全に透明にしきれなかった部分を透明にする為。
* stage.addChild(fill)をコメントアウトして、alphaOffset=0にすれば書いてある意味が分かる
*/ 
_ctf = new createjs.ColorTransform(0.9, 0.96, 1, 0.9, 0, 0, 0, -1);

:::
:::
:::

// _canvas全体の色を変化させる
_canvas.colorTransform(_canvas.rect, _ctf);

コード見て驚いた方も居ると思いますが、
コメントの部分や細かい実装を除けば残像表現はたった2行で実現出来てしまいます。

ここで重要なのは色を変化させる createjs.ColorTransorm です。

このオブジェクトをBitmapData.colorTransform()メソッドに適応することで色を変化させることが出来ます。

残像表現で最も重要な値がalphaMultiplierです。

この値を1.0未満に設定することで描画されたイメージが徐々に消えていき、
残像のような表現を得ることが出来ます。

こうすることで前に描かれた描画内容を徐々に消すことが出来ます。

また値の大小で消えるまでの時間も調整することが可能なので、是非触ってみて下さい!

次にぼんやりとした光を表現する方法について解説する。

露光表現

以下は今回残念な結果になってしまったGlow表現に対応するソースコードです。

_sp = new createjs.Shape();
// CreateJSでのDisplayObjectへのGlowの掛け方
// しかし、cache()には反映されないので公式でのGlowFilter実装待ち
//_sp.shadow = new createjs.Shadow("#C9E6FC", 0, 0, 10);
// cache()を使うことでcreatejs.BitmapDataのdrawで利用可能にする
_sp.cache(0, 0, W, H);

コードにも書いてあるがCreateJSでのGlowFilterに似た表現をする方法は、

DisplayObjectのshadowプロパティに色とブラーの度合いを設定すればいい。

がしかし、残念なことにこのshadowプロパティはcache()に反映されないのだ。

つまり、BitmapDataに反映されないということである。

その結果がこの残念な感じのエフェクトである。

shadowプロパティがcacheに反映されるのを待つか、GlowFilterが公式で実装されるのを待つか、

もしくはひとつひとつのピクセルを描画するたびにGlowエフェクトを自分で実装して表現するか、

今回は公式の実装待ちということで対処しました。

// Glowエフェクトの広がりの度合いだけ、サイズを小さくする
_glow = new createjs.BitmapData(null, W / RANGE, H / RANGE, 0x00000000);
_glow.clearRect(0, 0, W, H);
var glowBm = new createjs.Bitmap(_glow.canvas);

// FlashのblendMode = "add"と似た効果
glowBm.compositeOperation = "lighter";
// 小さくした分だけスケールを変更する
// こうすることで1ピクセルの描画が非常に大きく表示され、
// Glowエフェクトが掛かったようになる
glowBm.scaleX = glowBm.scaleY = RANGE;
// 描画時のスケール変換用行列
_matrix = new createjs.Matrix2D(1 / RANGE, 0, 0, 1 / RANGE);

:::
:::
:::

// _canvasの内容をスケールして描画する
_glow.draw(_canvas, _matrix, null, null, null, true);

そして、よく使われるテクニックとして、BitmapDataの縮小書き込み・拡大表示がある。

BitmapDataをCanvasの大きさよりもN等分だけ小さくしてインスタンス化し、

エフェクトの影響を与えたい描画をこのBitmapDatanに書き込んで、

表示するときにN倍だけ大きくして表示することで光がぼんやりとした感じを表現することが出来る。

Flashではピクセル吸着の設定が出来るが、CreateJSでは無理なのでちょっと格好悪い表現になってしまっているが…

また、compositeOperation = "lighter" という設定を行うことで、

Stage上で他のオブジェクトと重なった場合は色の加算処理を行うようになる。

色の加算を行うことで色が白くなりやすく、明るく、輝くような表現が出来る。

蛇足だがFlashではこのBitmapDataにGlowFilterを適用することでキラキラした表現が可能になる。

雷の描画

update()は毎フレーム呼ばれる描画処理を行う関数である。

ここで雷の描画、createjs.BitmapDataで色変換による残像表現を行い、

最終的に処理を行ったBitmapDataをCanvasで表示する。

/**
* 雷エフェクトの描画
*/
function update()
{
	// 稲妻を表現するための線を描画する為の位置を保持
	var p = new createjs.Point();
	// 透明度をランダムで決定
	var num = Math.random() * 5;
	// エフェクトの開始位置を代入
	p.x = _p.x;
	p.y = _p.y;
	var g = _sp.graphics;
	// _spの以前の描画内容を消去
	g.clear();
	// 描かれる線のスタイルと開始位置を設定
	// FlashのlineStyleと同義
	g.beginStroke(createjs.Graphics.getRGB(255, 255, 255, 1 - (num / 10)))
	.setStrokeStyle(num)
	.moveTo(p.x, p.y);
	var i = p.y;
	// ここで雷の線をcanvasの下端まで描画する処理を行う
	// 線が一番下まで描画されるまで実行し続ける
	while(i < H)
	{
		// y方向の線の長さをランダムで決定
		var n = Math.random() * 10;
		i += n;
		p.y = i;
		// x方向の線の長さをランダムで決定
		p.x += Math.random() * (n * 2) - n;
		// 線を描画
		g.lineTo(p.x, p.y);
	}
	// cacheの内容を更新する
	_sp.updateCache();
	// _canvas全体の色を変化させる
	_canvas.colorTransform(_canvas.rect, _ctf);
	// 雷を_canvasに描画する
	_canvas.draw(_sp);
	// 元のコードには無い
	// しかし、この一文が無いとGlowエフェクトの描画が残り続けてしまう
	_glow.colorTransform(_glow.rect, _ctf);
	// _canvasの内容をスケールして描画する
	_glow.draw(_canvas, _matrix, null, null, null, true);
	stage.update();
}

雷の描画部分では線の書式や、描画開始位置を設定した後に、

一定の範囲内にランダムで線を描画することで稲妻が走っている表現をしている。

この描画されたDisplayObjectの内容をupdateCache()でcreatejs.BitmapDataに反映可能にし、

2つのBitmapData(_canvas:雷本体, _glow:ぼんやりとして明るい雷 + _canvasの色を加算)を描画して、処理を終了する。

何故_glowはFlashの方ではcolorTransformが必要ではなく、CreateJSでは必要なのか、

というのがいまいち分かっていません。

おそらく、canvasでは透明度が自動でサポートされるので上書きによる塗りつぶしが出来ない所為だと思います。

おそらくなので信用しないで下さい。


このコードの全容はこんな感じといったところでしょうか。


最後までこの記事を読んで頂きありがとうございました。