チュートリアル: プレイヤーのキャラクターを同期する

ルールを書かずに、各プレイヤーのキャラクターの位置と RPC を Unity で同期するまでの手順

このチュートリアルでは、ルームに参加したプレイヤーがそれぞれ自分のキャラクターを動かし、ほかのプレイヤーの画面にも同じように表示される、小さなルームを作ります。

  • 接続すると、自分のキャラクターが 1 体生成されます。
  • 矢印キー(WASD)で自分のキャラクターを動かすと、ほかのプレイヤーの画面でも滑らかに動きます。
  • スペースキーを押すと、全員の画面でそのキャラクターが一瞬大きくなります(RPC)。

サーバータイプは sync を使います。ルームのルールを書く必要はありません。サーバータイプの違いは構成と選び方を参照してください。

作業の流れ

手順作業する場所作るもの
1. ネームスペースを設定するGS2Realtime のネームスペース設定
2. キャラクターの Prefab を作るUnity Editor移動と RPC のスクリプトを付けた Prefab
3. ルームに接続するUnity EditorPrefab Catalog と、接続して自分のキャラクターを生成するシーン
4. 動作を確かめるUnity(2 クライアント)—
5. 公開に向けて制限するGS2オブジェクトカタログ

GS2 SDK for Unity を導入した Unity プロジェクト(Unity 2021.3 以降)で作業します。同期のコンポーネントは SDK に含まれているので、追加のパッケージは必要ありません。

1. ネームスペースを設定する

GS2-Realtime のネームスペースで、サーバータイプを指定します。

設定する内容設定項目指定する値
サーバータイプserverTypesync

ルームの用意(GS2-Matchmaking と RoomWant)は、ほかのサーバータイプと同じです。

2. キャラクターの Prefab を作る

スクリプトを用意する

自分のキャラクターを動かすスクリプトです。Transform を動かすだけで、NetworkTransform がほかの参加者へ位置を送ります。スペースキーで、全員に RPC を送ります。

using Gs2.Realtime.Sync;
using UnityEngine;

// NetworkEntity の Controlled Behaviours に登録し、自分のキャラクターでだけ動かす
public sealed class PlayerMover : MonoBehaviour
{
    [SerializeField] float speed = 3f;

    void Update()
    {
        var move = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
        // Transform を動かすだけで、NetworkTransform がほかの参加者へ送る
        transform.position += move.normalized * (speed * Time.deltaTime);

        if (Input.GetKeyDown(KeyCode.Space))
            GetComponent<NetworkEntity>().Rpc(nameof(PlayerEmote.Wave), RpcTarget.All);
    }
}

RPC で呼ばれるメソッドには [NetworkRpc] を付けます。IEnumerator を返すと、コルーチンとして実行されます。

using System.Collections;
using Gs2.Realtime.Sync;
using UnityEngine;

public sealed class PlayerEmote : MonoBehaviour
{
    // 全員の画面で、そのキャラクターが一瞬大きくなる
    [NetworkRpc]
    public IEnumerator Wave(NetworkRpcInfo info)
    {
        Debug.Log("Wave from connection " + info.Sender);
        transform.localScale = Vector3.one * 1.5f;
        yield return new WaitForSeconds(0.3f);
        transform.localScale = Vector3.one;
    }
}

info.Sender は、RPC を送ったプレイヤーの接続 ID です。サーバーが設定するので、送った側が偽ることはできません。

Prefab を作る

  1. キャラクターの見た目になる Prefab(Capsule など)を作り、ルートに次のコンポーネントを追加します。

    コンポーネント設定
    NetworkEntitySchema は空のまま(位置だけを同期するため)
    NetworkTransformPosition Axes は All、Rotation Axes は None
    PlayerMover上のスクリプト
    PlayerEmote上のスクリプト
  2. NetworkEntity > Controlled Behaviours に PlayerMover を登録します。PlayerMover は、自分が所有するキャラクターでだけ有効になります。登録しないと、自分の画面にあるほかのプレイヤーのキャラクターにも、自分のキー入力が適用されてしまいます。

位置と回転は SDK に標準のフィールドがあるので、同期スキーマのアセットがなくても同期できます。HP のような独自の値を同期するときは、独自の値を表示するを参照してください。

3. ルームに接続する

