こんばんは。アメーバ事業本部ピグディビジョン所属の久保です。
今回は6月5日にリリースいたしましたスマートフォンで遊べる「スマートフォン版ピグつりゲーム」(http://fishing.pigg.ameba.jp)の開発の一端をご紹介させていただきます。

スマートフォン版ピグつりゲームとは


スマートフォン版ピグつりゲームとは、PC版アメーバピグの中で遊べるつりゲームをスマートフォンでも気軽に楽しんでもらえるように制作したブラウザゲームです。PC版はFLASHで動作しますが、スマートフォン版ではiPhoneの対応を考慮に入れてHTML/CSS/JavaScriptにて動作します。対応環境は iOS4以上、Android 2.2/2.3 となります。

構成はトップページ/手帳ページ/ショップページ といったメニュー画面と、釣りを楽しむゲーム画面に分けることができます。メニュー画面は運用効率を踏まえ JavaScript MVC を実現する「Backbone.js」※1を使用し、ゲーム画面はレスポンスと制作効率を考え自作フレームワークを使用しています。

メニュー画面について

スマートフォンの画面は小さく表示できる情報に限りがあるため、自ずとページ数が増えていきます。その結果、データ転送量が貧弱なこともあり、読み込む回数と時間の増加に比例してUXを損ねてしまいます。メニュー画面は回遊性が高くこの問題が顕著に発生します。そこで、1つのHTMLファイルに他のページのHTMLも記述し、ユーザーのタップアクションによってHTMLをJavaScriptで切り替えて擬似的にページ遷移しているように見せる工夫を行っています。このような対応をBackbone.jsを使うと手軽に実装することができます。もちろんデメリットも存在し、1ページで複数ページ分のデータを保持するので端末のメモリ使用量や読み込みが局所的に発生しないように気を配る必要があります。

ゲーム画面について

ゲーム画面は装備を選択するシーン/さかなをつるゲームシーン/釣り上げるシーンの3つで構成されています。ゲーム画面の開発で常に意識したのは以下の2点です。
  1. 開発効率をよくする
  2. ゲームのレスポンス
開発当初は、既成のゲームライブラリを検討したのですが、Android OSでまともに動くものがなく採用を断念いたしました。そこで、FLASHらしく作成できるようHTMLやCSSの記述をせずJavaScriptだけで完結するライブラリを自前で作成することにしました。以下がそのサンプルコードです。チェーンメソッドでも記述できる点はFLASHというよりjQueryのようでもありますが、HTML/CSS を書く必要がなく処理をまとめて書くことができます。HTML/CSS/JavaScript それぞれに分けて記述するのは処理が分散し管理しにくいので、数行で済むこの方法は確認する際にとても見通しがよくなります。

// こんな感じでチェーンメソッドで記述できます。
// エレメントを生成

var element = new ww.core.Sprite({
    x:0,
    y:10,
    width:100,
    height:100,
    fill:{color:’rgba(255,0,0,1.0)’}
})
// ステージに配置
.addChildTo(stage)
// 1000msec かけて 右へ 100px 移動
.animate({x:100}, 1000);

ゲームのレスポンスについてですが、一番課題になったのがアニメーション処理。はじめはCSSのtransitionおよびkeyframeアニメーションを採用する予定でしたが、ユーザーのタップ動作に合わせ停止させるとすぐ止まったり、止まらなかったりといったムラのあるラグが発生しました。この現象はアニメーション中繰り返される描画の間隔が広いためタップした直後に停止命令を下しても、反映されるのは次の描画になることが原因でした。運がよければすぐ描画されますが、次の描画まで長いとラグが発生します。iOS/Androidともに古いOSであればあるぼどこの現象は顕著になります。これではタイミングゲームでは致命的な問題となるので採用を見送るほかありませんでした。

次に検討したのが、一定のタイミングでインラインCSSをJavaScriptで更新し続ける方法です。この手法だと、iPhoneでは制作者が意図した通りの挙動をしてくれることが多いのですが、Androidの場合、カクカクとした挙動でゲームにならない状態でした。しかしCSSアニメーションよりはこちらで挙動を管理できる可能性があったので、さらに発展させ各エレメントごとに個別に行っていた更新処理を1つのタイムラインで対応するように一元化しました。下記のような setTimeout を繰り返し実行する関数を用意し、targetArr に再描画の必要があるエレメントをアニメーションさせる間だけ格納し、アニメーションが終了したタイミングで破棄します。これにより一括して描画されるようになり無駄な描画を減らすことができるようになりました。

※実際のコードより簡略化しています。
// 更新する対象と更新処理の関数を管理する配列。// ここに格納されている間は描画を実行します。
var targetArr = [];
// タイムライン
(function(){
    setTimeout(arguments.callee, Math.floor(1000/60));
    for(var i=0, len=targetArr.length; i<len; i++){
        var t = targetArr[i]; // 更新する対象と処理が格納されている配列
        // 以下 対象の更新と描画処理
        t.target.update(); // 更新
        t.target.render(); // 描画
    }
})();

タイムラインで管理することでレスポンスはだいぶ解消されるようになりましたが、Android端末では描画処理のコストが高いため挙動が重くタイミングゲームとして成り立ちませんでした。この問題にはタイムラインに可変フレームレートを採用することで解決を計りました。たとえば固定フレームレートの場合、15fpsの場合1秒間に15回処理を実行しますが、低スペックな端末だときっちりと1/15で処理されることはなく数ミリから数百ミリ秒遅れることがあります。結果1秒間で納まりきらず1秒を超えたタイミングで処理が完了します。つまりどんなに時間がかかろうとも15回きっちり描画します。可変フレームレートであれば、再描画のタイミングでどの程度遅れて実行されるのかを判断しズレを調整しながら描画するので、もし指定した完了時間を超えるようであれば描画回数を減らすことが可能になります。これをつりゲームのさかなの移動距離で表すと下図のようになります。1秒間に15回の描画を行って、右から左に泳ぐアニメーションを作成した場合、スペックが高ければ高い環境ほど15回に近い回数で描画を行います。

JavaScriptで可変フレームレートを実現する場合は、1/15回ごとにDate.now()を実行して前回から本当に1/15秒 かかったかを確認します。もし遅れている場合は、その差分を描画に反映させるようにしました。これにより高スペック端末と低スペック端末でそれぞれ適した描画回数になり、レスポンスを上げることに成功しました。下図の右の図はそのことについて記したものです。1回のループにかかる時間が、Aの移動距離を想定した場合で、Bに相当する距離を移動できるだけの時間がかかってしまった場合、Cの分を補正して帳尻を合わせています。これを繰り返していくと処理が遅い場合、描画回数が減ることになります。
※実際のコードより簡略化しています。
//初期値
var initialVals = {x:0, y:0}; 
//変更後の値
var property = {x:100, y:100}; 
// 時間からアニメーションの何%完了したかを取得する
// 1で100%です。計算結果が1を超えた場合は100%とします。

var progress = Math.min(1, (Date.now() - アニメーションの開始時間) ) / アニメーションの再生時間);

// すべての変更値に補正値を加味して計算を行う
for(var key in property){
    initialVal = initialVals[key];
    delta = property[key]-initialVal;
    update[key] = linear(progress, initialVal, delta, 1);
}

function linear(t, b, c, d){
    return c / d * t + b;
}

以上で、ブラウザゲーム開発のお話は終わりです。猛烈な端折りかたで理解しにくい記事となってしまいましたが、つりゲームの方はわかりやすく作られていますので、ぜひ遊んでいただければと思います。

※1 Backbone.js (http://backbonejs.org/)