ユーザ名を使う

マルチプレイゲームでは、ゲーム中にプレイヤーの名前を表示したいことがあります。 ニコ生ゲームでは、各プレイヤーの「ニコニコ生放送のユーザ名」を取得・利用することができます。

対応バージョンは Akashic Engine v3 以降です。

ユーザ名の利用許諾

ニコ生ゲームでのユーザ名取得は、技術的には「関数を一つ呼び出してその完了を待つ」だけで実現できます。

ただし自作ニコ生ゲームにおいては、ユーザの許可なくユーザ名を利用することはできません。 コンテンツからユーザ名を取得しようとすると、ゲーム画面に許諾を求めるダイアログが表示されます。(下図参照)

ユーザ名の利用許諾を求めるダイアログ

ゲームからは、ここで「ユーザー名」ボタンを押したユーザの名前だけが取得できます。 許諾されなかった場合、ランダムに生成された名前 (e.g. "ゲスト383") が通知されます。

このような制約を設けるのは、ニコニコ生放送が歴史的に「名前を出さない」コミュニケーションを許容してきたという文化的背景があるためです (184 コメント など)。 公式ニコ生ゲームでは、何がしかゲームプレイに参加しなければ名前が出ることはないようになっているため、このような許諾画面を表示していません。

ライブラリの導入

ユーザ名を取得するには、拡張ライブラリ @akashic-extension/resolve-player-info を導入して利用する必要があります。

game.json のあるディレクトリで、次を実行してください。

akashic install @akashic-extension/resolve-player-info

テキストエディタで game.json を開いて、次のような environment.external.coeLimited プロパティがなければ作成してください。値は "0" としてください。(v2.2.4 以降の akashic-cli では、 akashic install 時に自動的に作成されます。)

{
  ...,
  "environment": {
    "external": {
      "coeLimited": "0",
      "atsumaru": "0"
    }
  }
}

スクリプトアセット内で、 require() で関数 resolvePlayerInfo() を取得します。

const resolvePlayerInfo = require("@akashic-extension/resolve-player-info")
  .resolvePlayerInfo;

TypeScript の場合は import を利用してください。

import { resolvePlayerInfo } from "@akashic-extension/resolve-player-info";

名前の取得

得られた resolvePlayerInfo() のもっとも簡単な用法は、引数として { raises: true } を与えて呼び出すことです。これにより名前利用の許諾ダイアログが表示され、ダイアログに応答したプレイヤーのユーザ名情報が g.game.onPlayerInfo で通知されます。

また以降そのプレイヤーが生成したイベント (画面押下の g.PointDownEvent など) には、 player.name プロパティにユーザ名が含まれるようになります。

const nameTable = {};
g.game.onPlayerInfo.add(ev => {
  // 各プレイヤーが名前利用許諾のダイアログに応答した時、通知されます。
  // ev.player.name にそのプレイヤーの名前が含まれます。
  // (ev.player.id には (最初から) プレイヤーIDが含まれています)
  nameTable[ev.player.id] = ev.player.name;
});

scene.onPointDownCapture.add(ev => {
  // onPlayerInfo での通知後、そのプレイヤーが生成したイベントには名前情報が与えられます。
  if (ev.player.name != null) {
    console.log(ev.player.name + " さんが操作しました");
  }
});

// 以下で名前利用の許諾ダイアログを表示します。
// ただし、この関数はローカルイベント契機でのみ呼び出してください (後述) 。
resolvePlayerInfo({ raises: true });

API

resolvePlayerInfo(opts, callback) は、一つまたは二つの引数をとる関数です。

第一引数 opts は次のプロパティを持つオブジェクトです。

プロパティ名 デフォルト値 意味
raises boolean false true の場合、取得した名前情報で g.PlayerInfoEvent を生成して全体に通知する (g.game.raiseEvent() を呼び出す) 。
limitSeconds number 15 名前許諾ダイアログを自動的に閉じるまでの秒数。

第二引数 callback は、名前取得が完了した時に呼び出されるコールバック関数です。 この値は raises オプションに true を与える場合には、省略できますraises オプションの挙動をカスタマイズしたい場合に、 raisesfalse にして callback を渡してください。

callback は、 callback(err, playerInfo) の形で、一つまたは二つの引数で呼び出されます。それぞれの引数は次の内容を持ちます。

引数 内容
第一引数 (err) Error または null 名前取得に成功した場合、 null
失敗した場合、対応する Error
第二引数 (playerInfo) オブジェクトまたは null 取得した名前情報。文字列の name プロパティと補足情報 userData (詳細後述) を持つ。
errnull でない時、またその時のみ null

プレイヤーの名前は、 callback の第二引数 (playerInfo) を使って、 playerInfo.name でアクセスすることができます。

また playerInfo.userData.acceptedfalse の場合、そのユーザは名前取得を拒否したユーザです。この時 playerInfo.name は自動生成の文字列または null です。

playerInfo.userData.unnamedtrue の場合、そのユーザは名前がない特殊なユーザです (サーバサイドで実行されている特殊なインスタンスなど)。通常、 unnamed のユーザが実際にゲームプレイを行うことはありません。特に用途がなければ、このプレイヤーは無視してください。 raises オプションを true にして自動的に名前情報を全体通知する場合にも、 unnamed のプレイヤーは無視されます。

raises オプションで通知される g.PlayerInfoEvent

raisestrue を指定すると、取得した名前情報は自動的に g.PlayerInfoEvent として全プレイヤーに通知されます。このイベントは g.game.onPlayerInfo トリガーで通知されます。

onPlayerInfo のハンドラに渡される引数 (ev) は g.PlayerInfoEvent で、これには次のプロパティが含まれます。

