プレイヤーの離脱に対処する
概要
マルチプレイのゲーム中に他のプレイヤーが離脱してもゲーム進行が停止しないようにするには、プレイヤーに時間制限を設ける方法があります。
詳細
マルチプレイゲームでは、各プレイヤーの行動がゲーム進行に反映されます。これは多彩なゲーム性を生みますが、一方で、プレイヤーの誰かがゲームから離脱した場合、ゲームの進行が途中で止まってしまうことがあります。ですが、Akashic Engine はプレイヤーの切断 (ネットワーク切断・アプリ終了など) を検出・通知する機能を提供していません。そのため、ゲーム開発者は自前で対応策を用意する必要があります。
この問題に対処する方法の一つとして、参加プレイヤーに時間制限を課し、時間を超過したプレイヤーをゲームから除外するといった仕組みが考えられます。
const playerIds = ["user1", "user2", "user3"]; // ゲーム開始時の参加ボタンの pointDown などから、予めプレイヤーのIDをリスト化しておく
const actionAPlayerIds = [];
const actionBPlayerIds = [];
const buttonA = new g.Label({
scene,
x: 0,
font,
text: "actionA",
touchable: true
});
buttonA.pointDown.add((e) => {
actionAPlayerIds.push(e.player.id);
});
scene.append(buttonA);
const buttonB = new g.Label({
scene,
x: g.game.width / 2,
font,
text: "actionB",
touchable: true
});
buttonB.pointDown.add((e) => {
actionBPlayerIds.push(e.player.id);
});
scene.append(buttonB);
setTimeout(() => {
closeVotePhase();
}, timeout);
function closeVotePhase() {
const disapearedPlayerIds = playerIds.filter(id => actionAPlayerIds.indexOf(id) === -1 && !actionBPlayerIds.indexOf(id) === -1);
// 以下、離脱プレイヤーへの対応処理
}
この例では、一定時間過ぎてもボタンを押さなかったプレイヤーを判定しています。このプレイヤーに対しては負けの処理を行う・任意のアクションをしたとみなすなどの処理を行うことができます。 また、この例では一定時間必ずプレイヤーの操作を待ちますが、一対一のゲームなどでは setTimeout
を待たずに closeVotePhase()
を呼ぶこともできます。
このような仕組みを組み込むことで、もしゲーム中に放置するプレイヤーがいた場合でも、ゲームを進行させることができます。
サンプル
このページのサンプルコードをダウンロード
サンプルは小さなじゃんけんゲームです。 このゲームでは、各プレイヤーは「join game」ボタンを押すことでじゃんけんに参加表明することができます。 ニコニコ生放送上で動かす場合、「放送者」のインスタンスに「start game」ボタンが表示されます。放送者がこのボタンを押すとゲームが始まります。ゲーム開始後に途中参加することはできません。
選択フェーズ
参加プレイヤーは3つのハンドサインから1つを選びます。選択結果は MessageEvent で各インスタンスに送られますが、まだ画面には反映されません。また、プレイヤーが何も選ばなかった場合、 MessageEvent は送られません。
const button = new JankenButton({
scene,
src: scene.getJankenImageAsset(handSign),
handSign,
width: 100,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
local: true,
touchable: true
});
button.onPointDown.add(() => {
const data = {
messageType: "selectJanken",
handSign
};
g.game.raiseEvent(new g.MessageEvent(data));
});
判定フェーズ
一定時間を過ぎると選択結果が集計されます。 各プレイヤーの画面に「win」「lose」あるいは「draw」の勝敗が表示されます。 勝利したプレイヤーが一人だけの場合は、そこでゲームが終了します。二人以上が残っていた場合、次の選択フェーズを開始するボタンが表示されます。敗北したプレイヤーは、次の選択フェーズに参加することはできません。
これをプレイヤーが最後の一人になるまで繰り返します。
このゲームでは、選択フェーズで何も選ばなかったプレイヤーは自動的に敗北と同じ扱いになります。このように、プレイヤーがゲームを放置した際に、自動的に何らかのペナルティや負けが成立するようなルールをゲームに持たせることで、プレイヤーの離脱に対処することができます。
const size = handsigns.size;
let survivedPlayerIds;
if (size === 2) {
// ハンドサインが2種類の場合、勝敗が決まる
// 勝利したプレイヤー以外をplayerIdsから除外する
if (selection.guu.size === 0) {
survivedPlayerIds = selection.choki;
} else if (selection.choki.size === 0) {
survivedPlayerIds = selection.paa;
} else if (selection.paa.size === 0) {
survivedPlayerIds = selection.guu;
}
} else if (size === 3 || size === 1) {
// あいこ
survivedPlayerIds = mergeSets([selection.guu, selection.choki, selection.paa]);
} else if (size === 0) {
// 参加プレイヤーなし
// ゲーム終了フェーズへ移行する
}
this.currentPlayerIds = survivedPlayerIds;
tips: ハートビートについて
他の方法として、ハートビート処理を実装する方法が考えられます。 ハートビートとは、定期的に全プレイヤーに対して確認イベントを送り、各プレイヤーは自動的に応答イベントを返すことで、プレイヤーの離脱を判断する方法です。 ハートビートに反応しないプレイヤーをゲームから除外することで、ゲームの進行を維持することができます。
// 不十分な対策例
setInterval(() => {
g.game.raiseEvent(new g.MessageEvent({ type: "heartbeat" }));
}, time);
ですが、この方法ではプレイヤーがアプリを終了した場合やネットワークが切断された場合には対処できても、プレイヤーがゲームに参加したまま放置した場合には対処することができないため、十分ではありません。なぜなら、放置されたゲームインスタンスはハートビートの MessageEvent を送ることができるため、メッセージのリスナはそのプレイヤーがゲームをプレイしているのか、それとも放置状態なのかを判断することができないからです。
特に、ターン制などのゲームではプレイヤー離脱が致命的な問題になります。プレイヤーの放置によってゲームの進行自体が停止してしまうことも考えられます。
他のデメリットとして、ハートビートは本来のゲーム進行には不要な通信を継続的に発生させる点も挙げられます。