テンプレートでわかるランキングゲーム

Akashic 関連ツールに組み込みのテンプレートを使って、ランキング対応ゲームの作成をご紹介します。

前提とインストール

本格的なニコ生ゲームの作成には、 Akashic Engine を使った JavaScript プログラミングが必要です。 これ以降のページでは、読者が JavaScript の基礎的な知識を持っていると仮定します。

改造編同様、前提として Akashic Engine 関連ツールが必要です。 改造編の インストール のページを参考にインストールを行ってください。

ランキング対応ゲームを生成する

akashic-cli をインストールしたことで、 CUI ツールで akashic コマンドが利用できるようになっています。 これを使って、ここで解説するランキング対応ゲームを生成します。

参考: Akashic Engine 入門の 空のゲームの作成

まずは改造編同様、CUI ツールを起動します。適当な作業場所のフォルダに cd コマンドで移ってください。仮に c:\akashic というところに移るものとします。以下のようなコマンドになります。

c:
cd \akashic

ここに、今回のゲーム用のフォルダを用意したいと思います。以下のコマンドを実行してください。

mkdir game
cd game

mkdirgame というフォルダを作り、 cd で作業場所フォルダを今作成した新しいフォルダにしたので、このフォルダで Akashic ゲームを生成したいと思います。

以下のコマンドを実行してください。

akashic init -t javascript-shin-ichiba-ranking

akashic init は、「現在の作業フォルダに Akashic ゲームのを生成する」コマンドです。-t javascript-shin-ichiba-ranking の部分は、「JavaScript 向け・ランキングゲーム用のテンプレートを使う」という意味の akashic init コマンドのオプションです。これにより、いくつかランキングゲームの作成に必要な処理が最初から組み込まれたゲームが生成されます。

ここでは使いませんが、 -t オプションを変えると、内容が空のゲームや TypeScript 向けの雛形などを生成できます。 参考: akashic-cli 利用ガイドの --type オプション

このコマンドを実行すると、対話型のインターフェースで色々聞かれると思いますが、それぞれ以下のように入力して Enter を押してください。

項目 入力値 備考
width:(320) 640 ゲーム画面の横幅
height:(320) 360 ゲーム画面の縦幅
fps:(30) (入力せずそのまま) フレームレート

ニコ生ゲームなので、ゲーム画面が (ニコニコ生放送の一般的な解像度である) 16:9 になるようにしています。この解像度比率にしておけば、映像とぴったり合ったサイズのコンテンツを作成できます。 widthheight に表示されている (320)fps(30) はデフォルト値を意味しているので、 fps はそのまま Enter を押してもらう事で 30fps になります。

この状態で一度 akashic-sandbox を実行してみましょう。

akashic-sandbox

http://localhost:3000 にアクセスすると、画面中央でキャラクタが浮かんでいるだけのゲームになっています。

生成したゲームの画面

このゲームは次の要素で構成されています:

  • 画面内をクリックすると弾を発射するキャラクタ
  • 弾を発射すると増えていく右上の得点 (SCORE)
  • 画面右上の残り時間 (TIME)

これ自体は、画像表示や音声再生など、よく使われる機能のサンプルコードとしての意味あいが強く、ゲーム性はほぼありません。 弾を発射しても狙う敵はおらず、このキャラクタも移動するわけでもなく……このままでは画面の連打回数を競うだけのゲーム (余計なキャラクタつき) ですが、この状態でも最低限ランキング対応ゲームとしての形を満たしています。

以下、このテンプレートのコードを題材に、ランキング対応ゲームの作成に必要な作業をご紹介します。

現実にオリジナルのゲームを作る場合は、このコードを削って作成するのが簡単でしょう。具体的なゲーム作成方法は Akashic Engine 入門 (別ページで開きます) の「シングルプレイのゲーム作成」パートを参照してください。以下「入門」ページを読んだ後の読者を想定します。

ランキング対応の表明

最初に必要なのは「この Akashic ゲームはランキング対応ゲームである」と表明することです。

これは game.json の environment.nicolive.supportedModes の値に ["ranking"] を設定することで行われます。テンプレートコードの game.json では、既に次のように記述されています。

