【Unity/Photon Voice2】ボイスチャットの実装（ルーム・チャンネルを分けて）

# はじめに
この記事はUnityでボイスチャットを導入し、なおかつチャンネルを分けたい、例えばAチームはAチームの会話のみ話せ、Bチームの会話は入ってこない、というような実装をします。

サンプルとなるプロジェクトです。
https://github.com/konbraphat51/PhotonVoiceChannelSperated
細かい部分は本記事とは異なりますが、本筋はだいたい同じです。
Photon IDを空欄にしていますので、後述するID2種をご自分で調達して登録してください。

:::note warn
Photon Voiceでは古い記事の実装がうまく通用しなくなったケースがいくらか見られ、この記事もいずれ劣化するかもしれません。この記事は<strong>2022年8月</strong>の成功例としてご認識ください
:::

# Photon Voice 2の導入
[こちらのアセットストア](https://assetstore.unity.com/packages/tools/audio/photon-voice-2-130518?locale=ja-JP)から導入してください。
もしPhotonやったことのある方がいれば、PUN2も導入しないといけないのでは？と思うかもしれませんが、Photon voice 2にはPUN2も含まれていますため<strong>PUN2を混入しないでください。</strong> [公式](https://doc.photonengine.com/ja-jp/voice/v2/getting-started/voice-intro)もそう言ってます。

# Photonの登録
Photonはサーバーサービスですので、アプリの登録をしないといけません。
[Photonのページ](https://dashboard.photonengine.com/ja-JP/)からユーザー登録、そしてRealtime、Voiceの<strong>2つのアプリ</strong>を登録してください。

# Photonの設定
Unity画面上部のWindow->Photon Unity Networking->Highlight Server SettingsからUnity側のPhoton設定にアクセスできます。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/f91f548c-de42-1e63-51fa-0baf20c52be5.png)

` App ID PUN `にRealtimeの方のアプリIDを、
` App ID Voice `にVoiceの方のアプリIDをコピペ。
` Fixed Region `をjpにするのが定番となっています。

# 今からやること
本格的な実装に入っていきます。
仕組みとしましては、 まず
・PhotonManager
・VoiceManager
・スピーカー
の三つのオブジェクトがあります。

PunManagerは、最初にサーバーへの接続する役割を担います。

VoiceManagerは画面の前のあなたの声を収録し、それをネットワークを通じて他人のスピーカーへ送り届けます。

スピーカーは、実際に受信した音を発します。ここで注意したいのが、最初（実行する前）スピーカーはいません。プレハブという形にして、PhotonにInstantiateしてもらう形になります。

# PunManager
空オブジェクトに、下記のスクリプトをはっつけてください。
大筋の流れは[@UpAllNightさんの超分かりやすい記事](https://qiita.com/UpAllNight/items/43e1b24301eb6029f18b)を参考にさせて頂きました。
```C#:PunManager.cs 
using UnityEngine;
using System.Collections;
using Photon.Pun;
using Photon.Realtime;

public class PunManager : MonoBehaviourPunCallbacks
{
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings();
    }

    void OnGUI()
    {
        GUILayout.Label(PhotonNetwork.NetworkClientState.ToString());
    }

    public override void OnConnectedToMaster()
    {
        PhotonNetwork.JoinOrCreateRoom("room", new RoomOptions(), TypedLobby.Default);
    }

    //ルームに入室後に呼び出される
    public override void OnJoinedRoom()
    {
        PhotonNetwork.Instantiate("Speaker", Vector3.zero, Quaternion.identity, 0);
    }
}
```
これのおかげで、まずサーバーに接続され、Speaker(後述）が生成されます。

# スピーカーオブジェクト
空オブジェクトに、`Speaker` `Photon Voice View`をアタッチしてください。すると`Audio Souce`も勝手に追加されますが削除しないでください。
下図のような感じで設定していれば大丈夫でしょう。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/ae356ac4-1795-8549-acea-0295b9171e3f.png)
これをプレハブにし、プレハブ名を「Speaker」にして
Assets -> Photon -> Resourcesに保存してください。
ここの原理は[こちらの記事](https://qiita.com/UpAllNight/items/43e1b24301eb6029f18b)が超分かりやすいです。

# VoiceManager
空オブジェクトに、`Photon Voice NetWork` `Recorder`コンポーネントをアタッチしてください。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/f57e08b0-5ee2-8c32-b5eb-b1b7097f0b16.png)
こんな感じに設定してみてください。<strong>画像はPlayerプレハブになっていますが、Speaker Prefabには先ほど作ったスピーカーをアタッチしてください。</strong>
ちなみに筆者はTransmit Enableにチェックを付けていなくて詰んでいました。

# 第一弾（ルーム分けなし）、完成
これでまず、第一弾完成です。試しにビルドして二つ同時に実行すると、自分のマイクの音が返ってくることがわかると思います。

# チャンネル分け機能の実装
所属チームなどによってチャンネルを分け、チームごとに分けたボイスチャットをするまで、あと一息です。
先ほどのVoiceManagerに下記のスクリプトをアタッチしてみてください。

```C#:VoiceTeamSwitch.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Voice.PUN;
using Photon.Voice.Unity;

public class VoiceTeamSwitch : MonoBehaviour
{
    PhotonVoiceNetwork voiceNetwork;
    Recorder recorder;
    // Start is called before the first frame update
    void Start()
    {
        voiceNetwork = GetComponent<PhotonVoiceNetwork>();
        recorder = GetComponent<Recorder>();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            byte[] remove = new byte[0];
            byte[] add = new byte[] { 1 };
            //チャンネル変更部分
            voiceNetwork.Client.OpChangeGroups(remove, add);
            recorder.InterestGroup = 1;
            //チャンネル変更部分ここまで
            Debug.Log(1);
        }
        if (Input.GetKeyDown(KeyCode.W))
        {
            byte[] remove = new byte[0];
            byte[] add = new byte[] { 2 };
            voiceNetwork.Client.OpChangeGroups(remove, add);
            recorder.InterestGroup = 2;
            Debug.Log(2);
        }
    }
}
```
上記のコードで、Qキーを押すとチャンネル１に、Wキーを押すとチャンネル２に切り替えられます。
注意点として、デフォルトはチャンネル0ですが、<strong>チャンネル0は「全員」に聞こえるようになっています</strong>。よって他チャンネルのボイスを聞こえさせたくない場合は、まずチャンネル0から抜けさせる必要があります。

また、
` voiceNetwork.Client.OpChangeGroups(remove, add) ` は「受信」するチャンネル
` recorder.InterestGroup `　は「送信」するチャンネル
です。
「送信」は１チャンネルのみですが、「受信」に関しては複数チャンネル可能です。
第一引数は抜けるチャンネルを、第二は参加するチャンネルを指定します。
ここにbyte[0]を指定すると、「全チャンネル」から脱退・参加
nullをしているすると「何もしない」になります。