そう、私たちはMomentoで、リスをテーマにした『Flappy Bird』の多人数参加型レプリカを作ったのです

Flappy MoをMomento CacheとTopicsで1日以内に構築した方法をご覧ください。

最近、FlappyMoというブラウザベースのゲームを作りました。2014年に話題になった「Flappy Bird」というゲームのバージョンです。画面上のスクロールするパイプの間を鳥をジャンプさせるシンプルなアーケードスタイルのゲームです。私のバージョンは、鳥を当社のマスコットであるMoに置き換え、リアルタイムで友人と対戦できるようにマルチプレイの要素を加えています。

もちろん、ゲームのインタラクティブなコンポーネントを作るためにMomentoを使りました。Momento CacheTopicsはブラウザ上で完全に機能するので、この2つを活用して、通常なら数週間かかる作業を数時間で素早く簡単に取り組みました。従来のシングルプレイヤーゲームをマルチプレイヤーゲームにするためには、プレイヤーのトラッキングセッションの同期という2つの機能を追加する必要がありました。

その前に、プレーヤー体験をより有意義なものにするために追加した機能がいくつかあります。もちろん、何らかの認証が必要です。このゲームはサインアップ不要のオープンなものだが、それでもユーザーは一意に識別される必要があります。認証に加えて、ゲームは全員が同時に開始する必要がありました。これにより、プレイヤーは画面上でお互いを確認し、頭脳戦を繰り広げることができます。そこで、リモート・スタート機能を追加して、全プレイヤーが同時にゲームを開始できるようにしました。これで、以下のようなアーキテクチャになりました:

では、フラッピー・モーを楽しむために、システムのさまざまな要素がどのように組み合わされているのかを見ていきましょう。

認証

ユーザーが初めてゲームのウェブサイトにアクセスすると、ユーザー名を入力するよう促されます。正しいユーザー名を入力してSubmitを押すと、ゲームのサーバー側コンポーネントにAPIコールが行われ、ログインします。

技術的にユーザがシステムに存在しないので、ユーザ名とパスワードを検証する代わりに、制限された権限セットを持つ一時的な auth トークンを発行しています。認証トークンは1時間有効で、有効期限が切れる前に別のトークンが発行されないようにMomentoにキャッシュされます。

このトークンを使用するMomentoクライアントは、flappy-moトピックの公開と購読、およびplayer cache Setアイテムの読み取りを許可されます(詳細は後述します)。さらに、プレーヤーのユーザー名をトークンに埋め込むことで、プレーヤーのアクションを一意に識別できるようになります。

このトークンはプレーヤーに返され、ブラウザセッションでMomento CacheとTopicクライアントを初期化するために使用されます。

プレイヤーの追跡

認証トークンが発行され、ゲームページにたどり着いた後、サーバーに自分がここにいて、アクションを起こす準備ができていることを伝える必要があります。そのために、ゲームはビーコンをサーバーサイドのコンポーネントに送り、ページの初期ロード時にユーザー名をプレイヤーリストに登録します。逆に、戻るボタンを押すかタブを閉じるかしてページがアンロードされると、ユーザー名をリストから削除するために別のビーコンが送信されます。

ビーコンとは、ウェブ サーバーへの非同期かつノンブロッキングの呼び出しです。アンロード イベント中に実行されるアクションは、ナビゲート時にユーザー エクスペリエンスが低下するのを防ぐため、ノンブロッキングである必要があるためです。ビーコンは「ベストエフォート」アプローチで配信されます。つまり、イベントを複数回送信することが可能です。ログイン・ビーコンが複数回送信される状況を処理するために、set cache itemを使用します。

セットとは、ユニークな要素の並び順のないコレクションです。そのため、個々のユーザー名をプレーヤーセットに何度追加しようとしても、表示されるのは1回だけです。これにより、ビーコンハンドラはidempotent(冪等)となり、システムに同じ影響を与えることで重複したリクエストを処理することができます。

リモートスタート

マルチプレイヤーアーケードスタイルのゲームを楽しむには、全員が同時にプレイを開始する必要があります。これによって、パズルの同じ場所に常にいることになるので、フレンドがどこまで到達したかを見ることができます。

そのために、30秒ごとにcronスケジュールでAWSのLambda関数をトリガーしている。このLambda関数は、次のゲームのためにランダムな値を生成する。重力の強さ、ジャンプの高さ、パイプの間隔、ゲームのスピードはすべてランダムに選ばれ、ラウンドを常に変化させて楽しませてくれます。

