# Tutorial 4 - Room

このチュートリアルでは、Diarkis の **Room** 機能を実装します。Room はゲームセッションの基本単位であり、プレイヤーをグループ化してメッセージの交換を可能にします。マッチメイキング後に移行する先として一般的に使われます。

終了時には以下のことが身についています:

* `DiarkisInterface` から `DiarkisRoom` モジュールを取得する方法
* `SendJoinRandomRoom` でランダムなルームに参加（なければ作成）する方法
* `OnRoomCreation` と `OnRoomJoin` を別々に処理する方法
* ルーム内全員にブロードキャストメッセージを送る方法
* `GetRoomMembers()` でメンバーリストを最新状態に保つ方法
* ルームから退出する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### Room とは

Room は最大人数と TTL（有効時間、秒単位）を持つ仮想空間です。メンバー同士が自由にメッセージを交換できます。

```mermaid
flowchart LR
    A[Client A] -- join --> R[(Room)]
    B[Client B] -- join --> R
    C[Client C] -- join --> R
    R -- Broadcast --> A
    R -- Broadcast --> B
    R -- Broadcast --> C
```

`SendJoinRandomRoom()` は参加可能なルームがあればそこに入室し、なければ新規作成します。結果は `OnRoomCreation`（新規作成）または `OnRoomJoin`（既存参加）で届きます。

```mermaid
flowchart TD
    S([Join Room]) --> J[SendJoinRandomRoom]
    J --> C{Room available?}
    C -->|no| CR[OnRoomCreation]
    C -->|yes| JE[OnRoomJoin]
    CR --> IN[In room]
    JE --> IN
    IN --> BC[SendBroadcastToRoom]
    BC --> RCV[OnRoomMemberBroadcast]
    IN --> LV([Leave Room])
    LV --> EV[OnRoomLeave]
```

### シーンのセットアップ

`Tutorials/Scenes/Tutorial4-Room.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

Play モードに入るとシーンが自動接続します。**Network State** ラベルが緑色になったら **Join Room** ボタンが有効になります。

### コードの解説

#### Room モジュールの取得

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
_room = diarkis.Room;
```

