はじめに

初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

今回は、オンチェーンゲームにおけるプレイヤー間の交流を促進するコミュニケーションチャンネルの仕組みを提案しているERC7566についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIPについてまとめています。

概要

この提案は、マルチプレイヤーゲーム通信(MGC)インターフェースについて述べています。 このインターフェースは、プレイヤーをマッチングしてグループに分けるために「ルーム」と呼ばれる概念を使用し、プレイヤー間でのアクションを処理するために「メッセージ」という方法を用います。 このシステムにより、1つのスマートコントラクトが複数のプレイヤーがチェーン上でゲームをすることを可能にし、ゲームの公平性に影響を与える可能性がある中央集権型サーバーを回避できます。

具体的には、以下のように機能します。

ルームの作成とマッチング

プレイヤーはまず、マルチプレイヤーゲームに参加するために「ルーム」と呼ばれる仮想空間に入ります。 ルームは、特定のゲームセッションやプレイヤーグループを識別するためのものです。 マッチングシステムは、プレイヤーのスキルレベルや好みに基づいて、適切なルームを見つけるか、新しいルームを作成します。

メッセージを用いたアクション処理

ゲーム中、プレイヤーからの各アクション(例えば、動く、攻撃する、アイテムを使うなど)は「メッセージ」として扱われます。 これらのメッセージは、スマートコントラクトによって処理され、ゲームの状態を更新します。 これにより、プレイヤー間の相互作用やゲーム内での出来事がリアルタイムで反映されます。

スマートコントラクトの利用

このシステムはスマートコントラクトを利用しています。 スマートコントラクトは、ブロックチェーン上で自動的に実行されるプログラムのことで、ゲームのルールやプレイヤー間の取引がコードによって事前に定義されており、公平性が保証されます。 この方法により、ゲームのロジックが中央集権的なサーバーに依存することなく、ブロックチェーン上で透明かつ安全に実行されます。

この提案により、ブロックチェーン技術を活用してマルチプレイヤーゲームをより公平で、安全に楽しむことが可能になります。 また、中央集権型サーバーに依存しないため、サーバーのダウンや不正行為からゲームの公平性を守ることができます。

動機

提案されているマルチプレイヤーゲーム通信フレームワーク(MGCフレームワーク)は、中央集権型サーバーに頼らずにブロックチェーン上でマルチプレイヤーゲームを運用するための方法を提供します。 このフレームワークの利点と特徴は以下の通りです。

公平性の検証

全てのゲームデータへのアクセス

プレイヤーと開発者は、標準化されたインターフェースを通じてゲームデータにアクセスできます。 これにより、ゲームの公平性を独自に検証することが可能になります。

ブロックチェーン上のアクション

プレイヤーのアクションはすべてブロックチェーン上で行われ、記録されるため、データの改ざんや不正行為が防げます。

開発の簡略化

基本的なグルーピングとメッセージングアーキテクチャ

このフレームワークは、グルーピング(ルームの作成)とメッセージング(アクションの処理)の基本的な構造を提供します。 これにより、開発者はゲームの核心となるロジックの開発に集中できます。

分解可能な構成

大規模なゲームを複数のスマートコントラクトに分解することが可能です。 これにより、より複雑なゲームもブロックチェーン上で構築できるようになります。

拡張性とカスタマイズ性

メッセージのカスタマイズ

開発者は異なるゲームに合わせてカスタマイズされたメッセージを定義できます。 これにより、特定のゲームのニーズに合わせた拡張が可能になります。

ルームの階層的データ構造

各プレイヤーはルーム内で新しいIDを割り当てられ、これにより開発者はプレイヤーの状態をより簡単に管理できます。

実装のイメージ

具体的には、このフレームワークを使って、例えばチェスやカードゲームなどのマルチプレイヤーゲームをブロックチェーン上で実行することが考えられます。 プレイヤーはゲームに参加するために「ルーム」に入り、ゲーム中の各手(アクション)はメッセージとしてブロックチェーン上に記録されます。 これにより、ゲームの進行は完全に透明で、全ての参加者が公平性を検証できるようになります。

このフレームワークは、ゲーム開発者がブロックチェーン技術を利用して、中央集権的なサーバーの制約や不正行為のリスクなしにマルチプレイヤーゲームを提供するための強力なツールを提供します。

仕様

マルチプレイヤーゲーム通信(MGC)の原理は、異なるプレイヤーグループの状態を変更するために同じゲームロジックを使用することにあります。 このシステムは、「ルーム」と「メッセージ」の2つの核心部分から成り立っています。

