ライブラリ無しで

javaScriptで描画表現を行うとき、昨今はcreatejsに頼りっきりである。
Flashのような表示リスト、階層構造はとても使いやすい。
ただこれだけの機能、どの程度負荷がかかっているのであろうか?以前から気になっていたので簡単なサンプルで試してみた。

サンプルには、本ウェブサイトのaboutusページに使っているパーティクルの移動モーションを使う。

particle
1.createjs:Shapeを継承したParticleクラスを作成。Shapeの描画機能及びStage表示はcreatejsに頼る

http://nouv.biz/labo/20141209_createjs/nodeGarden_with_cs.html

2.ライブラリ無し:オリジナルクラスParticleクラスを作成。Particleクラスにcanvasを渡し、それぞれ描画
http://nouv.biz/labo/20141209_createjs/nodeGarden_2.html

結果(当然だけれど)圧倒的にcreatejsは重かった。
それはそうだ。今回のパーティクル(ただの円)であればcreatejsのShapeの豊富な機能は必要ないし、階層も考慮する必要は殆ど無い。いちいちcanvasを意識するのが面倒だけれど。

結局、ちょっとした演出程度に用いるのであれば「ライブラリを使わない」という選択肢もある、という結論でした。

衝突判定

ゲームを作らずとも一回や二回は「衝突判定」が必要となることがある。これまでは総当り方式で判定していた。総当り方式とは自分以外のオブジェクトが自分に接しているか、全て調べる方法である。 この方法は一般的でわかりやすいが効率は今ひとつ。総当りだから当然である。
そこで今回はグリッド方式を採用した。グリッド方式の詳細は「flash_actionScript3.0アニメーション 詳解」に記載されているのでそちらを参照いただきたい。
概要だけ整理すると
1.画面を一定の大きさのグリッドに区切り
2.そのグリッド内で総当り方式(グリッド内のオブジェクトがそれ以外に接しているか否か)
3.隣接するグリッド(そのグリッドの隣や上下)に含まれているオブジェクトが該当オブジェクトと接しているか総当り
の手順で調査する方法だ。
総当り方式だと、明らかに接していないオブジェクト同士(右上のオブジェクトと左下のオブジェクトが接しているか?など)も調査する。そのことを考えると、グリッド方式は理にかなった方法といえる。
上記書籍に記載されているのはFlash(ActionScript)なのでJavaScriptに置き換えたのが以下サンプル。
http://nouv.biz/labo/20140816_grid/CollisionGridTest.html 

グリッドを用いた衝突判定画像接触したオブジェクトのみ色を赤に変更している。

圧縮

「001111111111555555555599944578888」
とある案件でこのようなコードの桁数を短くする要望が発生してしまった。
ここは「圧縮」である。
「圧縮」でイメージするのはjpegやlzhなど。敷居が高く感じる。だが調べてみると思ったほど難しく無かった。

中でもランレングス圧縮という方法はわかりやすくすぐ実装できそうだ。
早速エディタを起動しキーボードを叩き始める。

ランレングスはざっくりいうと「同じデータはまとめてしまえ」という手法である。
「11111」のように数値の「1」が5回続いた場合は115( 1を2回書くことで2回以上続いていることを表す)のように記述する。
もっとも2回しか連続してしない場合は3文字使ってしまうので、元のデータより桁数は増えてしまう。
今回のコードは比較的同じ数値が連続することが多いので効果が期待できそう。

実装を終えテストしてみるとなかなかいい具合ではないか。
もう少し短くするため36進数変換し、さらに何箇所かにハイフンを入れる。これで大分見やすくなった。

今回はactionScript案件ではあったが、javascriptに移植してみた。
以下URLにてテスト可能なので興味があったらお試しを。
 http://nouv.biz/labo/20140810_runlength/RunLengthTest.html


フレームレートとパフォーマンスモニタについて

フレームレートとは元々は映像で用いられる言葉である。
アニメーションなどで1秒間に何コマ絵を動かしますか、という意味だ。1秒間に24コマの場合は24FPS(フレームパーセコンド)などと表される。FlashやJavaScriptなどで動作させるアニメーションでも用いられることが多い。

このフレームレート、Flashの場合直接フレームレートの指定が可能(デフォルトは12)だが、Javascriptの場合はtimer(setTimeoutなど)で逆算して指定することになる。
timerはブラウザにより大きく性能が異なるため、ゲーム等を制作する場合大きな障害となる。例えばキャラクタが画面を移動するとき、あるブラウザでは0.5秒かかり、別のブラウザでは2秒かかるなど、差異が大きすぎるとゲームとして成立しない。

この問題はオライリー・ジャパンの「JavaScriptグラフィックス」では以下の方法で対処している。

  1. どの程度のフレームレートが実現しているか調べる
  2. 実現したい速度と実際の速度の比率(=係数)を調べる
  3. 1フレームあたりのキャラクターの移動距離を標準の距離×係数で再設定する
  4. フレーム毎にキャラクターを再設定した距離移動させる

一方Flashサイトの場合、ゲーム用途よりバナーやトップページフラッシュなど演出目的のものが多いため、滑らかな動作が重要視される。滑らかに動かした方がより高級感を醸し出せるからだ。
滑らかに動かすためには、アニメーションは1秒のコマ数を増やせばよい。そこで多くの製作者は極力高いフレームレートを設定することになる。

