Unityでボイスチャットを作る話


この記事について

現在Unityで制作しているものにボイスチャット機能をつけようと思って先日いろいろ調べて実装してました。
ただ、調べた段階でクリティカルな記事が見つからなかったので、ボイスチャットを実装したい人向けに助けになれば良いですね。
ボイスチャットは別で(Skypeとかで)いいじゃん。というのも確かにそうなんですけど、それでは表現できないことがあるよねという話を「おまけ」の項でしているのでよろしくお願いします。

必要なもの

・Unity5
(4でもいいかもですが、僕は5で作っているので4での動作は保証しません。)
・PhotonUnityNetwork
(AssetStoreからDLしてください。)
・DFVoice
(UnityのVoIP用Asset 要30ドル。Unity5だとバグが出て使えなかったが、最近改善された。最高。)
https://www.assetstore.unity3d.com/jp/#!/content/19344

この記事はDFVoiceの使い方の記事になりそうです。
How to implement voice chat in Unity using DFVoice
( https://www.youtube.com/watch?t=424&v=yRXYh4i0F0U&noredirect=1 の解説になります。)
但し、この動画は無駄な部分もありますのでそこを上手く取り除いて以下で解説してゆきます。

やること

・Photonサーバに接続した時にInstantiateされる自分のアバターを作る。
・自分の声をパケット化して、それを自分を除いた他のユーザに対してRPCで送るクラスを書いてアバターにAddComponent
・自分の発話を他の人に伝えるためのAudioSourceをAddComponent
・DFVoiceの中にあるUnityAudioPlayerクラスとMicrophoneInputDeviceクラスをAddComponent
以上です。

やることは本当に少なくて、コード書くのもこの4つの中で上2つのみですね。簡単。

Photonサーバに接続した時にInstantiateされる自分のアバターを作る。

NetworkController.cs
using UnityEngine;
using System.Collections;

public class NetworkController : Photon.PunBehaviour
{
    private string _room = "Tutorial_Convrge";
    // Use this for initialization
    void Start ()
    {
        PhotonNetwork.ConnectUsingSettings("0.1");
    }

    public override void OnJoinedLobby()
    {
        Debug.Log("joined lobby");

        RoomOptions roomOptions = new RoomOptions() {};
        PhotonNetwork.JoinOrCreateRoom(_room, roomOptions, TypedLobby.Default);
    }

    public override void OnJoinedRoom()
    {
        PhotonNetwork.Instantiate("NetworkedPlayer", Vector3.zero, Quaternion.identity,0);
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("Connected to Master");
    }

}

このコードでは、Photonサーバにログインした時の処理を書いています。
これはアバターにNetworkedPlayerにつけるのではなくHierarchy上に作成した別の適当なものにAddComponentしておいてください。(空のGameObjectで大丈夫です。)
OnJoinedRoomメソッドによって、Resources/以下にある、「NetworkedPlayer」というPrefabをInstantiateします。
PhotonNetwork.Instantiateにする点に注意。

InstantiateされたアバターはPhotonサーバ上で共有するのでPhotonView Componentを忘れずにつけてください。
ちなみにNetworkedPlayerをこの記事で言う所の「自分のアバター」として扱います。
NetworkedPlayerは空のGameObjectでもCubeでもなんでもいいです。(僕はCubeにしています。)
自分のアバターにボイスチャットのためのComponentをどんどん付与することでボイスチャットを可能にしていきます。

・自分の声をパケット化して、それを自分を除いた他のユーザに対してRPCで送るクラスを書いてアバターにAddComponent

MyLocalVoiceController.cs
using UnityEngine;
using System.Collections;

using DaikonForge.VoIP;
using System;

public class MyLocalVoiceController : VoiceControllerBase
{

    private DateTime lastTalking = DateTime.Now.AddMinutes(-1);

    public override bool IsLocal
    {
        get { return GetComponent<PhotonView>.isMine; }
    }

    protected override void OnAudioDataEncoded(VoicePacketWrapper encodedFrame)
    {
        byte[] headers = encodedFrame.ObtainHeaders();
        GetComponent<PhotonView>().RPC("vc", PhotonTargets.Others, headers, encodedFrame.RawData);
        encodedFrame.ReleaseHeaders();

        lastTalking = DateTime.Now;
    }

    [PunRPC]
    void vc(byte[] headers, byte[] rawData)
    {
        if (!GetComponent<AudioSource>().enabled) return;

        VoicePacketWrapper packet = new VoicePacketWrapper(headers, rawData);
        ReceiveAudioData(packet);
    }

    public bool isTalking()
    {
        return DateTime.Now.CompareTo(lastTalking.AddMilliseconds(100)) == -1;
    }
}

マイクに対して発話するとOnAudioDataEncodedが呼ばれるっぽい動きをしてる気がします。
で、PunRPC属性がついたVCメソッドをリモートユーザに呼ばせています。その際に音声のヘッダと音声データを渡しています。

このメソッドはこの通り書けば問題ないと思います。
OnAudioDataEncodedメソッド内でGetComponentが都度呼ばれているのが気になる人は何か工夫をするといいかもです。

自分の発話を他の人に伝えるためのAudioSourceをAddComponent

この通りにやります。

DFVoiceの中にあるUnityAudioPlayerクラスとMicrophoneInputDeviceクラスをAddComponent

この通りにやります。
AudioSourceからマイク入力の音声を発するためこれは必要です。

UnityAudioPlayerクラスのフィールドである、「Is Three Dimensional」にチェックをつけておくとユーザ同士の3次元の距離によって音声が減衰するやつになるのかな。凄い。

MicrophoneInputDeviceクラスのフィールドである「Push To Talk」については、
None:ゲームが開始されて何もせずにボイスチャットできる。
何かキーを設定する:そのキーを押している間だけほかの人にボイスを送信する。
という感じになってます。


以上のことをやるとこんな感じのInspectorになってるはずです。

以上でBuildしてみて、確かめてください。ちゃんとボイスチャットできるようになっているはずです。簡単すぎでしょ。

もし出来なかった場合は動画のほうを参考にするといいかもです。Part2だけでも理解できると思いますが、個人的にはPart1から見ることをおすすめします。

おまけ

この共有されたボイスをリアルタイムにピッチを上げたり下げして、テレビでよく見る「※プライバシーのため音声を変えています」のアレを表現することもできます。

やること:
・AudioMixerで音声のピッチを上げる処理をするMix処理を設定する。
・それをNetworkedPlayerにAddされていたAduioSourceのOutputフィールドに適用する。

AudioMixerで音声のピッチを上げる処理をするMix処理を設定する。
AudioMixerとは:
>オーディオミキサーはUnity5から追加された機能です。この機能で複数のAudioSourceのボリュームを一括制御/Offにしたり、オーディオグループにエフェクトを付与するといった事が可能になりました
(引用元:http://tsubakit1.hateblo.jp/entry/2015/05/20/233000 )

ということです。

本当に命名が適当で申し訳ないのですが、PPPというMixerを作って、そのGroupの中にtestというMix処理を設定しています。
設定のやり方は、
・Groupsの横の+ボタンを押してMix処理を追加する。
・作った処理をクリックして、Inspectorを見る。
・Add EffectというのがあるのでそこからPitch Shifterを選択する。
・あとはPitchを1.25xにするなど自由に設定する。

それをNetworkedPlayerにAddされていたAduioSourceのOutputフィールドに適用する。

下の方にOutputというフィールドがあるのでここに先ほど作成したMix処理をドラッグアンドドロップしてください。


以上でピッチ変更ボイスを共有する事もできるようになったと思います。

このリアルタイムMix処理edボイスの共有について、Effectはローパスフィルターとかエコーとかもあるのでいろいろ遊びがいはありそうですよね。
パっと思いついたのだと、誰かと洞窟ステージを一緒に攻略している時は声が反射してエコーみたいなことになったり、ゲーム内で電波が悪い場所ではトランシーバー経由のボイスにノイズ走らせたりとか。
Mix処理さえ作っておけばOutputにそれを動的に適用するだけなので、「特定の場所に入ったら」をトリガーにして処理をとっかえひっかえすると良さそうですね。

こういうのはゲーム走らせておいて、別プロセスでSkypeなどの既存サービスを使ってボイスチャットをするーなんてことだと絶対に表現できない部分なのでこれから僕がオンラインのVRゲーム作る際はこの点を工夫して見ようかなと思います。
いろいろEffectはあるので皆さんが僕以上に新しい表現を作ってくれることを楽しみにしてます。

以上です。