ルーム

プレイヤーの容器

ルームはプレイヤーを収容するための仮想の空間です。 プレイヤーはゲームに参加するためにまずルームに入ります。

マッチングと接続の表示

ルームはプレイヤー同士をマッチングする機能を持ち、接続しているプレイヤーを表示します。 これにより、誰がゲームに参加しているかが一目でわかります。

ゲームの開始条件

プレイヤーがルームに入ることで初めてゲームをプレイできるようになります。 つまり、ルームはゲームを開始するための「部屋」として機能します。

メッセージ

プレイヤー間のアクション

メッセージはプレイヤー間で行われるアクションを表します。 これには移動、攻撃、アイテムの使用など、ゲーム内でのさまざまな行動が含まれます。

ゲーム行動の実行

メッセージを使ってゲームの振る舞いを実行し、ルーム内のプレイヤーの状態を変更します。 例えば、他のプレイヤーを攻撃するメッセージを送ることで、そのプレイヤーの体力を減らすことができます。

プレイヤー状態の変更

メッセージを通じて行われるアクションは、プレイヤーの状態に直接影響を与えます。 これにより、ゲームの進行状況やプレイヤー間の相互作用がリアルタイムで反映されます。

このように、MGCの原理はシンプルですが、プレイヤーを効果的にグルーピングし、彼らのアクションを管理することで、ブロックチェーン上でマルチプレイヤーゲームを実現する強力な手段を提供します。 これにより、ゲーム開発者は中央集権型サーバーに頼ることなく、公平で透明なゲーム環境を提供できるようになります。

インターフェース

ts
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0;

import "./Types.sol";

interface IMOG {
    /**
     * Create a new room.
     * @dev The entity MUST be assigned a unique Id.
     * @return New room id.
     */
    function createRoom() external returns (uint256);

    /**
     * Get the total number of rooms that have been created.
     * @return Total number of rooms.
     */
    function getRoomCount() external view returns (uint256);

    /**
     * Player joins room.
     * @dev The member MUST be assigned a unique Id.
     * @param _roomId is the id of the room.
     * @return Member id.
     */
    function joinRoom(uint256 _roomId) external returns (uint256);

    /**
     * Get the id of a member in a room.
     * @param _roomId is the id of the room.
     * @param _member is the address of a member.
     * @return Member id.
     */
    function getMemberId(uint256 _roomId, address _member)
        external
        view
        returns (uint256);

    /**
     * Check if a member exists in the room.
     * @param _roomId is the id of the room.
     * @param _member is the address of a member.
     * @return true exists, false does not exist.
     */
    function hasMember(uint256 _roomId, address _member)
        external
        view
        returns (bool);

    /**
     * Get all room IDs joined by a member.
     * @param _member is the address of a member.
     * @return An array of room ids.
     */
    function getRoomIds(address _member)
        external
        view
        returns (uint256[] memory);

    /**
     * Get the total number of members in a room.
     * @param _roomId is the id of the room.
     * @return Total members.
     */
    function getMemberCount(uint256 _roomId) external view returns (uint256);

    /**
     * A member sends a message to other members.
     * @dev Define your game logic here and use the content in the message to handle the member's state. The message MUST be assigned a unique Id
     * @param _roomId is the id of the room.
     * @param _to is an array of other member ids.
     * @param _message is the content of the message, encoded by abi.encode.
     * @param _messageTypes is data type array of message content.
     * @return Message id.
     */
    function sendMessage(
        uint256 _roomId,
        uint256[] memory _to,
        bytes memory _message,
        Types.Type[] memory _messageTypes
    ) external returns (uint256);

    /**
     * Get all messages received by a member in the room.
     * @param _roomId is the id of the room.
     * @param _memberId is the id of the member.
     * @return An array of message ids.
     */
    function getMessageIds(uint256 _roomId, uint256 _memberId)
        external
        view
        returns (uint256[] memory);

    /**
     * Get details of a message.
     * @param _roomId is the id of the room.
     * @param _messageId is the id of the message.
     * @return The content of the message.
     * @return Data type array of message content.
     * @return Sender id.
     * @return An array of receiver ids.
     */
    function getMessage(uint256 _roomId, uint256 _messageId)
        external
        view
        returns (
            bytes memory,
            Types.Type[] memory,
            uint256,
            uint256[] memory
        );
}