ところが高くすればするほどパソコンに負荷がかかり、スペックの低いパソコンでは設定したフレームレートの半分も出せないことがある。結果カクカクした動きになっしまう。

その対策については、こちらもオライリー・ジャパンの「Flash Hacks」にわかりやすく記載されている。
まず上記1同様、「どの程度のフレームレートが実現しているか」を調べ、オブジェクトが多数動くことが負担になっている場合は、動作しているオブジェクト(キャラクターや粒子など)の数を減らしている。
上記書籍は2005年発売でActionScript1.0又は2.0で記述されているので、今回はActionScript3.0に置き換えつつ、より実践的にしたPerformanceMonitorクラスを作成した。
以下がそのサンプルとなる(http://nouv.biz/labo/20121004_performance_montor/)。
大量のパーティクル(粒子)を生成し、ランダムにY回転させている(重いので他作業中の方は注意)。

右下に、実現しているフレームレートと、パーティクルの数を表示している。
フレームレートは24FPS、パーティクルの初期生成数は10万個を初期値にしているが、フレームレートが24フレームに達しないと、序々にパーティクルの個数を減らし調整する。

フレームレートが目標に達しているか調査する部分のスクリプトを以下に記載。

  private function onEnterframe(event:Event):void {
   var nowTime:Number = new Date().getTime();
   var pastTime = nowTime-oldTime ; 
   var fps:Number = 1000 / pastTime;

   if (interCount > 100) {
    //過去の累積が悪影響をおよぼすので、ある程度の回数でクリアする
     totalFPS = 0;
     interCount = 0;
   }
   totalFPS += fps;
   interCount++;
   oldTime = nowTime;
  }

  /*
   * 
   */
  public function get _averageFps():int {
   var averageFPS:Number = Math.floor(totalFPS / interCount );
   return averageFPS;
  }

  /**
   *達成したいフレームレートを指定する
   * 達成できているかどうかを返す
   */
  public function fpsBool(targetFPS:int):Boolean {
   var bool:Boolean;
   if (_averageFps >= targetFPS) {
    bool = true;
   }else {
    bool = false;
   }
   return bool;
  }

現時刻から前に通過した時刻を引き、そこからフレームレートを求めその値を返す。
一方、パーティクルはその結果を受けて個数調整を行なっている。
numBallsは生成するパーティクルの個数。上限下限を設けている。

	
performanceBool = performanceMonitor.fpsBool(24);
if (!performanceBool) {
	numBalls = (numBalls - 100) > 0? numBalls - 100 : numBalls;
}else {
	numBalls = (numBalls + 100) <= makeNum ? numBalls + 100 : makeNum;
}

 

JavaScriptのクラス記述方式(継承)

前回作成したDHTMLSpriteクラスを継承した、bouncySpriteクラスを作成する。
関数を用いたクラス生成方式の場合、継承は以下のとおりとてもわかりやすい。

var bouncySprite=function(params){
	var x=params.x;
	var y=params.y;
	var xDir=params.xDir;
	var yDir=params.yDir;
	var maxX=params.maxX;
	var maxY=params.maxY;
	var animIndex=0;
	var that=DHTMLSprite(params);
	that.moveAndDraw=function(){
		x+=xDir;
		y+=yDir;
		animIndex+=xDir>0 ? 1:-1;
		//進行方向に合わせて歯車の回転も変える。
		animIndex%=5;
		//1行4列 5の余りをx軸(列数)とする
		animIndex+=animIndex0 && x>=maxX)){
			xDir=-xDir;
		}
		if( (yDir0 && y>=maxY)){
			yDir=-yDir;
		}
		that.changeImage(animIndex);
		that.draw(x , y);
	}

	return that;
}

使うプロパティと追加のプロパティを自分で引き受け、thatとしてDHTMLSpriteのインスタンスをthatとして生成する。そして、そのthatに新たなメソッドを付け加える。

一方、パターン2の教科書的方法は少々ややこしい(まだバグ無くならず)。こちらを参照。

2012.9.12 バグ解消。全文記載

function BouncySprite(params){
	//新たに追加されたプロパティを記述する
	this.x=params.x;
	this.y=params.y;
	this.xDir=params.xDir;
	this.yDir=params.yDir;
	this.maxX=params.maxX;
	this.maxY=params.maxY;
	this.animIndex=0;
	DHTMLSprite.call(this , params);
	//親のオブジェクトのコンストラクタを呼び出す
}
BouncySprite.prototype=new DHTMLSprite();
BouncySprite.prototype.moveAndDraw=function(xPos , yPos){
	with(this){
		xPos+=xDir;
		yPos+=yDir;
		animIndex+=xDir>0 ? 1:-1 ; 
		//進行方向に合わせて歯車の回転方向を変える
		animIndex%5; 
		//1行4列 5の余りをx軸(列数とする)	
		animIndex+=animIndex0 && xPos>maxX)){
			xDir=-xDir;	
		}
		if( (yDir0 && yPos>=maxY)   ){
			yDir=-yDir;
		}
		changeImage(animIndex)	;
		draw(xPos, yPos);
	}
}

継承は関数形式の方がはるかに使いやすそう。