ゲームの詳細がランダム化されると、Momentoトピックを介して公開されます。ゲームのすべてのオープンインスタンスはトピックを購読し、リアルタイムでメッセージを受信します。各ゲームはメッセージの値でラウンドの詳細を更新し、3秒間のカウントダウンを開始します。

セッション同期

ここまでは、ゲームプレーヤーを管理し、全員が同時にプレーできるようにしました。今度は、プレーヤーが画面上を飛び回りながら、すべてのゲームをリアルタイムで更新する必要があります。

各プレーヤーの移動はブラウザで計算されるので、プレーヤーがジャンプボタンを押すたびに、Momento Topicsを介して新しい速度とy座標を公開します。すべてのゲームインスタンスはこのメッセージを即座に受信し、移動イベントを送信したプレイヤーの位置を更新します。

ユーザー名はメッセージの公開に使用される認証トークンに埋め込まれているため、デフォルトでユーザーIDがイベント内で提供され、選手の動きを安全かつセキュアに暗黙的に変更できます。

await topics.subscribe('flappy-mo', 'player-moved', {
  onItem: async (event) => updatePlayer({
    message: event.value(), 
    username: event.tokenId()
  })
});

ユーザー名は自動的にイベントに含まれるので、更新された座標を公開するときに入力する必要はありません。パブリッシュはシンプルなワンライナーです:‍

topics.publish('flappy-mo', 'player-moved', JSON.stringify({ 
  velocity: player.velocity, 
  y: player.y
}));

バックエンドのアーキテクチャやWebSocket接続を管理する必要はありません。すべてのアクティブなゲームで状態を同期させるのは、文字通り上の2つのコードブロックだけです。しかも、完全にブラウザ上で行われます。それだけなのです!

Gameplay

この時点で、試合について話すべきでしょう。ゲームのカウントダウンがリモートスタート機能によってトリガーされると、いくつかのことが起こります。

まず、プレーヤーの配列がキャッシュからリフレッシュされます。ブラウザに直接キャッシュされたプレーヤーを読み込み、新しいユーザー名を追加してローカルにあるリストを更新し、もう存在しないユーザーを削除します。

私はこのパターンが大好きです。キャッシュ・セットはビーコンによってサーバー・サイドで変更されるが、クライアントでは直接読み込まれます。クライアントとサーバー間でリソースを共有することで、開発は驚くほどシンプルになります。

選手リストを更新すると、座標はすべての選手の開始時の値にリセットされます。これにより、すべてのプレーヤーが同じ時間に同じ場所からスタートするようになります!

前述したように、プレーヤーの動きはすべてブラウザ上で計算されます。アニメーションループがトリガーされると、各プレイヤーごとに計算が行われ、ラウンドごとの重力、パイプの間隔、ゲームスピードなどを考慮して、それに応じてプレイヤーを動かします。パイプとプレイヤーが衝突すると、アクティブ状態をfalseに設定し、次のラウンドが始まるまで画面から隠します。このプロセスは、全員がパイプにぶつかるまで続き、次のリモートスタートがトリガーされるとゲームはリセットされます。

Ready to play?

どのように作られているかがわかったところで、いよいよ試してみよう!ゲームはこちらから。30秒ごとにラウンドが始まるので、ページにアクセスしてもすぐには始められないでしょう。

プレイするには、マウスをクリックするか、スペースキーを押してジャンプします。モバイルの場合は、画面をタップするだけです。このゲームは楽しいが、信じられないほどイライラするゲームなので、時間をかけて、ランダムなゲーム設定でサイコロが有利に転がることを祈りましょう!

ソースコードをご覧になりたい方は、GitHubにあるコードをどうぞ。ユーザーインターフェースはNext.jsで書かれており、リモートスタート機能はNode.jsのLambda関数です。ご自由にフォークしてください!

Momentoは、ブラウザセッションを接続し、サーバーサイドの実装とクライアント間の共有リソースを提供することで、ゲーム開発を劇的に簡素化します。ゲーム内チャット、大規模なリーダーボード、レート制限コントロールなど、Momentoの豊富なSDKを使用して簡単に構築できます。ブラウザベースのアプリ、iPhone、Androidなど、あらゆるプラットフォームに対応したクライアントが用意されているため、クロスプラットフォーム開発も簡単です。

Momentoのゲーム開発についてもっと知りたいですか?ゲームのページをご覧ください!質問があったり、アイデアを出したいですか?Discord に参加してください!