【enchant.js】アトラスXの例えばこんなハック【GGJ】

先日のGGJ、皆様お疲れ様でした。
同じチームとなった@さんと@さん、本当にお疲れ様でした。一緒にゲームを作ることが出来て、しかも完成にまでこぎつけることが出来てほんとうによかったです。
GGJ内のサイト。
Game: Earthly Star | Global Game Jam
jsdo.it内のコード。コードの中身はこちらから。
Earthly Star 〜地上の星〜 - jsdo.it - Share JavaScript, HTML5 and CSS
プレイはこちらから。

さて。
Earthly Star」はGPSを利用した一種のARGです。
私たちが目指したのは、登録された方向に登録された距離を移動したらイベントが発生するという、相対座標を使ったシステムでした。

一方、今回NII会場主催の@が用意してくれた「ARG用アトラスX改」は事前に目的地となる場所の座標を登録しておき、プレイヤーがその座標についたらイベントが発生するという、絶対座標を使ったシステムです。

今にして思えば「ARG用アトラスX改」をいじらなくてもよかったのですが(今、その方法思い付いた orz)、その時は「こりゃ、中のイベント発生のところをいぢらないとどうにもならんなぁ」ということでハックすることにしました。

ではどのように改造したのか。
それをこれから書いてゆこうと思います。

ARG用アトラスX改」には、位置情報を取得してマップや現在座標を表示する「プロローグ2nd」という関数がCSSに存在します。該当箇所をまるっと書き出してみましょう。

プロローグ2nd = function(){
	セーブ("プロローグ");

    if(現在位置が取得できた == NO){
		クリア();
		台詞("現在地が取得できませんでした");
		台詞("再試行します");
		一時停止();
		現在位置を取得して次へ(プロローグ);
        return;
    }

    if(typeof debug == "undefined" || debug != "nogps"){
	    画像("http://maps.google.com/maps/api/staticmap?center="+現在の緯度+","+現在の経度+"&size=320x320&sensor=false&zoom=18");

	    現在地 = {経度:現在の経度, 緯度:現在の緯度};
	    舞台 = "なし";
	    遊ぶ範囲から外れている = YES;
	    最も近いエリアまでの距離 = 40000000;
	    最も近いエリアまでの方位 = "";
	    遊ぶ範囲.forEach(function(エリア){
	        現在の距離 = 距離(エリア, 現在地);
    	    if(現在の距離 < エリア.半径){
        	    遊ぶ範囲から外れている = NO;
	        }
    	    if(現在の距離 < 最も近いエリアまでの距離){
        	    最も近いエリアまでの距離 = Math.floor(現在の距離 - エリア.半径);
            	最も近いエリアまでの方位 = 方位(現在地, エリア);
	        }
    	});

	    イベント場所.forEach(function(エリア){
    	    現在の距離 = 距離(エリア, 現在地);
        	if(現在の距離 < エリア.半径){
	        	舞台 = エリア.場所;
	        }
    	});
    }else{
        舞台 = place;
        現在の緯度 = 0;
        現在の経度 = 0;
        位置情報の正確さ = 0;
	    画像("http://maps.google.com/maps/api/staticmap?center="+現在の緯度+","+現在の経度+"&size=320x320&sensor=false&zoom=18");
    }

    クリア();
	セリフ("現在地");
	セリフ("緯度:"+Math.round(現在の緯度*1000000)/1000000);
	セリフ("経度:"+Math.round(現在の経度*1000000)/1000000);
	accstr = "m";
    if(位置情報の正確さ >= 100)accstr = "m(遊ぶのには不安な精度です)";
    セリフ("誤差:±"+位置情報の正確さ+accstr);
    if(typeof debug != "undefined"){
        セリフ("舞台:"+舞台);

        for(f in フラグ)
    	    セリフ(f+":"+eval("フラグ."+f));
        console.log("プレイヤーの情報");
        str = "";
        for(f in game.memory.player)
            if(eval("typeof game.memory.player."+f) != "function")
        		str += f+":"+eval("game.memory.player."+f)+", ";
        console.log(str);
        console.log("友達の情報 x " + 友達の数);
        for(i = 0; i < 友達の数; i++){
            str = "";
	        for(f in game.memories.friends[i])
    	        if(eval("typeof game.memories.friends[i]."+f) != "function")
        			str += f+":"+eval("game.memories.friends[i]."+f)+", ";
	        console.log(str);
        }
    }

    if(eval("typeof "+フラグ.進行+"_"+舞台+" != 'undefined'")){
        eval("次へ("+フラグ.進行+"_"+舞台+");");
        return;
    }else if(eval("typeof "+フラグ.進行+" != 'undefined'")){
        eval("次へ("+フラグ.進行+");");
        return;
    }
    if(舞台 != "なし"){
	    セリフ("何かの気配は感じるのだが...");
    }else if(遊ぶ範囲から外れている==YES){
		セリフ("現在地は、このゲームを遊ぶ地域から外れています");
        セリフ(最も近いエリアまでの方位+"に "+最も近いエリアまでの距離+"m 移動すると");
		セリフ("ゲームを遊ぶ地域に戻れます");
    }else{
        セリフ("ここでは何も起こらないようだ...");
    }
	選択肢("ゲームをリセット",リセット);
	選択肢("次の場所へ",プロローグ);
};

