> For the complete documentation index, see [llms.txt](/ja/llms.txt)


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

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



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

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

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

## 作業の流れ

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

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

## 1. ネームスペースを設定する {#namespace}

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

| 設定する内容 | 設定項目 | 指定する値 |
| --- | --- | --- |
| サーバータイプ | `serverType` | `sync` |

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

## 2. キャラクターの Prefab を作る {#prefab}

### スクリプトを用意する

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

```csharp
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` を返すと、コルーチンとして実行されます。

```csharp
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 など）を作り、ルートに次のコンポーネントを追加します。

   | コンポーネント | 設定 |
   | --- | --- |
   | **NetworkEntity** | **Schema** は空のまま（位置だけを同期するため） |
   | **NetworkTransform** | **Position Axes** は `All`、**Rotation Axes** は `None` |
   | **PlayerMover** | 上のスクリプト |
   | **PlayerEmote** | 上のスクリプト |

2. **NetworkEntity > Controlled Behaviours** に `PlayerMover` を登録します。`PlayerMover` は、自分が所有するキャラクターでだけ有効になります。登録しないと、自分の画面にあるほかのプレイヤーのキャラクターにも、自分のキー入力が適用されてしまいます。

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

## 3. ルームに接続する {#connect}

### 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 アドレス・ポート・暗号鍵）とアクセストークンは、[ゲームサーバーへの接続情報を取得](#ゲームサーバーへの接続情報を取得)の方法で取得して渡します。

```csharp
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. 動作を確かめる {#verify}

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

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

## 5. 公開に向けて制限する {#catalog}

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

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

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

## 次に読むもの

- [Unity クライアント]()：操作権の貸し出し、ルームイベント、カスタムプロパティ、マスタークライアント、Interest Group、UDP、再接続の扱い。
- [Photon からの移行ガイド]()：PUN2 の API との対応表と移行の手順。
- [チュートリアル: サーバーで HP を管理する敵を作る]()：ルールでオブジェクトを生成・更新するサーバータイプ `stateMachine` の使い方。
- [チュートリアル: ロールバック式同期でキャラクターを動かす]()：位置をサーバーが計算し、自分のキャラクターを予測して動かす方法。



