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


# リアルタイム編：2人のキャラクターを同期する

自分が動かしたキャラクターを、相手の画面にも表示してみましょう。



二つのゲーム画面を同じルームにつなぎます。WASD キーでキャラクターを動かし、スペースキーを押すと両方の画面で同じキャラクターが一瞬大きくなります。

基本編で用意した GS2 の学習用プロジェクトと Unity プロジェクトを使います。このページでは新しい `RealtimePractice` シーンを作り、SDK から接続します。

## 1. リアルタイム用のネームスペースを作る

マネージメントコンソールで学習用プロジェクトの東京リージョンを開きます。

1. **Realtime → ネームスペースリスト → ネームスペースを新規作成**を開きます。
2. 次の値を入力して作成します。

| 項目 | 値 |
| --- | --- |
| 名前 | `adventure-sync` |
| サーバの種類 | オブジェクト同期（`sync`） |
| サーバのスペック | `realtime1.nano` |

## 2. ログインするシーンを作る

1. Unity の **File → New Scene**で空のシーンを作り、`RealtimePractice` として保存します。
2. <a href="/samples/adventure/AdventureLogin.cs" download>AdventureLogin.cs</a> を `Assets` に保存します。同じファイルがすでにある場合は、そのファイルを使います。
3. 空の `Login` オブジェクトを作り、`AdventureLogin` を追加します。
4. マネージメントコンソールの **Deploy → Default → アウトプットリスト**から、`ApplicationClientId` と `ApplicationClientSecret` を **Client Id / Client Secret** に入力します。
5. **Account Namespace** は、Studio でアカウントを作った場合は `Account`、SDK コースの場合は `adventure-account` にします。**Account Slot** は `realtime-a` にします。
6. **Edit → Project Settings → Player → Other Settings → Active Input Handling**を **Both** にします。再起動を求められたら Unity を再起動します。
7. 同じ Player 設定の **Resolution and Presentation → Run In Background**をオンにします。別のウィンドウを操作している間も、ビルドしたアプリで同期を続けるための設定です。

## 3. 同期するキャラクターを作る

次の二つのファイルを `Assets` に保存します。

- <a href="/samples/adventure/realtime/AdventureRoomPlayer.cs" download>AdventureRoomPlayer.cs</a>：移動とスペースキーの入力。
- <a href="/samples/adventure/realtime/AdventureRoomEmote.cs" download>AdventureRoomEmote.cs</a>：全員の画面で実行する動作。

移動処理は Transform を動かします。位置を他の画面へ送る処理は、次の手順で追加する `NetworkTransform` が担当します。

```csharp
var move = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
transform.position += move.normalized * (3f * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space))
    GetComponent<NetworkEntity>().Rpc(nameof(AdventureRoomEmote.Wave), RpcTarget.All);
```

RPC は、指定した処理をほかの参加者の画面でも実行する仕組みです。ここでは `[NetworkRpc]` を付けた `Wave` が、キャラクターを0.3秒だけ大きくします。

```csharp
[NetworkRpc]
public IEnumerator Wave(NetworkRpcInfo info)
{
    transform.localScale = Vector3.one * 1.5f;
    yield return new WaitForSeconds(0.3f);
    transform.localScale = Vector3.one;
}
```

キャラクターごとに操作権を持つプレイヤーを決めます。自分の入力で相手のキャラクターも動かしてしまわないよう、移動処理を **Controlled Behaviours** に登録します。この欄のスクリプトは、操作権を持つ画面でだけ動きます。

1. **GameObject → 3D Object → Capsule**を作り、名前を `NetworkPlayer` にします。
2. 次のコンポーネントを追加します。

| コンポーネント | 設定 |
| --- | --- |
| Network Entity | Schema は空欄。Controlled Behaviours に、このオブジェクトの AdventureRoomPlayer を1件追加 |
| Network Transform | Position Axes は All、Rotation Axes は None |
| Adventure Room Player | 追加したまま使う |
| Adventure Room Emote | 追加したまま使う |

3. `NetworkPlayer` を Project ウィンドウにドラッグして Prefab にします。Hierarchy に残った Capsule は削除します。

## 4. 接続用のオブジェクトを作る

