ranking モードのゲーム作成

game.json で "supportedModes""ranking" を指定したゲーム (ランキング対応ゲーム) は、ランキングモードで起動されるよう登録されます。 ランキングモードでは、次のような動作が行われます。

  1. 配信者・視聴者全員の手元でゲーム(シングルプレイ)が起動される
  2. 一定時間でゲームが終了する
  3. 各プレイヤーのスコアが自動的に集計され、上位者がランキング形式で発表される

ゲーム自身はランキング発表機能などを持たなくてよい点に注意してください。 例えば、以下の『泥棒バスター』は、ニコニコ生放送上でランキングモード対応のゲームとして公開されていますが、単体ではランキングの機能は持っていません。 (ランキングモード専用なので、繰り返し遊ぶ機能すら持っていません)

ゲーム開発者はこれと同様、基本的にはシングルプレイのゲームを作成し、後述のスコアの対応を行うだけで、ランキング形式で遊べるゲームにすることができます。

その性質上、 "ranking" を指定するゲームは、次の条件を満たす必要があります。

  • 一人プレイである
  • タイムアタック (一定時間内のスコアを競うゲーム) である
  • 0 点以上のスコアを特定の変数に代入するようになっている

特に「一定時間で終了される」ため、例えば「ゲームオーバーになるまでプレイし続け、生き残った時間を競う」ようなコンテンツを作ることはできないことに注意してください。 (そういったコンテンツはみんなで遊ぶ "multi_admission" モードで作成できます)

# スコア

スコア は、ゲームプレイに応じて定まる 0 以上の整数です。

ランキング対応ゲームでは、スコアの値を g.game.vars.gameState.score に代入する必要があります。 ゲームプレイ中にスコアが更新される度に、この score に値を代入してください。

以下のコード例は、画面をクリックした回数がそのままスコアになるゲームの例です。

// score を初期化
g.game.vars.gameState = {};
g.game.vars.gameState.score = 0;

var scene = new g.Scene({...});
scene.loaded.add(function (){
    // シーン中でどこかが押下される度に `score` の値を加算
    scene.pointDownCapture.add(function () {
        g.game.vars.gameState.score++;
    });
});

スコアの値に上限は今のところありませんが、 10 万未満 (5 桁以内) にすることを推奨します 。あまり大きな数字を指定すると、ランキング結果の画面でのスコア表示がおかしくなる可能性があります。

なお g.game.vars は、「ゲーム開発者が自由に使える領域」として Akashic Engine が提供する変数です。 初期値は {} で、ニコニコ生放送とは関係なく存在します。 上のコード例で、最初に g.game.vars.gameState = {} を実行しているのはそのためです。(gameState は初期状態では存在していません)

ここまでの内容で、ランキングモード対応のゲームを作成することができます。 以下はより発展的な内容です。

# セッションパラメータとランキング対応ゲームテンプレート

ニコニコ生放送は、起動したゲームコンテンツに対していくつかの補助情報を提供します。 ランキングモードの場合、これには次のような内容が含まれます。

  • 制限時間
  • 共通乱数シード

この補助情報を「セッションパラメータ」と呼びます。 ゲーム開発者はこれを参照して、より発展的な演出を行うことができます。

セッションパラメータ は、ゲーム開始直後に受信できるメッセージイベント (g.MessageEvent) です。 が、このイベントは「ニコニコ生放送以外の実行環境では受信できない」「受信タイミングがシビア」など、扱いに煩雑な実装を必要とします。

そこで akashic init コマンドは、 ランキング対応ゲームテンプレート を提供しています。 このテンプレートにはセッションパラメータの受信処理が最初から含まれているため、 ゲーム開発者は (main.js の関数の引数として) すぐにセッションパラメータを受け取ることができます。 セッションパラメータを利用する場合、このテンプレートでゲームを作成することを推奨します

akashic init コマンドでのコンテンツ生成時に -t オプションで以下のように指定することで、 ランキング対応ゲームテンプレートを使ってコンテンツ開発をスタートできます。

