このページのサンプルコードをダウンロード
Akashic Engine では、マルチプレイのゲームを作成することができます。
ただしマルチプレイゲームの公開には、独自のサーバソフトウェアが必要になります。 そのため (シングルプレイのゲームと比較すると) 公開方法が限られます。
現在 Akashic Engine で作成したマルチプレイゲームを一般に公開する方法としては、 「ニコ生ゲームとして公開する」があります。
この詳細は マルチプレイゲームを公開する で後述します。
マルチプレイゲームのコーディングに特別な準備は必要ありません。
Akashic Engine の提供する各種イベントには、全て「誰がそのイベントを生成したか」の情報が付与されています。たとえば g.E#pointDown
のハンドラに与えられる g.PointDownEvent
には player
プロパティがあり、「誰が画面をタッチしたのか」識別することができます。
操作したプレイヤーに応じて処理を分ければ、自動的にマルチプレイゲームに対応できます。
例えば、一番最初にボタンを押したプレイヤーに得点が入る「早押しボタン」のようなコンテンツは、次のようなコードで作ることができます。
const scene = new g.Scene({
game: g.game,
assetIds: ["button"] // button は適当な画像のアセットID
});
scene.onLoad.add(() => {
const button = new g.Sprite({
scene: scene,
src: scene.asset.getImageById("button"),
touchable: true
});
const scores = {}; // 得点を格納するテーブル
// エンティティ button が押された時:
button.onPointDown.add(ev => {
const playerId = ev.player.id; // 押したプレイヤーのID
if (scores[playerId] == null) {
// 初回の場合は0点で初期化
scores[playerId] = 0;
}
scores[playerId]++; // 押したプレイヤーの得点を加算
});
scene.append(button);
});
pointDown
のハンドラに与えられる引数 ev
(g.PointDownEvent
) の player
プロパティでプレイヤーを識別し、プレイヤーごとに得点を求めています。
player.id
はプレイヤーごとにユニークな文字列または undefined
です。
このコンテンツは、もちろん現実的には「ボタンの表示タイミングをランダムにする」「ボタンを押した時間によって得点を変える」「ボタンを押した時の画像や効果音をつける」「一定時間後に全員のスコアを表示して勝敗を決める」といった処理を足していかないとゲームらしくはなりませんが、骨格としてはマルチプレイに対応したものになっています。
もう少し画面上で分かりやすい例として、「各プレイヤーに対応するキャラクタ画像がクリックした位置に移動するコンテンツ」を考えます。
キャラ画像としては 素材ページ の次の画像を chara.png
として利用します。
コードは次のようなものになります。
const scene = new g.Scene({
game: g.game,
assetIds: ["chara"]
});
scene.onLoad.add(() => {
const characters = {}; // キャラクタ管理テーブル
// 画面内のどこかが押されたとき
scene.onPointDownCapture.add(ev => {
const x = ev.point.x; // 押された位置のX座標
const y = ev.point.y; // 押された位置のY座標
const playerId = ev.player.id; // 押したプレイヤーのID
if (characters[playerId] == null) {
// プレイヤー playerId にとって初めての操作の場合: キャラクタデータを生成
const chara = {
targetX: x,
targetY: y,
entity: new g.Sprite({
scene: scene,
src: scene.asset.getImageById("chara"),
x: x,
y: y
})
};
characters[playerId] = chara;
// キャラクタのフレームごとの処理(目標座標 (targetX, targetY) に近づくように位置を更新)を登録
chara.entity.onUpdate.add(() => {
const diffX = chara.targetX - chara.entity.x;
const diffY = chara.targetY - chara.entity.y;
// 既に目標座標にいるなら何もせず抜ける
if (diffX === 0 && diffY === 0) return;
// 目標座標から各軸10px以上離れていたら、1割ずつ接近。10px以下なら目標座標に移動
chara.entity.x += Math.abs(diffX) > 10 ? Math.floor(diffX / 10) : diffX;
chara.entity.y += Math.abs(diffY) > 10 ? Math.floor(diffY / 10) : diffY;
chara.entity.modified(); // 変更を反映
});
scene.append(chara.entity); // シーンに生成したキャラクタ画像を追加
} else {
// プレイヤー playerId のキャラが既にいる場合: キャラの目標座標を、クリックされた位置に更新
characters[playerId].targetX = x;
characters[playerId].targetY = y;
}
});
});
コードは増えていますが、
characters
) を作り、player
プロパティを参照して、プレイヤーごとの処理を行うという点では「早押しボタン」と同じ構造になっています。
ここまで、ゲームの動作確認には Akashic Sandbox (akashic-sandbox
) を利用してきました。しかしこのツールはシングルプレイの動作確認を行うためのものです。マルチプレイの確認を行うことはできません。ブラウザで複数のウィンドウを開いても、それぞれ独立した別のゲームプレイになってしまいます。
akashic-cli v1.5.1 以降には、マルチプレイゲームの動作確認機能 (serve
コマンド) が追加されています。これは(独立したツールではなく) akashic-cli に組み込まれていますが、 akashic-sandbox 同様サーバとして動作するコマンドです。
インストールされている akashic-cli のバージョンは次のコマンドで確認できます。
akashic -V
1.5.1 より小さい値が表示された場合は、次のコマンドで akashic-cli の最新版をインストールしてください。
npm install -g @akashic/akashic-cli
インストール後、game.json のあるディレクトリで、次のコマンドを実行してください。
akashic serve
実行後、 http://localhost:3300/
にアクセスしてください。次のような画面が表示されます。
先の例の「各プレイヤーに対応するキャラクタ画像がクリックした位置に移動するコンテンツ」の場合、画面をクリックするとキャラクタ画像が表示されます。もう一度クリックするとキャラクタ画像が移動します。
この状態でもう一つブラウザウィンドウを開き、同じ URL を読み込むと、同じゲームプレイに接続することができます。それぞれの画面をクリックすると、それぞれのキャラクタ画像が操作されます。これでマルチプレイの動作を確認することができます。
なお akashic serve
は akashic-sandbox
と異なり、ブラウザをリロードしてもゲームプレイがリセットされません。リセットは画面内のボタンで行います。
画面内のツールバー左側に、ゲームプレイ全般の操作を行うボタンが配置されています。次の機能があります。
akashic serve
コマンドは、 akashic-sandbox
と同様に Ctrl-C で終了することができます。
akashic serve
でしばらくプレイした後に、ブラウザウィンドウを追加すると、最初にビデオ映像を早送りするような画面が表示されることに気がつきます。これは Akashic Engine のマルチプレイの実現方法に起因する動作です。
Akashic におけるマルチプレイは、プレイヤー間でゲームの実行状態を「間接的に」共有することで実現されています。 Akashic はプレイヤー間で実行状態を直接的に共有することはしませんが、その代わりに 全てのプレイヤーが行う全ての操作を共有します 。ゲーム内で行われた全操作を全プレイヤーに通知することで、全プレイヤーの手元でまったく同じ処理を行い、結果として同じ実行状態を再現します。 同じゲームスクリプトに、同じ経過フレーム数で同じイベントを与えれば、同じ実行状態に至るはずだ 、というのが、Akashic Engine のマルチプレイの基本的なデザインです。後から参加したプレイヤーに、早送りで最新状態に追いつく動作が発生するのはこのためです。
Math.random()
ではなく g.game.random
を使う必要があるのもこのためです。
Math.random()
を使うと、その結果は実行環境(ブラウザウィンドウ)ごとに異なるので、同じイベントを与えても同じ状態が再現されなくなってしまいます。
g.game.random
は一つのゲームプレイ内で全員が同じ乱数シードと乱数生成アルゴリズムを使うように作られているので、この問題がありません。
Math.random()
のような制約はつきますが、このデザインによってゲーム開発者は、ゲーム状態の通知や共有などの問題をほぼ気にすることなく、手軽にマルチプレイのゲームを作成することができます。
全プレイヤーで共通の乱数ではなく、プレイヤー固有の乱数を生成したい場合には、
g.game.localRandom
が利用できます。 ただしg.game.localRandom
はローカル処理 (ローカルイベントに起因して実行される処理) の中でのみ利用することができます。 ローカル処理については、次項のプレイヤーごとに異なる描画を行うを参照してください。