1. Project ウィンドウで **Create → GS2 → Realtime → Prefab Catalog**を選び、`PracticeCatalog` を作ります。
2. **Prefabs**に1件追加し、**Id**を `1`、**Prefab**を `NetworkPlayer` にします。
3. <a href="/samples/adventure/realtime/AdventureRoom.cs" download>AdventureRoom.cs</a> を `Assets` に保存します。
4. 空の `Room` オブジェクトを作って `AdventureRoom` を追加します。`NetworkWorld` と `NetworkSession` も追加されます。
5. **Network World → Prefab Catalog**に `PracticeCatalog`、**Adventure Room → Login**に `Login` オブジェクトの `AdventureLogin` を割り当てます。
6. **GameObject → 3D Object → Plane**で地面を作ります。
7. **GameObject → Camera**でカメラを作り、名前を `Main Camera`、Tag を `MainCamera` にします。位置を `(0, 12, -10)`、回転を `(50, 0, 0)` にします。
8. **GameObject → Light → Directional Light**でライトを作り、回転を `(50, -30, 0)` にします。

接続コードは、ルーム名からIPアドレス・ポート・暗号鍵を取得し、ログインしたプレイヤーのアクセストークンと一緒に渡します。

```csharp
session.Connect(login.Session.AccessToken.Token,
    get.IpAddress, get.Port, get.EncryptionKey);
await UniTask.WaitUntil(
    () => session.State == NetworkSessionState.Ready || session.State == NetworkSessionState.Disconnected,
    cancellationToken: this.GetCancellationTokenOnDestroy());
```

接続が完了すると `Ready` が呼ばれ、Prefab ID `1` のキャラクターを生成します。

```csharp
world.Instantiate(1,
    new Vector3(Random.Range(-3f, 3f), 1, Random.Range(-3f, 3f)),
    Quaternion.identity);
```

## 5. 二つの画面で動かす

1. シーンを保存し、**File → Build Profiles**でデスクトップ向けのビルドを作ります。シーンリストに `RealtimePractice` を入れ、今回はほかのシーンのチェックを外して、`RealtimePractice` だけを有効にします。
2. ビルドしたアプリを起動し、ログイン画面の保存先を `realtime-a` にして **ログイン**を押します。Unity Editor でも再生し、こちらの保存先は `realtime-b` にしてログインします。
3. マネージメントコンソールで `adventure-sync` の **ルームリスト → ルームを作成**を開き、ルーム名を `practice` にして作成します。通知先ユーザーIDリストは空のまま使います。
4. 一覧を更新し、`practice` の情報にIPアドレスとポートが表示されたら、アプリと Unity Editor の両方で、ルーム名 `practice` の **このルームに接続**を押します。
5. 両方に二体のキャラクターが表示されたら、片方のゲーム画面をクリックして WASD キーを押します。同じキャラクターが両方の画面で動きます。
6. スペースキーを押し、両方の画面でそのキャラクターが一瞬大きくなることを確かめます。

## 6. 操作する人を入れ替える

1. Bの画面に表示された **自分の接続 ID** を、Aの **操作権を渡す相手の接続 ID** 欄に入力します。
2. Aが所有するオブジェクトの **このオブジェクトの操作権を渡す**を押します。オブジェクト番号を控えておきます。
3. Aで WASD キーを押しても、そのキャラクターは動かなくなります。Bでは、元から持っていたキャラクターと、受け取ったキャラクターの両方が動きます。
4. Bの入力欄にAの接続IDを入れ、控えたオブジェクト番号の操作権をAへ返します。再び、それぞれ一体ずつ操作できる状態になります。

操作権を渡す呼び出しは `entity.TransferOwnership(相手の接続ID)` です。所有者が変わると、`Controlled Behaviours` の移動スクリプトも、操作できる側で有効になります。

## 7. 切断して接続し直す

Aの **切断**を押します。Bの画面からAのキャラクターが消えます。Aで同じルームへ接続し直すと、新しいキャラクターが現れ、Bのキャラクターは現在の位置に表示されます。

試し終わったら、両方の画面で切断します。マネージメントコンソールの `adventure-sync` のルームリストから、実習で作った `practice` を削除します。

サーバー側で敵のHPや勝敗を管理する実装は、<a href='' target="_blank" rel="noopener">GSLで敵を動かすチュートリアル（別タブ）</a>に、ルール定義からUnityでの操作まで載っています。

[チュートリアルのトップへ戻る]()




