datchの日記

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

【Wonderfl移植計画】Light Burst【8作目】

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

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

80位 LightBurst

移植元のFlashソースコード

CreateJSのデモ

こいつの解説編が終わったらせっかくインフラの勉強を始めるので、そちらの記事を中心に書いていこうと思う。

といってももちろん移植計画は別に続ける方向で調整します。

とりあえず、今回はソースコードだけ掲載。

解説は次回!

// EaselJS 0.7.1
// BitmapData for EaselJS 1.0.0
// @see [http://kudox.jp/java-script/createjs-easeljs-bitmapdata]
// GlowFIlter for EaselJS
// @see [http://kudox.jp/java-script/createjs-easeljs-glowfilter]

var stage;

var W; // {const int} canvasの横幅
var H; // {const int} canvasの縦幅

var CX;
var CY;

var vOutline = new Array();
var lx = 0;
var canvas;

var container;
var glowContainer;

var myText;
var glowText;

var _screen; // javascripのscreenオブジェクトと名前衝突したので_を付けて回避
var buffer;

var lightBuffer;

var glowTextMask;

var blurFilter = new createjs.BlurFilter(12, 35);

window.onload = init;

// CreateJSのPointクラスにpolar関数がなかったのでこちらで追加
// 極座標を求める
function polar(d, rad)
{
	var x = d * Math.cos(rad);
	var y = d * Math.sin(rad);
	return new createjs.Point(x, y);
};

// CreateJSではSimpleTextは存在しないのでTextクラスで代用
function init()
{
	stage = new createjs.Stage('canvas');
	W = stage.canvas.width;
	H = stage.canvas.height;
	CX = W / 2;
	CY = H / 2;

	canvas = new createjs.Shape();

	glowContainer = new createjs.Container();
	stage.addChild(glowContainer);

	var overlayRect = createOverlayRect();
	var trimRect = createTrimRect();

	lightBuffer = new createjs.BitmapData(null, W, H, 0x000000);

	myText = new createjs.Text('Light Burst', '46px Georgia', "#98833D");
	glowText = new createjs.Text('Light Burst', '46px Georgia', "#F5DC8D");
	glowText.filters = [new createjs.GlowFilter(0xFFE8AB, 1, 10, 10, 4, 2), new createjs.BlurFilter(7, 7, 1)];

	var vTemp = new Array();
	// const はgoogleのCoding規約で使うことを推奨していないので使わない
	var TW = myText.getBounds().width;
	var TH = myText.getBounds().height;
	// createJSのfilterはcacheを適用しないと反映されない
	glowText.cache(0, 0, TW, TH);

	myText.x = (W - TW) / 2;
	myText.y = (H - TH) / 2;

	// [Text -> BitmapData]
	// 'Light Burst'という文字をBitmapDataに落としこむ
	var bmd = new createjs.BitmapData(null, TW, TH);
	myText.cache(0, 0, W, H);
	bmd.draw(myText);
	// stage.addChild(new createjs.Bitmap(bmd.canvas));
	// stage.update();
	// return;	

	// [getPixelColor]
	// 画像処理の癖で座標の指定は(x, y)で統一していたので(w, h)というのは新鮮に感じた、というどうでもいい個人的な感想
	// Booleanという型で暗黙のキャストをしていて対応していたのを、きっちり変換
	for(var h = 0; h < TH; ++h)
	{
		for(var w = 0; w < TW; ++w)
		{
			var color = bmd.getPixel(w, h);
			vTemp[h * TW + w] = (color != 0x00000000) ? true : false;
		}
	}

	// [Gain inner Pixels]
	for(h = 0; h < TH; ++h)
	{
		for(w = 0; w < TW; ++w)
		{
			var flg = false;
			var pos = h * TW + w;
			// 画面端なら無条件でフラグを立てる
			if(h == 0 || h == TH - 1 || w == 0 || w == TW - 1)
			{
				flg = vTemp[pos];
			}
			// 画面端でなければ
			else
			{
				//flg = false; // 必要ないと判断し、コメントアウト
				// ピクセルに文字が描画されており
				if(vTemp[pos] == true)
				{
					// ピクセルの4方向全てに文字が描画されていなければ(色が付いていなければ)フラグを立てる
					// ※つまり、文字のエッジ部分を対象にしている
					if(vTemp[pos + TW] + vTemp[pos - TW] + vTemp[pos - 1] + vTemp[pos + 1] < 4)
					{
						flg = true;
					}
				}
			}
			// フラグが立っているならば、位置を記憶する
			// テキストの大きさ/2でずらしているのはBitmapの文字の移動に対応するため?
			if(flg)
			{
				vOutline.push(new createjs.Point(w - TW / 2, h - TH / 2));
			}
		}
	}

	glowText.x = myText.x;
	glowText.y = myText.y;
	// cacheはcanvasの機能を用いて作成
	//glowText.cacheAsBitmap = true;
	glowText.updateCache();

	glowTextMask = createGlowTextMask();
	// glowTextMask.cacheAsBitmap = true;
	glowText.mask = glowTextMask;
	glowTextMask.scaleX = 1.35;
	// !勘所! マスクは表示リストから省く
	glowTextMask.visible = false;
	glowTextMask.alpha = 0;
	glowTextMask.cache(0, 0, glowText.getBounds().width + 40, glowText.getBounds().width + 40);

	glowContainer.addChild(glowText)
	// glowContainer.blendMode = BlendMode.LAYER;

	container = new createjs.Container();
	stage.addChild(container);
	container.addChild(glowContainer);
	container.addChild(new createjs.Bitmap(lightBuffer.canvas));
	container.addChild(myText);
	//container.addChild(overlayRect);
	container.addChild(trimRect);
	container.visible = false;

	buffer = new createjs.BitmapData(null, W, H, 0x000000);
	screen = new createjs.Bitmap(buffer.canvas);
	stage.addChild(screen);

	createjs.Ticker.addEventListener('tick', xAnimation);
	createjs.Ticker.setFPS(60);
	zoomOutScreen();
}

