Google Chromeのイースターエッグを自動でプレイする
萬成 亮太
はじめまして。
2015年新卒の萬成(まんなり)です。数少ないフロントエンドエンジニアとして頑張ってます。
皆さんは Google Chrome のイースターエッグをご存知ですか? 心当たりの無い方はネット回線をOFFにして Google Chrome で適当なページを開き、スペースキーを押してみてください。ティラノサウルスのゲームが始まりましたね? これが Google Chrome のイースターエッグです。僕もネット回線が弱い場所や会議中によくプレイします。ある日、いつものようにこのゲームをプレイしていた時、「自動でプレイしたら面白いんじゃね?」と思い、そんなブックマークレットを作ることにしました。
本稿では、Google Chrome のイースターエッグ(以下、T-Rex Runner)を改造する方法と、自動プレイのブックマークレットについて説明します。ちなみにブックマークレットを実行すると以下のように自動でティラノサウルスがジャンプします。
(ブックマークレットは Google Chrome for Mac バージョン44 で動作確認しています。)
Chromeのティラノサウルスを自動でプレイするブックマークレット書いた pic.twitter.com/qfa3I6jQHM
— まんせー (@mannari) 2015, 8月 2
ゲームの説明
ブックマークレットの実装方法を説明する前に、T-Rex Runner のルールについて説明します。
T-Rex Runner は、ティラノサウルスを操作し、前から迫ってくる障害物をジャンプして避け続けるシンプルなゲームです。障害物にぶつかるとゲームオーバーです。
障害物はサボテンとプテラノドンの2種類です。サボテンはいくつか大きさがあります。プテラノドンは飛ぶ高さが3段階あります。最も高いものはジャンプしなくても避けることが出来ます。プテラノドンはこちらに向かって飛んで来るので、サボテンより少し速いです。障害物が迫ってくるスピードはゲームが進むに連れて上がっていきます。一定の速度以上には上がらず、それ以降は単調です。
サボテン | プテラノドン(上段・中段・下段) |
ティラノサウルスは常に走り続けています。スペースキーを押すことで障害物を避けます。基本はジャンプだけですが、プロのプレイヤーは下矢印キーも使います。ジャンプ中に下矢印キーを押すと高速で落下する他、プテラノドンの中段を下からくぐり抜けることが出来ます。
走る | ジャンプ (スペースキー) |
高速落下・くぐり抜け (下矢印キー) |
プログラムの構造と上書き
ここから T-Rex Runner を改造する方法を紹介します。早速ネット回線を切り、ゲーム画面で開発者ツールを開いてみましょう。
T-Rex Runner のプログラムはすべて JavaScript で実装されています。 Runner
というクラスが本体で、これがゲーム全体を管理しています。スコア管理やゲーム画面の描画などは、Runner
が持っているDistanceMeter
やHorizon
などが担当しています。ティラノサウルスや障害物はそれぞれTrex
、Obstacle
クラスによって表現されます。クラス図にしてみるとおおよそ以下の様な構造です。
T-Rex Runner のおおまかなクラス構造 |
さて、このプログラムには2つの素晴らしい点があります。
1つはメソッドやプロパティの多くが public な点です。このプログラムは JavaScript の prototype を多用しているため、これらを上書きしたり呼び出したりすることで簡単に挙動を変えることが出来ます。
そしてもう1つが、プログラムのメインであるRunner
クラスの以下の2行。
function Runner(outerContainerId, opt_config) {
// :
// Runner をインスタンス化したとき、インスタンス自体を Runner クラスの
// public static なメンバーに代入
Runner.instance_ = this;
// :
}
// Runner クラスをグローバルからアクセス可能
window['Runner'] = Runner;
この素晴らしい2行により、グローバルからゲーム本体であるRunner
のインスタンスが取得できるようになっています。T-Rex Runner の作者の遊び心を感じますね。
試しにメソッドを上書きして動作を変えてみましょう。開発者ツールのコンソールで以下のコードを実行してからゲームをプレイしてみてください。
Runner.instance_.gameOver = function(){};
ティラノサウルスが無敵状態になったかと思います。Runner.gameOver()
メソッドは障害物に接触した時に呼ばれ、ゲームを終了してゲームオーバーを表示する処理が記述されています。上記のコードによってその処理を無効化することで、障害物にあたってもゲームが終わらなくなります。
他にも以下を実行することで、月にいるかのようなふんわり感を出したり出来ます。
Runner.instance_.tRex.option.GRAVITY /= 6;
このようにクラスのメソッドやプロパティを外部から上書きしたり呼び出したりすることで、ゲームの動作を変えることが出来ます。
自動でジャンプするブックマークレットを作る
本題の自動ジャンプの実装について説明します。
自動ジャンプの処理は以下の様な感じになるかと思います。
① 一番近い障害物の位置を頻繁に確認する
② 障害物の接近を判定し、ある程度近かったらジャンプする
下線を引いた部分は T-Rex Runner にアクセスして情報を読み取ったりメソッドを呼び出したりして実現します。それぞれについて、実際に外部からどのようにアクセスするか説明します。
一番近い障害物の位置
障害物の生成や破棄はRunner.instance_.horizon
が行います。生成された障害物オブジェクトは、Runner.instance_.horizon.obstacles
配列に代入されています。この配列の中にある障害物オブジェクトの座標を見ることで、次にジャンプすべき障害物がどのあたりにあるのかが分かります。ティラノサウルスから一番近い障害物は以下のようにして取得出来ます。
// 一番近い障害物を取得
function getNearestObstacle() {
var tRexX = Runner.instance_.tRex.xPos;
var obstacles = Runner.instance_.horizon.obstacles;
var nearestX = Runner.defaultDimensions.WIDTH;
var result = null;
for (var i = 0, ii = obstacles.length; i < ii; i++) {
// 障害物の位置
var x = obstacles[i].xPos;
var y = obstacles[i].yPos;
// 上段プテラノドン(yPos==50)は無視
if (y != 50 && tRexX < x && x < nearestX) {
nearestX = x;
result = obstacles[i];
}
}
return result;
}
頻繁に確認
障害物をジャンプして避けるには、障害物の位置を頻繁に確認する必要があります。
パッと思いつく方法はsetInterval
を使う方法です。
setInterval(function() {
// ジャンプする必要があるか確認 && ジャンプする
}, 1);
ですが、この方法は普通すぎて面白くないので、T-Rex Runner の更新処理をフックしてやります。
T-Rex Runner の中にある多くのクラスには update
というメソッドがあります。このメソッドはゲームをリアルタイムに更新する処理を担います。例えば Trex.update
であればジャンプ中のY座標やまばたきの更新、DistanceMeter.update
であれば進んだ距離の再計算、といった具合です。そしてこれらのメソッドは、元をたどればすべてRunner.update
から呼び出されています。Runner.update
はゲームが動いている間、以下のようにメソッドの末尾で再帰的に呼び出すようになっています。
Runner.prototype = {
update: function() {
// :
if (!this.crashed) {
this.tRex.update(deltaTime);
this.raq();
}
},
raq: function() {
if (!this.drawPending) {
this.drawPending = true;
// Runner.update を再帰的に呼び出し
this.raqId = requestAnimationFrame(this.update.bind(this));
}
},
}
この Runner.update
をフックすることで、頻繁に確認する処理を実現できます。ゲームを開始する前に以下を実行することで、元のRunner.update
の動作を守りつつ、同じタイミングで任意の処理を走らせることが出来ます。
var _update = Runner.instance_.update;
// requestAnimationFrame の呼び出し先がこのFunctionになる
Runner.instance_.update = function() {
// オリジナルの Runner.update を発火
_update.call(this);
// ここで任意の処理
};
接近の判定
ここがブックマークレットの肝となる部分です。前述の「頻繁に確認」の中で、ジャンプをするかどうかの判定を行います。判定方法はいろいろあると思いますが、今回は泥臭い二次方程式で解きました。
障害物が右側から近づき、ティラノサウルスがジャンプした時に描く放物線の中に障害物が完全に入った時にジャンプするように閾値を計算します。
障害物が閾値より左側に来た時ジャンプ | 閾値は障害物をできるだけ早くジャンプするように、障害物の形状を使って計算 (※実際にはティラノは真上にジャンプします) |
この方法を採用する前は単純な式に幾つかのパラメータをいろいろ当てはめてベストスコアを模索する方法を取っていましたが、時間がかかる上に成績が良くなかったので上記の方法に変えました。パラメータ模索のため僕のPCは2営業日の間オフラインでした。
ジャンプする
ティラノサウルスはスペースキーを押すとジャンプします。ということは、T-Rex Runner の中にキーダウン時に呼ばれるメソッドがあるはずです。
Runner.prototype = {
onKeyDown: function(e) {
// :
if (e.target != this.detailsButton) {
if (!this.crashed && (Runner.keycodes.JUMP[e.keyCode] || e.type == Runner.events.TOUCHSTART)) {
// :
if (!this.tRex.jumping && !this.tRex.ducking) {
this.playSound(this.soundFx.BUTTON_PRESS);
// ジャンプ
this.tRex.startJump(this.currentSpeed);
}
}
if (this.crashed && e.type == Runner.events.TOUCHSTART && e.currentTarget == this.containerEl) {
this.restart();
}
}
// :
}
};
ありました。このonKeyDown
を直接呼ぶことで、ジャンプを呼び出させることが出来ます。そのためには、上記コードの12行目までのif文を成立させる必要があります。以下のようにスペースキーが押された時のキーボードイベントと同様のオブジェクトを第一引数に渡すことで、ジャンプ処理を走らせます。
// ジャンプ
function jump() {
Runner.instance_.onKeyDown({
keyCode: 38
});
}
自動でプレイするブックマークレット
ブックマークレットの説明は以上です。これまでの説明を元にブックマークレットを作りました。下の画像をブラウザのブックマークバーにドラッグアンドドロップして、T-Rex Runner の画面でブックマークを呼び出してみてください。自動でティラノサウルスがジャンプするかと思います。
まとめ
Google Chrome のティラノサウルスのゲームを自動でプレイするブックマークレットを作成しました。ネット回線が弱い場所や会議中にぜひ楽しんでみてください。
ちなみに僕の環境では18000ポイントでゲームオーバーになりました。サボテンが立て続けに来るとジャンプが間に合わなくなるようです。この記事を読んだ誰かがもっと賢いブックマークレットを作ってくれることに期待しています。