ポイントとなるのは「イベント場所」にforEachかましてるところです。抜粋。

	    イベント場所.forEach(function(エリア){
    	    現在の距離 = 距離(エリア, 現在地);
        	if(現在の距離 < エリア.半径){
	        	舞台 = エリア.場所;
	        }
    	});

「イベント場所」とは「ARG用アトラスX改」ではなく、「一ツ橋クエスト」や「Earthly Star」など、ゲームの方で定義されたグローバル変数です。
ちょっと「一ツ橋クエスト」の中身をのぞいてみましょう。

イベント場所 = [
    {場所:"NII入口", 経度:139.757736, 緯度:35.692760, 半径:50},
    {場所:"コンビニ", 経度:139.759445, 緯度:35.692841, 半径:30},
];

と、このようなオブジェクトの配列です。
forEachは配列のすべての要素に対し、指定された関数を実行するというモノです。この部分で現在地と登録されている絶対座標とを比較してるんですね。つまり、ここでチェックする内容を変更すれば、目的を達成できるわけです。

では、まず「イベント場所」の内容を検討してみましょう。
相対座標を得るにはどうすればいいのか。経度、緯度、それぞれにどれだけ動いたかを持たせる。これを言い換えると、とある方位にxメートル移動ってことになります。
というわけでこんなんなりました。

イベント場所 = [
    {場所:"北",   経度:0, 緯度:0, 半径:20, 距離:50,  方位:"北"},
    {場所:"北西", 経度:0, 緯度:0, 半径:50, 距離:100, 方位:"北西"},
    {場所:"西",   経度:0, 緯度:0, 半径:50, 距離:150, 方位:"西"},
];

経度と緯度は不要だったのですが、知らない所で参照されても嫌なのであえて残しました。

次はforEachの中身ですね。
まずはスタート地点から見て、現在どの方位にいるのか、どれだけ移動したのかを知りたい。
移動距離については問題ないですね。2つの座標の距離を求める、「距離」なんていう身も蓋もない名前の関数が用意されています。
次は方角ですが、実はこちらも「方角」という関数が「ARG用アトラスX改」には用意されています。
そんなこんなでこんな感じ。

	    イベント場所.forEach(function(エリア){
    	    現在の距離 = 距離(フラグ.起点, 現在地);
            現在の方位 = 方位(フラグ.起点, 現在地);
            if (現在の方位 == エリア.方位) {
                if (エリア.距離 - エリア.半径 < 現在の距離 && 現在の距離 < エリア.距離 + エリア.半径) {
                    舞台 = エリア.場所;
                }
            }
    	});

「フラグ.起点」ってなんじゃー!
はい、そいつがスタート地点の座標です。
それではこのスタート地点の座標をいつ取得するのか、そこいらへんにいきましょう。

ゲームを起動すると、まず必ず位置情報を取得にいきます。つまり「プロローグ2nd」が呼ばれるわけです。だったらその時に現在座標をスタート地点として登録すればいい。なので「現在地」を取得している真上でこんなん書きました。

        if(フラグ.起点 == undefined){
            フラグ.起点 = {経度:現在の経度, 緯度:現在の緯度};
        }

登録はこれでOKです。次は初期化ですね。
「プロローグ2nd」の最後を見ると「選択肢("ゲームをリセット",リセット);」という行がありますね。ここで呼び出される「リセット」関数の中で「フラグ.起点」を初期化してやればOKですね。「リセット」は「プロローグ2nd」の次に定義されています。

リセット = function(){
    フラグ.進行 = "開始";
    フラグ.履歴 = "";
	現在位置を取得して次へ(プロローグ);
};

こいつの中に「フラグ.起点 = undefined;」という1文を足します。

大きな修正はこんなところですね。
あとは、遊ぶ範囲が限定されないので関連の処理をコメントアウトしたり、不要と思える情報を表示してるところもコメントアウト。これで出来上がりです。

と、まぁこのように。
わかってしまうと簡単なハックなんですが、やっぱり分かるまでが大変ですね。そこがハックの面白い所でもありますが。最終的はソースは下記の通りです。

そうそう。会場では@さんにお会いしました。
「おっ。知った顔がいる」wwww