createRoom

javascript
function createRoom() external returns (uint256);

概要

新しいルームを作成する関数。

詳細

この関数は新しいゲームルームを作成し、それに一意のIDを割り当てます。 ゲーム内でプレイヤーが互いにマッチングし、ゲームを始めるための「部屋」を提供するために使用されます。

戻り値

  • uint256
    • 新しく作成されたルームの一意のID。

getRoomCount

javascript
function getRoomCount() external view returns (uint256);

概要

作成されたルームの総数を取得する関数。

詳細

この関数は、これまでに作成されたルームの合計数を返します。 これにより、現在のゲーム内でどれだけのアクティブなルームがあるかを把握することができます。

戻り値

  • uint256
    • 作成されたルームの総数。

joinRoom

javascript
function joinRoom(uint256 _roomId) external returns (uint256);

概要

プレイヤーがルームに参加する関数。

詳細

指定されたIDのルームにプレイヤーを追加します。 参加するプレイヤーは、そのルーム内で一意のIDを割り当てられます。 これにより、ゲーム内でプレイヤーを識別し、管理することが可能になります。

引数

  • _roomId
    • プレイヤーが参加するルームのID。

戻り値

  • uint256
    • ルームに参加したプレイヤーに割り当てられた一意のメンバーID。

getMemberId

javascript
function getMemberId(uint256 _roomId, address _member) external view returns (uint256);

概要

ルーム内のメンバーIDを取得する関数。

詳細

指定されたルームIDとメンバーのアドレスに基づき、そのメンバーのルーム内での一意のIDを返します。 これにより、特定のルーム内でのプレイヤーの識別が可能になります。

引数

  • _roomId
    • メンバーIDを取得したいルームのID。
  • _member
    • IDを取得したいメンバー(プレイヤー)のアドレス。

戻り値

  • uint256
    • 指定されたメンバーのルーム内での一意のID。

hasMember

javascript
function hasMember(uint256 _roomId, address _member) external view returns (bool);

概要

メンバーがルーム内に存在するか確認する関数。

詳細

指定されたルームIDとメンバーのアドレスをもとに、そのメンバーがルーム内に存在するかどうかを確認します。 これにより、特定のプレイヤーがゲームに参加しているかどうかを判定することができます。

引数

  • _roomId
    • 存在確認を行いたいルームのID。
  • _member
    • 存在確認を行いたいメンバー(プレイヤー)のアドレス。

戻り値

  • bool
    • メンバーがルーム内に存在する場合はtrue、存在しない場合はfalse。

getRoomIds

javascript
function getRoomIds(address _member) external view returns (uint256[] memory);

概要

メンバーが参加しているすべてのルームIDを取得する関数。

詳細

指定されたメンバー(プレイヤー)が参加しているすべてのルームのIDを配列で返します。 これにより、特定のプレイヤーがどのゲームに参加しているかを簡単に確認できます。

引数

  • _member
    • ルームIDを取得したいメンバーのアドレス。

戻り値

  • uint256[] memory
    • 指定されたメンバーが参加しているルームのIDの配列。

getMemberCount

javascript
function getMemberCount(uint256 _roomId) external view returns (uint256);

概要

特定のルーム内のメンバー数を取得する関数。

詳細

指定されたルームIDに基づいて、そのルーム内にいるメンバー(プレイヤー)の総数を返します。 これにより、ゲームのサイズや参加者の規模を把握することができます。

引数

  • _roomId
    • メンバー数を取得したいルームのID。

戻り値

  • uint256
    • 指定されたルーム内のメンバーの総数。

sendMessage

javascript
function sendMessage(uint256 _roomId, uint256[] memory _to, bytes memory _message, Types.Type[] memory _messageTypes) external returns (uint256);

概要

メンバーが他のメンバーにメッセージを送信する関数。

詳細

特定のルーム内で、1人のメンバーが他のメンバーに向けてメッセージを送信します。 この関数を使用することで、ゲームのロジックに基づいたプレイヤーの状態の変更や、プレイヤー間のコミュニケーションが可能になります。 送信されたメッセージは一意のIDを持ちます。

引数

  • _roomId
    • メッセージを送信するルームのID。
  • _to
    • メッセージの受信者となるメンバーIDの配列。
  • _message
    • 送信するメッセージの内容。
  • _messageTypes
    • メッセージの内容のデータタイプの配列。

戻り値

  • uint256
    • 送信されたメッセージに割り当てられた一意のID。