NetworkWorld を置く

  1. Project ウィンドウの Create > GS2 > Realtime > Prefab Catalog でカタログを作ります。
  2. カタログの Prefabs に、Id 1 と手順 2 の Prefab を登録します。Prefab ID はオブジェクトの種類を表す番号で、ほかのクライアントはこの番号から表示する Prefab を選びます。
  3. シーンに空の GameObject を作り、NetworkWorld を追加して、Prefab Catalog にカタログを指定します。
  4. 地面になる Plane と、上から見下ろす Camera を置きます。

接続スクリプトを追加する

NetworkWorld と同じ GameObject に NetworkSession と、次のスクリプトを追加します。ルームの接続情報(IP アドレス・ポート・暗号鍵)とアクセストークンは、ゲームサーバーへの接続情報を取得の方法で取得して渡します。

using Gs2.Realtime.Sync;
using UnityEngine;

[RequireComponent(typeof(NetworkWorld), typeof(NetworkSession))]
public sealed class PlayerRoomConnection : MonoBehaviour
{
    const uint PlayerPrefab = 1; // Prefab Catalog に登録した Id

    NetworkWorld world;
    NetworkSession session;

    // イベントは一度だけ登録します(接続のたびに登録すると、再接続でハンドラーが重複します)
    void Awake()
    {
        world = GetComponent<NetworkWorld>();
        session = GetComponent<NetworkSession>();
        // ルームの状態を反映したら、自分のキャラクターを生成する。
        // 切断するとキャラクターは削除されるので、再接続のたびに作り直す
        session.Ready += () => world.Instantiate(
            PlayerPrefab,
            new Vector3(Random.Range(-3f, 3f), 0, Random.Range(-3f, 3f)),
            Quaternion.identity);
        session.Disconnected += reason => Debug.Log("切断しました: " + reason);
    }

    public void Connect(string accessToken, string ipAddress, int port, string encryptionKey)
    {
        session.Connect(accessToken, ipAddress, port, encryptionKey);
    }
}

ポイントは次の 3 つです。

  • 自分のキャラクターは world.Instantiate で作ります。 Unity の Instantiate で作った GameObject は、ほかの参加者には共有されません。world.Instantiate の戻り値は、その場で自分の所有(IsMine)として動かせます。ルームが生成を確定するまでの移動や RPC は保留され、確定後に送られます。
  • ほかのプレイヤーのキャラクターは NetworkWorld が作ります。 ルームから届いた生成を見て、カタログの Prefab から GameObject を作り、届いた位置へ補間して動かします。
  • 接続すると、その時点の状態が届きます。 あとから参加したプレイヤーにも、参加済みのプレイヤーのキャラクターがその時点の位置で表示されます。反映が終わると Ready が呼ばれます。

4. 動作を確かめる

同じルームへ、2 つのクライアントから接続します。同じ userId で接続すると先の接続が置き換わるため、別々の userId のアクセストークンを使ってください。

  1. 両方の画面に、2 体のキャラクターが表示されることを確かめます。
  2. 片方でキャラクターを動かし、もう片方の画面でも同じキャラクターが滑らかに動くことを確かめます。もう片方のキー入力では、自分のキャラクターだけが動きます。
  3. 片方でスペースキーを押し、両方の画面でそのキャラクターが一瞬大きくなることを確かめます。
  4. 片方を切断し、もう片方の画面からそのキャラクターが消えることを確かめます。接続し直すと、新しいキャラクターが生成されます。

5. 公開に向けて制限する

ここまでの設定では、ルームはプレイヤーが生成する Prefab や呼ぶ RPC を検査しません。改造したクライアントからの生成や RPC を制限するには、ネームスペース設定の syncSetting.objectCatalog にオブジェクトカタログを設定します。

  1. Prefab Catalog アセットを右クリックし、Copy Object Catalog JSON を選びます。登録した Prefab の同期するフィールドと RPC の ID が、JSON としてクリップボードにコピーされます。
  2. その JSON を syncSetting.objectCatalog に設定します。

設定すると、登録した Prefab だけを生成でき、methods に登録した RPC だけを所有者が呼べるようになります。RPC の ID はメソッド名から決まるので、メソッド名を変えたら JSON を作り直してください。

次に読むもの