datchの日記

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

【Wonderfl移植計画】砂ゲー 解説編【5作目】

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

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

全体の構成

このプログラムの構成は以下のとおり、

  • 砂を上端から発生させる
  • クリックした時にパーティクルを発生させる
  • パーティクルの移動

以上をそれぞれ解説していく.

砂を上端から発生させる

RUN()関数の中の一番上。

単純に10%の確率で(x, y) = (200~250, 0)に砂を発生させる単純な処理である。

for(var I = 200; I < 250; ++I)
{
	if(Math.random() < .1)
	{
		_CANVAS.setPixel(I, 0, SAND_COLOR);
	}
}
クリックされた時にパーティクルを発生させる

ここで工夫していた点があり、マウスが早く移動した時に途切れ途切れになってしまうのを、

前のマウス座標と今のマウス座標を一定の感覚でブレンドすることで補完するようになっている。

例えば、(300, 200) → (330, 210) と移動したときに、

(301, 200) → (303, 201) → (304, 201) と順次補完されていく。

次にFlashからCreateJSに移行するときの勘所。

どうやらcanvasはフラッシュのBitmapDataとはことなりピクセルの粒度が細かい。

一般的にピクセルは整数値の座標しか取らないのだが、canvasではそうではなく小数点まで扱うようだ。

(そもそも整数値の座標を扱うのをピクセルというのであって、小数点の座標を扱うのはもはやピクセルと呼ぶべきではないのかも)

ベクター形式を扱っているのかは、著者が勉強不足のため不明。

そのため、Math.floor()をつかうことで整数型に整形して問題を解決している。

// クリックフラグが立っている場合
// 指定した状態のパーティクルを発生させる
if(_CLICK)
{
	for(var J = 0; J < 20; ++J)
	{
		var R = J / 20;
		// @note
		// _BX : 1フレーム前のマウスX座標
		// _BY : 1フレーム前のマウスY座標
		//  R  : 0.00 ~ 0.95 (0.05)刻みで上昇
		//(1-R): 1.00 ~ 0.05 (0.05)刻みで減少
		// 以下の式は1フレーム前と現在のマウス位置までの間を補完する
		// 尚、canvasではピクセルの粒度が細かいのかMath.floorで整数にしないと、小数点部分にピクセルが出来てしまい、それらは動作の対象にならない問題がある
		_CANVAS.fillRect(new createjs.Rectangle(Math.floor(_BX * R + stage.mouseX * (1 - R)), Math.floor(_BY * R + stage.mouseY * (1 - R)), 5, 5), _COLOR);
		// @note 試しに以下の式で1フレーム前の値を加味しないと飛び飛びに矩形が出来て、マウスを早く移動すると隙間が出来てしまう
		//_CANVAS.fillRect(new createjs.Rectangle(Math.floor(stage.mouseX), Math.floor(stage.mouseY), 5, 5), _COLOR);
	}
}
// 1フレーム前の座標として保存
_BX = stage.mouseX;
パーティクルの移動

毎フレーム、全てのピクセルを色によって一定のルールで移動・待機させる。

まず黒の何もない状態をあらわす色は、もちろん何も変化を加えない。

if(C == ERASE_COLOR)
{
	continue;
}

問題は砂と水のパーティクルの扱いなのだが、

砂のパーティクルは水のパーティクルの動作を継承しているので、

砂の動作を解説すれば水の動作も理解できるので、砂の動作を解説する。

まずは落下の動作から。

  1. 1つ下のピクセルの色を取得
  2. 下に何もない場合はそのまま1つ下に移動させる
  3. 下が水の場合は、一定の確率(ここでは50%)で水と砂の位置を置換させる

という処理になっている。

{//[落下]
	T = _CANVAS.getPixel(X, Y + 1);
	// 何もない場合は砂をそのまま落下させる
	// 正確には砂の色(茶色)と何もない色(黒)を置換させる
	if(T == ERASE_COLOR)
	{
		_CANVAS.setPixel(X, Y 	 , T);
		_CANVAS.setPixel(X, Y + 1, C);
		continue;
	}
	// [水より砂の方が重い、適当な確率で場所の置換を許す。]
	if(T == WATER_COLOR && Math.random() < .5)
	{
		_CANVAS.setPixel(X, Y    , T);
		_CANVAS.setPixel(X, Y + 1, C);
		continue;
	}
}

左右の動作も、横移動がランダムな点を覗いて同じ。

{//[左右移動]
	// -3~3の間で横方向の移動量を決定する
	TX = X + Math.floor(Math.random() * 7) - 3;
	// 移動先のピクセルを取得する
	T = _CANVAS.getPixel(TX, Y);
	if(T == ERASE_COLOR)
	{
		_CANVAS.setPixel(X , Y, T);
		_CANVAS.setPixel(TX, Y, C);
		continue;
	}
	// [水より砂の方が重い、適当な確率で場所の置換を許す。]
	if(T == WATER_COLOR && Math.random() < .8)
	{
		_CANVAS.setPixel(X , Y, T);
		_CANVAS.setPixel(TX, Y, C);
		continue;
	}
}

水の動作は砂が移動先にあった場合移動処理を行わないだけで、他はほとんど一緒。

// WATER
// 水の場合は砂との置換が行われない
// そのほかは砂と同様の動作
if(C == WATER_COLOR)
{
	{//[落下]
		T = _CANVAS.getPixel(X, Y + 1);
		if(T == ERASE_COLOR)
		{
			_CANVAS.setPixel(X, Y 	 , T);
			_CANVAS.setPixel(X, Y + 1, C);
			continue;
		}
	}
	{//[左右移動]
		TX = X + Math.floor(Math.random() * 7) - 3;
		T = _CANVAS.getPixel(TX, Y);
		if(T == ERASE_COLOR)
		{
			_CANVAS.setPixel(X , Y, T);
			_CANVAS.setPixel(TX, Y, C);
			continue;
		}
	}
}

非常にシンプルである。

以上で解説を終わります。

最後まで呼んでくれた方はどうもありがとうございました。