jgame.js 2.2のリリース

今日jgame.js 2.2をリリースした。
http://sourceforge.jp/projects/jgame-js/news/24412

主な修正点

例によって修正点は20個超えしているので細かいところは変更履歴見てもらうとして、代表的なところ。

Tileクラスの調整

項目的にはTileクラスの調整ってのが一番多い。マップエディタ作る過程で色々整理したので。
衝突判定は結局自作が必要だけど、一応RPGで使えるレベルとしては及第点じゃねぇかなと思う。
たださすがにまだウディタとかよりは弱い。


「まだ」というか、いつまでも弱いかもしれない。
ネイティブと同じことをHTML5でやるのもいいけど、さすがにまだちっと早いんでねーのという気がする。

キーイベント処理の見直し

結構大きいのがキーイベント処理の見直し。
これまでdocumentのkeydownイベントを見ていたのを、canvasがフォーカスをもてるようにしてcanvasのkeydownで動くようにした。
この作りに直すにあたってはcocos2d-html5の実装を参考にさせてもらった。


これによって、「マウスでフォーカスを得る」ってのと「複数のゲームインスタンスを同一ページに配置し別々にキー操作出来る」ってのが可能になった。
前者はiframeでの利用で有効、後者はiframe以外で有効。


関連して気にいってるのがこの「Blogなどにミニゲームとして配置する」っていうサンプル。
http://jgame-js.sourceforge.jp/multi-game.html


元々jgame.jsは変化がない時のCPU利用率を極力抑えるように設計されていて、なぜかっていうとブログパーツにおける小さいゲームってのをサポートしたかったんだけど、キー入力も別々に出来るようになったんで、ブログパーツとして完全に使えるようになったと思う。
jgame.js製ゲームをブログに10個くらい置いても動作するし重くもならない。非稼動時はメインループも止めるからただの静止画扱いになる。


案外既存のゲームエンジンって非稼動時も高負荷だったりフルスクリーン前提だったりするんで、ここはjgame.jsの差別化要素になるかなと。

メインループの調整

ずっとやりたかったんだけど、メインループも直した。ここはコード貼ろう。
これが2.1までのメインループ。

main() {
	var fps_stack:number[] = [];
	var _main = (t:number) => {
		if (this._exit)
			return;

		if (t === undefined)
			t = Date.now ? Date.now() : new Date().getTime();
		if (this.tick > (t+10000) || (this.tick+10000) < t) {
			//this.tick > (t+10000): 前回更新分が10秒以上未来の時間の場合。多分タイマーバグっとるのでリセット
			//(this.tick+10000) < t: 10秒以上更新されてない。多分タイマーバグっとる。バグっとるよね?
			this.tick = t - 1;
			this.renderTick = t - this.targetFps;
			this.refresh();
		}

		var time = t - this.tick;
		if (this.tick < t) {
			this.raiseInputEvent();
			this.update.fire(time);
			this.tick = t;
		}

		for (var i=0; i<this.timers.length; i++)
			this.timers[i].tryFire(time);

		if (this.targetFps == 0 || this.renderTick <= t) {
			if (this.render)
				this.render.fire();

			this.renderer.render();
			if (this.targetFps)
				this.renderTick = t+this.targetFps;
			if (this.fps) {
				if (fps_stack.length == 19) {
					this.fps.innerHTML = Math.round(20000 / (t-fps_stack[0])).toString();
					fps_stack = [];
				} else {
					fps_stack.push(t);
				}
			}
		}

		window.requestAnimationFrame(_main);
	}

	this.tick = 0;
	this.renderTick = 0;
	window.requestAnimationFrame(_main);
}

2.2からのメインループ。

main() {
	var fps_stack:number[] = [];
	var _main = (t:number) => {
		if (this._exit)
			return;

		if (t === undefined)
			t = Date.now ? Date.now() : new Date().getTime();
		if ((this.tick+500) < t || this.tick > t) {
			//this.tick > t自体はタブ切り替え程度でも結構頻発する
			if ((this.tick+10000) < t || (this.tick > t+500))
				this.refresh();
			this.tick = t - 1000 / 60;
			this.renderTick = t;
		}

		var time = t - this.tick;
		this.raiseInputEvent();
		this.update.fire(time);
		this.tick = t;

		for (var i=0; i<this.timers.length; i++)
			this.timers[i].tryFire(time);

		if (this.renderTick <= t) {
			if (this.render)
				this.render.fire();

			this.renderer.render();
			if (this.targetFps)
				this.renderTick = t+this.targetFps;
			if (this.fps) {
				if (fps_stack.length == 19) {
					this.fps.innerHTML = Math.round(20000 / (t-fps_stack[0])).toString();
					fps_stack = [];
				} else {
					fps_stack.push(t);
				}
			}
		}

		window.requestAnimationFrame(_main);
	}

	this.tick = 0;
	this.renderTick = 0;
	window.requestAnimationFrame(_main);
}