getMessageIds

javascript
function getMessageIds(uint256 _roomId, uint256 _memberId) external view returns (uint256[] memory);

概要

特定のメンバーがルーム内で受け取ったすべてのメッセージIDを取得する関数。

詳細

指定されたルームIDとメンバーIDに基づき、そのメンバーが受け取ったすべてのメッセージのIDを配列で返します。 これにより、プレイヤーがゲーム内でどのようなコミュニケーションを受けているかを追跡することができます。

引数

  • _roomId
    • メッセージIDを取得したいルームのID。
  • _memberId
    • メッセージIDを取得したいメンバーのID。

戻り値

  • uint256[] memory
    • 指定されたメンバーが受け取ったメッセージのIDの配列。

getMessage

javascript
function getMessage(uint256 _roomId, uint256 _messageId) external view returns (bytes memory, Types.Type[] memory, uint256, uint256[] memory);

概要

メッセージの詳細情報を取得する関数。

詳細

指定されたルームIDとメッセージIDに基づき、メッセージの内容、データタイプ、送信者ID、受信者IDの配列を返します。 これにより、ゲーム内の特定のコミュニケーションの詳細を確認することができます。

引数

  • _roomId
    • メッセージの詳細を取得したいルームのID。
  • _messageId
    • 詳細を取得したいメッセージのID。

戻り値

  • bytes memory
    • メッセージの内容。
  • Types.Type[] memory
    • メッセージの内容のデータタイプの配列。
  • uint256
    • メッセージの送信者ID。
  • uint256[] memory
    • メッセージの受信者IDの配列。

ライブラリ

Types.solライブラリには、上記のインターフェイスで使用されるSolidity型の列挙が含まれています。

補足

マルチプレイヤーオンチェーンゲームが「ルームベース」である理由と、プレイヤーのデータやゲーム内での動作に関する説明をしています。

ルームベースの理由

オンチェーンゲームでは、「ルーム」を使用してプレイヤーをグルーピングします。 ルームはゲーム内の独立した空間を提供し、プレイヤーはそれぞれのルームに入るたびに新しいIDを割り当てられます。 この方式により、新しいゲームラウンド、ゲームタスク、ゲームアクティビティごとに異なるルームを設定でき、ゲーム内でのさまざまなシナリオを柔軟に管理することが可能になります。

プレイヤーの状態とデータの扱い

ゲーム状態とは、ゲーム内でのプレイヤーのデータ変更を指します。 sendMessage関数は、プレイヤーの状態を変更する役割を果たします。 ゲームのロジックに応じて、ルーム内(内部的)またはルーム外(グローバル)でデータを定義することができ、ゲームを非常に柔軟に設計することができます。

プレイヤーデータの初期化

プレイヤーデータは、createRoomまたはjoinRoomのいずれかで初期化することができます。 これにより、プレイヤーがゲームに参加する時に、必要なデータを設定し、適切なゲーム状態から開始できます。

プレイヤーの退出のチェックと処理

プレイヤーがゲームから退出したかどうかは、block.timestampまたはblock.numberを使用して、メンバーの最新のsendMessage時間を記録することでチェックできます。 sendMessageにメッセージタイプを追加し、他のプレイヤーがこのメッセージタイプを使用して、メンバーがオフラインであるとクレームし、罰することができます。

適切なゲームカテゴリ

このフレームワークは、リアルタイムのマルチプレイヤーゲーム標準ではなく、マルチプレイヤーオンチェーンゲームに適しています。 適したゲームカテゴリは、コントラクトがデプロイされるネットワークに依存します。 一部のレイヤー2ネットワークはブロックを非常に迅速に処理するため、よりリアルタイム性の高いゲームを作成することが可能です。 一般的に、このネットワークは戦略、トレーディングカード、ターンベース、チェス、サンドボックス、決済ゲームに適しています。

このように、マルチプレイヤーオンチェーンゲームの設計は、柔軟性、拡張性、プレイヤーのデータ管理、ゲーム状態の変更など、多くの利点を提供します。 これにより、開発者は中央集権的なサーバーに依存せず、ブロックチェーン上で透明かつ公平なゲーム体験をプレイヤーに提供することが可能になります。

実装

以下に実装コードが格納されています。

引用

Rickey (@HelloRickey), "ERC-7566: Multiplayer Game Communication [DRAFT]," Ethereum Improvement Proposals, no. 7566, November 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7566.

最後に

今回は「」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!