プロパティ名 内容
player.id string プレイヤー ID
player.name string または null そのプレイヤーの名前
userData.accepted boolean 名前取得が許諾されたか。
false の場合、名前は自動生成されたものまたは null

コード例

マルチプレイゲーム内で参加ボタンを表示し、押したプレイヤーに名前利用の許諾を求めるコードは、次のようなものになります。

const resolvePlayerInfo = require("@akashic-extension/resolve-player-info")
  .resolvePlayerInfo;

module.exports = () => {
  const scene = new g.Scene({ game: g.game });
  scene.onLoad.add(() => {
    const font = new g.DynamicFont({
      game: g.game,
      fontFamily: "sans-serif",
      size: 15
    });

    // 「参加」ボタン (Label)
    const entryButton = new g.Label({
      scene: scene,
      x: 10,
      y: 10,
      font: font,
      text: "参加",
      fontSize: 15,
      textColor: "black",
      local: true, // ローカルエンティティにする
      touchable: true
    });
    entryButton.onPointUp.add(ev => {
      // 「参加」ボタンを押された時: 参加処理の代わりに名前取得の許諾を求める。
      // プレイヤーがダイアログに応じると、 raiseEvent() で名前情報が全体に通知されるので、
      // onPlayerInfo でそれを受信した時に参加者とする。
      resolvePlayerInfo({ raises: true });
    });
    scene.append(entryButton);

    // 参加者の情報を保持するテーブル
    const playersTable = {};
    g.game.onPlayerInfo.add(ev => {
      const player = ev.player;
      const anonymous = !ev.userData.accepted;
      playersTable[player.id] = {
        name: player.name, // 名前
        anonymous: anonymous, // 名前利用を拒否したか (自動生成の名前か)
        score: 0 // スコア (例。このコード例では使っていません)
      };
    });

    // 参加を「締め切る」ボタン (Label)
    const startButton = new g.Label({
      scene: scene,
      x: 10,
      y: 10,
      font: font,
      text: "締め切る",
      fontSize: 15,
      textColor: "black",
      local: true, // ローカルエンティティにする
      touchable: true
    });
    startButton.onPointUp.add(() => {
      // 参加が締め切られた。
      // この時点 playersTable に登録されているプレイヤーが「参加者」。
      // 名前が playersTable[(プレイヤーID)].name から参照できる。
      console.log("参加者情報", playersTable);

      // 参加者情報を受け取りゲーム部分のシーンを作成する関数 createGameScene()
      // (このコード例では定義していないので各ゲームで作成してください)
      const gameScene = createGameScene(playersTable);
      // それに replaceScene することでゲーム本編を開始する。
      g.game.replaceScene(gameScene);
    });

    g.game.onJoin.add(ev => {
      const broadcasterPlayerId = ev.player.id; // 放送者のプレイヤーID
      // 自分が放送者なら、「締め切る」ボタンを表示。
      if (g.game.selfId === broadcasterPlayerId) {
        scene.append(startButton);
      }
    });
  });
  g.game.pushScene(scene);
};

なお「ローカルエンティティ」や g.game.raiseEvent() など、Akashic Engine のマルチプレイ関連の機能については、チュートリアルの マルチプレイの基礎プレイヤーごとに異なる描画を行う を参照してください。

利用上の制限

呼び出し中にシーン遷移しない

resolvePlayerInfo() の呼び出し後、 コールバックが呼び出されるまでの間に、シーン遷移を行わないでください

これは実装に起因する制限です。 一部のタイムアウト判定に scene.setTimeout() が利用されている関係上、シーンを切り替えてしまうと正しく動作しなくなるケースがあります。

ローカル処理でのみ使う

resolvePlayerInfo() は、 ローカルイベントの処理 (ローカル処理) 内でのみ利用してください

言い換えると、原則ローカルエンティティ (local: true をつけて生成したもの) の onPointDownonPointUp のハンドラ内でのみ呼び出すようにしてください。 「ゲーム開始時に強制的に全員のプレイヤー名を取得する」といった使い方はできません。

これは、マルチプレイの参加者が「途中から」ゲーム画面を開く可能性があるためです (ニコ生ゲームの起動中に配信を見始めたなど)。 この時 Akashic Engine は最新フレームに追いつくためゲームを早送りで実行しますが、早送り中に名前取得ダイアログが表示されても操作はできません。 また早送り終了後に仮に名前取得が許諾されて PlayerInfoEvent が通知されても、ゲームは既に参加者募集中ではないなど、状態の混乱が考えられます。 さらに早送り中にシーン切り替えが発生すると、前述の「呼び出し中にシーン遷移しない」に反する可能性があります。

ゲーム開始時に参加者が確定しないゲーム (手番のプレイヤーを都度抽選するなど) の場合は、 「参加者が決まるたびに (名前利用を許諾していなければ) 許諾を促すボタンを表示する」など、「いつ見始めても参加できる」形を検討してください。

細かな補足

名前の重複

名前利用を許諾しなかった場合に自動生成される名前 ("ゲスト123" など) は重複する可能性があることに気をつけてください。自動生成に限らず、ユーザ名は重複する可能性があります。

厳密に識別したい場合は、参加順で番号を振る、名前に色をつけて表示するなど、コンテンツ側での対応が必要です。

ゲームアツマールでの動作

このライブラリは ゲームアツマール 環境 (アツマール) でも動作します。 ただし名前取得の許諾ダイアログは表示されず、自動的に許諾された扱いになります。 これはアツマールが名前を取得する API を最初から公開しているサービスであるためです。

ニコ生アプリのバージョン

iOS, Android の古いバージョンのニコ生アプリでは、この名前取得機能に対応していない場合があります。 その場合は、アプリの更新を促すダイアログが表示され、自動的に名前利用を許諾しなかった扱いになります (自動生成の名前が通知されます) 。