$ akashic init -t javascript-shin-ichiba-ranking    # サンプルコンテンツを生成する場合
$ akashic init -t typescript-shin-ichiba-ranking    # TypeScript版のサンプルコンテンツを生成する場合

以下の文書は、これらのテンプレートを利用している前提で記述されています。

なおこれらのテンプレートは、最小限ランキングモードに対応するゲーム内容が main.js に記述されています。 これは適宜削除・編集してご利用ください。

# 制限時間の参照

上述のとおり、ランキングモードはタイムアタックであり、ゲームは一定時間で自動的・強制的に終了されます。

ただしゲームコンテンツは、与えられた制限時間の値を参照して、任意の演出を行うことができます。 例えば終了の 10 秒前にゲームを切り上げ、「結果発表」として自分のスコアを画面に出す、といった演出がそれにあたります。 (これは必須の処理ではありません。単に時間いっぱいゲームプレイを行い、終了時点でのスコアを競うことももちろんできます。)

現在のニコニコ生放送では、制限時間はデフォルトで 80 秒前後の値が与えられます。 この値をゲームコンテンツから指定する方法は、 制限時間の表明 を参照してください。

制限時間 は、セッションパラメータのプロパティ totalTimeLimit で与えられます。 与えられる場合、この値は整数で、制限時間の秒数を表します。

「ランキング対応ゲームテンプレート」を利用している場合、 main() の引数 param から param.sessionParameter.totalTimeLimit で参照できます。

次のコードは、制限時間まで JavaScript 上でカウントダウンしていく例です。

function main(param) {
    // セッションパラメータを main() の引数から取得
    var sessionParameter = param.sessionParameter;

    // 制限時間を算出 (セッションパラメータで指定されなかった場合は 60 秒として扱う)
    var timeLimit = 60;
    if (sessionParameter && sessionParameter.totalTimeLimit) {
        timeLimit = sessionParameter.totalTimeLimit;
    }

    // 制限時間をフレーム換算した「残り時間(フレーム)」
    var remainFrame = timeLimit * g.game.fps;

    var scene = new g.Scene({...});
    scene.loaded.add(function() {
        scene.update.add(function() {
            // フレームごとに「残り時間(フレーム)」を減算
            --remainFrame;

            if (remainFrame <= 10 * g.game.fps) {
                // 残り時間が 10 秒を切った時の処理。
                // ここでは、このイベントハンドラの削除のみ行っています。
                return true; // trueを返すことでハンドラの登録が解除されます
            }

        });
    });
}

制限時間の値は、あくまでも目安であることに注意してください 。 ゲーム起動後、厳密にこの時間で終了されることを保証するものではありません。 これはゲームリソースのダウンロード時間のばらつきや、スコア送信時の通信遅れを救済するためのサーバ側の多少のマージンなど、いくつかの要因でずれが生じるためです。

たとえば制限時間から逆算して「クリア画面」を作るような場合、念のため、制限時間の 10 秒ほど前にはゲーム上の演出が完了するように作る ことを推奨します。

# 制限時間の表明

制限時間 totalTimeLimit の値は、ゲーム開発者の希望する値を game.json で表明することができます。 game.json で environment.nicolive.preferredSessionParameters.totalTimeLimit を指定することで、 ニコ生ゲームはその値を参照して制限時間を決定します。

たとえば、デフォルトの 80 秒前後ではなく 30 秒で終わるゲームが作成したい場合は、 game.json に次のように記述します。

{
  "environment": {
    "nicolive": {
      "supportedModes": ["ranking"],
      "preferredSessionParameters": {
        "totalTimeLimit": 30
      }
    }
  }
}

この totalTimeLimit の値は 20 以上 200 以下でなければなりません。

この申告によって、ゲーム実行時に渡される実際の totalTimeLimit の値が保証されるわけではないことに注意してください。 スクリプト上ではあくまでも、送られてきたセッションパラメータの値に従ってゲームを進行すべきです。

preferredSessionParameters でサポートされているフィールドは、現在 totalTimeLimit のみです。