{
  // ... その他の記述 ...

  "environment": {
    "nicolive": {
      "supportedModes": ["ranking"]
    }
  }
}

以前は environment.nicolive ではなく environment.niconico プロパティを利用していました。 現在 environment.niconico は非推奨になっています。 詳細は別項 ニコ生ゲーム関連の仕様 を参照してください。

制限時間の申告

表明した以上はランキング対応でなければなりません。単なるシングルプレイの Akashic ゲームと、ランキング対応ゲームの違いは、大きく次の二つです。

  • ゲームが一定時間で自動的・強制的に終了されること
  • スコアが自動的に集計され、ランキング形式で発表されること

そのためまず決めることは 制限時間 、「このゲームは何秒のゲームなのか」です。 game.json に environment.nicolive.preferredSessionParameters.totalTimeLimit の項目を記述すると、希望の制限時間を申告することができます。

次の例では、希望制限時間を 30 秒としています。

{
  // ... その他の記述 ...

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

totalTimeLimit には 20 以上 200 以下の整数を指定できます。

特に指定しなかった場合は、適当なデフォルト値が使われます (現在は 80 秒程度になります)。テンプレートでは特に指定していないので、必要なら上の例を参考に記述を追加してください。

後方互換性のため、 environment.nicolive がなく environment.niconico がある場合、そちらが参照されます。

制限時間の処理

制限時間は、上記のように申告さえしておけば (あるいはしなくても) 他の対応は不要です。 ゲームは制限時間に応じて自動的に・強制的に終了されます

しかし現実には、「残り時間をゲーム画面に表示したい」といったことはよくあるでしょう。 あるいは残り時間が短くなったら結果発表演出を行いたい、ということも考えられます。

テンプレートのコードでも残り時間の表示を行なっています。 テンプレートの main.js では、 main() の引数 param から制限時間を取得しています。次の箇所がそれです。

let time = 60; // 制限時間
if (param.sessionParameter.totalTimeLimit) {
  time = param.sessionParameter.totalTimeLimit;
}

この値を使って、次のように scene.update で 1 フレームごとにカウントダウンしていき、ラベル timeLabel で画面に表示しています。

const updateHandler = () => {
  if (time <= 0) {
    // (中略)
    scene.update.remove(updateHandler); // このハンドラを削除=カウントダウンを停止
  }
  // カウントダウン処理
  time -= 1 / g.game.fps;
  timeLabel.text = "TIME: " + Math.ceil(time);
  timeLabel.invalidate();
};
scene.update.add(updateHandler);

制限時間は (game.json で希望値を申告していても) 必ず param.sessionParameter から取得してください。

というのも、第一に game.json での記述は、あくまで "希望値の申告" であるためです。 ゲーム実行時の実際の制限時間が保証されるわけではありません。

第二に、 ニコ生ゲームでは でも述べた通り、ランキング対応ゲームは ゲームアツマールでも公開できます (デフォルトでされます)。ゲームアツマール上では単にシングルプレイのゲームという扱いになるため、ニコ生による自動終了やスコア集計はなく、 制限時間も与えられません 。 そのため上のコードのように、制限時間が与えられなかった時は 60 秒として動作する、といった処理が必要です。

制限時間は「ゲーム起動から終了までの全ての時間」であることに注意してください。 これには「ゲームリソースのダウンロード時間」なども含まれます。 そのため、制限時間のギリギリで行う演出は、回線状況によって途切れてしまう可能性があります。 終了前に結果発表演出などを挟む場合は、念のため 制限時間の 10 秒程度前までに演出を完了させることを推奨します

スコアの処理

ランキング対応に必要なもう一つの作業は、スコアを設定することです。

これは g.game.vars.gameState.score に整数を代入することで行われます。

g.game.vars は、「ゲーム開発者が自由に使っていい領域」として Akashic Engine が提供している変数で、初期値は空のオブジェクトです。ランキングゲームではここに gameState という変数が入っていることを期待します。

そのためテンプレートの main.js では、次のようなコードで最初にスコアを 0 点にセットしています。

g.game.vars.gameState = { score: 0 };

テンプレートのゲームは、単に画面がクリックされたら 1 点加算するというものでした。 これは次の記述で実現されています。

scene.pointDownCapture.add(() => {
  // 制限時間以内であればタッチ1回ごとにSCOREに+1します
  if (time > 0) {
    g.game.vars.gameState.score++;
    scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
    scoreLabel.invalidate();
  }

  // ... その他、弾の発射や効果音再生処理
});

++score を 1 加算し、それをラベル scoreLabel で画面に表示しています。 実際のゲームでは「アイテムを取得した時」「敵を倒した時」「一定時間生き残った時」などお好みの条件でスコアを追加してください。

スコアは 0 以上の整数である必要があります。上限は今のところありませんが、現実的に表示が崩れてしまうので、10 万未満 (5 桁以内) を推奨します。

スコアは、ゲーム中も常に g.game.vars.gameState.score に代入してください。

つまり「スコアを中間変数に保持しておいて、最後に g.game.vars.gameState.score に代入する」というような処理にはしないでください。 というのも、通信遅延などで起動が遅れた場合などでは、最悪 score の代入が間に合わずにゲームが終了されてしまう可能性があるためです。 途中までプレイできていたのに 0 点扱いになってしまうと、プレイヤーからは不具合に見える動作になってしまいます。

ゲームアツマール向けの処理

シングルプレイの Akashic ゲームで、制限時間の処理とスコアの設定ができれば、ランキング対応は完了です。

ただし、ランキング対応ゲームは ゲームアツマール上でも公開できます (デフォルトでされます)。 ゲームアツマール上では単にシングルプレイのゲームとして扱われるため、ニコ生による自動終了や集計はありません。 そのため現実的には、ゲームアツマール上固有の処理を実現したい場合が多くあります。

  • ゲーム終了後、リスタートボタンを表示して最初に戻れるようにする
  • ゲーム終了時、ゲームアツマールのスコアボードを表示する

このような時に、テンプレートの param.isAtsumaru が利用できます。 この値は ゲームアツマール上でゲームが実行されている時にのみ true になります。

テンプレートでは、この変数を使って「制限時間経過後に ゲームアツマールのスコアボードを表示する」ようになっています。 これは次のコードで実現されています。

const updateHandler = () => {
  if (time <= 0) {
    // ゲームアツマール環境であればランキングを表示します
    if (param.isAtsumaru) {
      const boardId_1 = 1;
      window.RPGAtsumaru.experimental.scoreboards
        .setRecord(boardId_1, g.game.vars.gameState.score)
        .then(() => {
          window.RPGAtsumaru.experimental.scoreboards.display(boardId_1);
        });
    }
    // ... 中略 ...
  }
  // ... 中略 ...
};
scene.update.add(updateHandler);

ゲームアツマール上でのみ使える API (= window.RPGAtsumaru) については、ゲームアツマールの API リファレンス を参照してください。

注意点: 乱数について

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

このため g.game.random で生成される乱数が、プレイヤー間で一致しません 。 言い換えると、 g.game.randomg.game.localRandom がどちらも各プレイヤー固有の乱数を生成してしまいます。

これはマルチプレイゲームと異なる点に注意してください。マルチプレイの場合、エンジンが乱数シードを揃えるため、 g.game.random の結果はプレイヤー間で一致します。

そこでランキング対応ゲームテンプレートは、プレイヤー間で共通の乱数生成器 (共通乱数生成器) を独自に提供しています。

テンプレートの main.js 内、 main() の引数が param の時、共通乱数生成器は param.random で取得できます。

function main(param) {
  const random = param.random; // 共通乱数生成器
  const r = random.generate(); // プレイヤー間で共通の乱数値を生成
}

ランダム性を持たせつつ、全プレイヤーが等しい条件で競うようなゲームを作成する場合は、この共通乱数生成器を利用してください。 もちろんトランプの手札や麻雀の配牌のように、プレイヤーごとにランダムに異なる状態を作りたいゲームでは、単に g.game.random (か g.game.localRandom) を利用することになるでしょう。

投稿

ゲームが作成できたら、ゲームアツマールを通して投稿できます。 ニコ生ゲームを投稿しよう を参照して、投稿してみてください。