スッキリした、けどそれが目的じゃなくて、重要なのはここ。

//前
		if (this.tick > (t+10000) || (this.tick+10000) < t) {
			//this.tick > (t+10000): 前回更新分が10秒以上未来の時間の場合。多分タイマーバグっとるのでリセット
			//(this.tick+10000) < t: 10秒以上更新されてない。多分タイマーバグっとる。バグっとるよね?
			this.tick = t - 1;
			this.renderTick = t - this.targetFps;
			this.refresh();
//今
		if ((this.tick+500) < t || this.tick > t) {
			//this.tick > t自体はタブ切り替え程度でも結構頻発する
			if ((this.tick+10000) < t || (this.tick > t+500))
				this.refresh();
			this.tick = t - 1000 / 60;
			this.renderTick = t;

スタンバイから戻った時などで、DOMに追加されてないCANVAS要素がお掃除されちゃうのか、正常にゲームが再開できないって問題があって、その問題の対処のためにthis.refreshってのがある。
で、このスタンバイから戻ったとかでどうも異常な状態になっているっぽい、って判定に以前は以前の更新から10秒経っているか現在の時間が前回の更新時より過去の時間になっているなら、という判定をいれていたんだけど、ここを2段階判定にした。


一つ目の判定は前回の時間より500ミリ秒以上経過しているかどうか。
二つ目の判定は前回の時間より10秒以上または現在時間が前回の時間より過去の時間。


二つ目の判定でrefreshをやるのは今も前も変わらないんだけど、その前に500ミリ秒で異常時間かどうかの判定を入れてる。
この異常時間になった場合、経過時間分の処理ではなく60分の1秒分の処理を行うように修正した。


これによって、ゲーム内での経過時間は最大10秒から0.5秒に短縮されたので、時間ベースエンジン特有のキャラがワープしちゃうとかの対策とかはしやすくなったと思う。
まあ0.5秒でも通常の30フレーム分なわけで、相当だけどな。


あと気にいらないのはfpsが残ってるけど、デバッグ情報以外はメインループこれで完成でいいかなという気がする。
一回C製ゲームエンジンの作者とかの意見も聞いてみたいけど。

その他の修正

他にはIE10サポートに力入れてみたり、ラベルのアンチエイリアスを見直してみたり、TypeScript 0.9.1.1に対応してみたり、ほとんどjgforce用だけどキーやマウス以外のユーザ定義イベントってのに対応してみたり。まあ色々。


3.0まで溜めてもよかったんだけど、jgforceで最新版jgame.jsを使っているのにjgame.jsがリリースされてないっつーのもね、ということで一応。
githubだとmasterにpushすりゃ勝手に皆zipダウンロードするだろって思うけど、sourceforgeだと勝手に皆SVNでチェックアウトはしないだろうし。

以前のリリースについて

jgame.js 2.1リリース当時の話はこっち。
jgame.js 2.1リリースと、3.0について
http://d.hatena.ne.jp/tsugehara/20130805/1375688955


2.0リリース当時の話はこっち。
jgame.js 2.0のリリース
http://d.hatena.ne.jp/tsugehara/20130426/1366959002


2.0リリースから2.1リリースの間に、ちょっと色々やることが出来て、2.0リリース当時の「あとはモジュールで」って予定が少し変わってきた。
TwitterのaltJS界隈の話が面白かったので、俺もTypeScriptに少しコミットしてみようかなと思ったせいもあるかもだけど、メインはjgforce関連すな。RPGツクールが2.1じゃ作れねぇよって事で。


ただ2.1当時に話していた3.0の予定についてはあんまり変わってない。
TypeScript 1.0のリリース予定がわからないので、jgame.jsのチケットで管理されてるマイルストーンは3.0と4.0で分けてるけど、上手くいけば3.0で両方やれるはず。
時期的な予定も変わってないから、年明けくらいかなぁとは思ってるけど。


2.2は2.x系最終リリースになればいいなぁと思ってるので、続きは今度こそぼちぼちやっていく予定。