function xAnimation()
{
	lx += 5;
	if(lx > W)
	{
		lx = 0;
		zoomOutScreen();
	}
	glowTextMask.x = lx;

	// [Drow]
	// Draw
	var g = canvas.graphics;
	g.clear();
	g.beginStroke(createjs.Graphics.getRGB(0xFF, 0xEB, 0x79, 0.3));
	g.setStrokeStyle(1);
	var RANGE = 17;
	var len = vOutline.length;
	for(var i = 0; i < len; ++i)
	{
		var tx = vOutline[i].x + CX;
		if(tx > lx - RANGE && tx < lx + RANGE || (i % 10 == 1 && tx > lx - RANGE * 5 && tx < lx + RANGE * 5))
		{
			var r = Math.atan2(vOutline[i].y, vOutline[i].x + (CX - lx));
			var dp = polar(CX * 3, r); // [このCXは長さ]
			g.moveTo(vOutline[i].x + CX, vOutline[i].y + CY);
			g.lineTo(vOutline[i].x + lx + dp.x, vOutline[i].y + CY + dp.y);
		}
	}
	g.endStroke();
	stage.update();
	lightBuffer.fillRect(lightBuffer.rect, 0);
	canvas.cache(0, 0, W, H);
	lightBuffer.draw(canvas);
	lightBuffer.applyFilter(lightBuffer, lightBuffer.rect, new createjs.Point(), blurFilter);
	lightBuffer.applyFilter(lightBuffer, lightBuffer.rect, new createjs.Point(), blurFilter);

	buffer.fillRect(buffer.rect, 0);
	container.cache(0, 0, container.getBounds().width, container.getBounds().height);
	buffer.draw(container, null, null, null, null, true);

	stage.update();
}

function zoomOutScreen()
{
	var s = 1.2;

	screen.scaleX = screen.scaleY = s;
	screen.x = -W * (s - 1) / 2;
	screen.y = -W * (s - 1) / 2;
	createjs.Tween
		.get(screen)
		.wait(900)
		.to({scaleX:1, scaleY:1, x:0, y:0, time:2}, 2000, createjs.Ease.quadOut);
}

function createGlowTextMask()
{
	var G_PADDING = 20;
	var GW = glowText.getBounds().width + G_PADDING * 2;
	var GH = glowText.getBounds().height + G_PADDING * 2;

	console.log(glowText.getBounds());

	var shape = new createjs.Shape();
	var g = shape.graphics;
	var r = GH / 2;
	g.beginLinearGradientFill(
		[createjs.Graphics.getRGB(0x00, 0x00, 0x00, 1), createjs.Graphics.getRGB(0xFF, 0xFF, 0xFF, 0)],
		[0, 1],
		2 * r, 2 * r, -r, -r
		);
	g.drawCircle(0, 0, r);
	g.endFill();

	shape.x = (W - GW) / 2 + r;
	shape.y = (H - GH) / 2 + r;

	return shape;
}

function createOverlayRect()
{
	var SCALE_W = 2;
	var SCALE_H = 1.2;
	var RW = SCALE_W * W / 2;
	var RH = SCALE_H * H / 2;

	var shape = new createjs.Shape();
	var g = shape.graphics;
	g.beginLinearGradientFill(
		[createjs.Graphics.getRGB(0xFF, 0xFF, 0xFF, 1), createjs.Graphics.getRGB(0, 0, 0, 1)],
		[0, 1], 2 * RW, 2 * RH, 0, 0);
	g.drawEllipse(0, 0, RW * 2, RH * 2);
	g.endFill();

	// !勘所! 現在のShapeにはboundsを自動的に計算しないのでsetBoundsをしないといけない
	// 今回は適当な値を入れて対処した
	var shapeWidth = RW * 2;
	var shapeHeight = RH * 2;
	shape.x = (W - shapeWidth) / 2;
	shape.y = (H - shapeHeight) / 2;
	//shape.blendMode = BlendMode.OVERLAY;
	//shape.cacheAsBitmap = true;
	//shape.cache(0, 0, shapeWidth, shapeHeight);

	return shape;
}

function createTrimRect()
{
	var TY = 35;

	var shape = new createjs.Shape();
	var g = shape.graphics;
	var mat = new createjs.Matrix2D();
	// !勘所! 引数の数の違いに注意
	g.beginLinearGradientFill(
		[createjs.Graphics.getRGB(0, 0, 0, 1), createjs.Graphics.getRGB(0, 0, 0, 0), createjs.Graphics.getRGB(0, 0, 0, 0), createjs.Graphics.getRGB(0, 0, 0, 1)],
		[0, 51/255, 204/255, 255/255], W, H - TY * 2, 0, TY);
	g.drawCircle(0, 0, W, H);
	g.endFill();
	mat.rotate(Math.PI / 2);
	mat.decompose(shape);
	//shape.cacheAsBitmap = true;
	shape.cache(0, 0, W, H);

	return shape;
}