# 共通乱数生成器

前述のとおりランキング対応ゲームは、配信者・各視聴者の手元で個別に(シングルプレイの)ゲームが実行されます。

マルチプレイと異なり、乱数生成器 (g.game.random) の生成結果は各プレイヤーで異なります。 そのため、各プレイヤーで異なる敵が出現するなどの動作が自動的に実現されます。 しかしコンテンツによっては、乱数を統一したいことも考えられます。 たとえば「『遊ぶたびにランダムにクイズが出題され、正答数を競うゲーム』を作りたいが、一度の出題内容は視聴者も配信者も同じにしたい」というような場合が考えられます。

ランキング対応ゲームテンプレートは、この用途のため 共通乱数生成器 を提供しています。 これはランキングを競う配信者・各視聴者で共通の乱数シードで作られた乱数生成器です。

main() の引数 param がある時、共通乱数生成器は param.random で取得できます。 以下に、param.random の取得と利用の例を記載します。

function main(param) {
    var random = param.random; // 乱数生成器
    var scene = new g.Scene({...});
    scene.loaded.add(function() {
        var filledRects = [];
        for (var i = 0; i < 10; i++) {
            filledRects[i] = new g.FilledRect({
                scene: scene,
                cssColor: random.get(0, 1) === 0 ? "red" : "blue",
                width: 32,
                height: 32,
                y: i * 32
            });
            scene.append(filledRects[i]);
        }
    });
}

上記は複数の矩形を配置するコード例です。 param.random.get()により、全視聴者の矩形の配置は同一になるので、同じ条件下での競争が可能となります。

なおセッションパラメータには、これと全く同じ用途の共通乱数シード randomSeed が含まれています。 共通乱数生成器は、ランキング対応ゲームテンプレートがこのシードから暗黙に作成・提供する乱数生成器です。 したがってランキング対応ゲームテンプレートを使う場合、このシードを参照する必要はありません。

# 動作確認

akashic-sandbox (v0.13.40 以降)を用いることで、スコアが正しく設定されているかなどを確認することができます。

ランキング対応ゲームのディレクトリ内で akashic-sandbox を起動します。

akashic-sandbox .

以下のスクリーンショットでは、ランキング対応ゲームのサンプル block-shooter を起動しています。 このコンテンツは、 こちら から zip ファイルをダウンロードすることができます。

任意の Web ブラウザから http://localhost:3000/game にアクセスすると、以下のスクリーンショットのようにゲーム画面が表示されます

akashic-sandbox起動時

画面の右上の歯車アイコンをクリックするとディベロッパーツールが表示されます。 Niconico タブをクリックすると、下記のような画面(v0.13.54 時点のもの)が表示されます。

niconicoタブ

「ランキングモードで参照される値」でゲーム中の g.game.vars.gameState の次のプロパティを確認できます。

  • スコア (score)
  • プレイ閾値 (playThreshold)
  • クリア閾値 (clearThreshold)

「セッションパラメータを送る」にチェックを入れることによって、下記の画面から mode や totalTimeLimit 等のゲームに送られるセッションパラメータの値を設定することができます。(実際にそれらの値を送るには、ページをリロードする必要があります。)

セッションパラメータ

mode で「ランキング(ranking)」を選択することで、akashic-sandbox は次のように動作します。

  • totalTimeLimit (制限時間) がゲーム開始時に送られます。また、この値を指定することができます。
    • ただしこの値は、あくまで akashic-sandbox 上でのデバッグのためのもので、ニコニコ生放送で実際に起動された時の値は保証されません。
  • 制限時間までの残り時間がカウントされます。
  • 「制限時間経過後にゲームを停止」にチェックを入れていれば、残り時間が 0 になった時にゲームの実行が停止されます。

なおインストールされている akashic-sandbox のバージョンは次のコマンドで確認できます。

akashic-sandbox -V

バージョンが 0.13.40 未満の場合は、Akashic Engine のインストールを参照してインストールしなおしてください。