Tutorial 2・3 の `diarkis.MatchMaker` と同じパターンです。

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnRoomCreation(OnRoomCreation, this);
handler.OnRoomJoin(OnRoomJoin, this);
handler.OnRoomMemberJoin(_ => RefreshRoomUI(), this);
handler.OnRoomMemberLeave(_ => RefreshRoomUI(), this);
handler.OnRoomLeave(_ => OnRoomLeave(), this);
handler.OnRoomMemberBroadcast(OnRoomMemberBroadcast, this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント                    | 発火タイミング             | コールバックシグネチャ                               |
| ----------------------- | ------------------- | ----------------------------------------- |
| `OnRoomCreation`        | 新規ルームを作成した（最初のメンバー） | `Action<DiarkisRoomCreationEventArgs>`    |
| `OnRoomJoin`            | 既存ルームに参加した          | `Action<DiarkisRoomJoinEventArgs>`        |
| `OnRoomMemberJoin`      | 他のメンバーがルームに参加した     | `Action<DiarkisRoomMemberJoinEventArgs>`  |
| `OnRoomMemberLeave`     | メンバーがルームから退出した      | `Action<DiarkisRoomMemberLeaveEventArgs>` |
| `OnRoomLeave`           | 自分がルームから退出した        | `Action<DiarkisRoomLeaveEventArgs>`       |
| `OnRoomMemberBroadcast` | ブロードキャストメッセージを受信した  | `Action<DiarkisPayloadEventArgs>`         |

`OnDestroy` での `UnregisterCallbacks(this)` はお忘れなく。オーナーパターンの詳細は Tutorial 1 を参照してください。

#### SendJoinRandomRoom()

```csharp
private void OnJoinClicked()
{
    _room.SendJoinRandomRoom(
        maxMembers: 4,
        ttl: 120,      // ルームの有効時間（秒）
        interval: 100, // オブジェクト同期の送信間隔（ミリ秒）
        allowEmpty: true);
}
```

`allowEmpty: true` にすると、空のルームにも参加します。結果は `OnRoomCreation` または `OnRoomJoin` で届きます。

#### OnRoomCreation と OnRoomJoin の違い

発火状況と取得できるデータが異なります。

```csharp
private void OnRoomCreation(DiarkisRoomCreationEventArgs args)
{
    if (!args.IsSuccess()) { /* エラー処理 */ return; }

    // args.GetRoomID() を使う — この時点では _room.RoomID がまだ未設定の場合がある
    string roomID = args.GetRoomID();
    if (_roomIDText != null)   _roomIDText.text   = roomID;
    if (_ownerUIDText != null) _ownerUIDText.text = GetMyUID(); // 自分がオーナー
}

private void OnRoomJoin(DiarkisRoomJoinEventArgs args)
{
    if (!args.IsSuccess()) { /* エラー処理 */ return; }

    // _room.RoomID と _room.OwnerUID はここで利用可能
    if (_roomIDText != null)   _roomIDText.text   = _room.RoomID;
    if (_ownerUIDText != null) _ownerUIDText.text = _room.OwnerUID;
}
```

> **注意:** `OnRoomCreation` 内では `_room.RoomID` ではなく `args.GetRoomID()` でルーム ID を取得してください。コールバック時点でモジュールの内部状態がまだ更新されていない場合があります。

#### メンバーリストの更新

`DiarkisMatchMaker` と異なり、`DiarkisRoom` には `GetRoomMembers()` というローカルキャッシュから即時にメンバー一覧を返すメソッドがあります。サーバーへのリクエストは不要です。

```csharp
private void RefreshRoomUI()
{
    if (_memberListText == null) return;

    DiarkisStringVector members = _room.GetRoomMembers();
    _memberListText.text = members != null && members.Count > 0
        ? string.Join("\n", members)
        : "";
}
```

`OnRoomMemberJoin`・`OnRoomMemberLeave`・`OnRoomJoin`・`OnRoomCreation` から `RefreshRoomUI()` を呼ぶことで表示を常に最新に保ちます。

#### ブロードキャスト — 送信と受信

`SendBroadcastToRoom` は送信者**以外**の全メンバーにメッセージを届けます。送信者自身の画面に表示するには `AddChatLine` を直接呼びます。ペイロードに UID を含めることで受信側が送信者を識別できます。

```csharp
// 送信側
private void OnMessageClicked(string message)
{
    string myUID = GetMyUID();
    byte[] payload = Encoding.UTF8.GetBytes($"{myUID}:{message}");
    _room.SendBroadcastToRoom(payload, reliable: true);
    AddChatLine($"[自分] {message}"); // ローカル表示（ブロードキャストは自分に届かない）
}

// 受信側
private void OnRoomMemberBroadcast(DiarkisPayloadEventArgs args)
{
    DiarkisByteVector payload = args.GetPayload();
    // バイト列をデコード...
    string raw = Encoding.UTF8.GetString(bytes);
    int sep = raw.IndexOf(':');
    string senderUID = raw[..sep];
    string message   = raw[(sep + 1)..];
    AddChatLine($"[{senderUID}] {message}");
}
```

`reliable: true` は TCP 相当の信頼性保証付き送信です。位置同期など多少のロストが許容できる高頻度更新には `false` を使います。

#### ボタン状態の管理

```csharp
private void RefreshButtons()
{
    bool inRoom = _room != null && _room.IsJoin();

    if (_joinButton)      _joinButton.interactable      = !inRoom && _connected;
    if (_leaveButton)     _leaveButton.interactable     = inRoom;
    if (_messageButton1) _messageButton1.interactable  = inRoom;
    if (_messageButton2) _messageButton2.interactable  = inRoom;
    if (_messageButton3) _messageButton3.interactable  = inRoom;
}
```

`_room.IsJoin()` が「現在ルームに参加しているか」の唯一の判断基準です。

### Room と MatchMaker を組み合わせる

マッチメイキング後にプレイヤーを Room に移行するのが一般的な本番フローです。

```mermaid
flowchart LR
    MM["MatchMaker<br/>(Tutorial 2 / 3)"] -->|マッチング成立| RM["Room<br/>(Tutorial 4)"]
```

Tutorial 1〜4 が完了すれば、接続 → マッチメイキング → ルーム参加という完全なフローを実装できます。Tutorial 5 では **Direct Messaging** — UID を指定した特定プレイヤーへのメッセージ送信 — を学びます。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.diarkis.io/diarkis-client/chtoriaru/unity-chtoriaru/tutorial-4-room.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
