ボイスチャット
ボイスチャットの実装に当たっていろいろなライブラリがありますが、
メジャーどころで情報が多そうなPhotonを使うことにしました。
しかしながらPhoton2のボイスチャットの実装方法に関する記事を
見つけることができませんでした。
公式のドキュメント読めば余裕かな?と思ってましたが、
実際に繋がっているかどうかのテストとかも面倒で意外と時間使ったのでメモします。
下準備
まずは**Photon Voice 2というアセットの導入です。
アセットストアからインポートします。**
PUN2もインポートします。
次にアプリケーションIDを作成する必要があります。
アカウント作成後に下記のようにPhotonVoiceという種類の
アプリケーションを作成します。

設定
下記パスのPhotonServerSettingsのアプリケーションIDを設定します。
Assets\Photon\PhotonUnityNetworking\Resources\PhotonServerSettings
先ほど作成したPhotonVoiceという種類のアプリケーションIDを入力します。
次にHierarchyに必要なコンポーネントを用意します。
PhotonVoiceNetworkとRecorderが必要です。
適当なオブジェクトにアタッチして、
赤枠で囲った箇所が合ってればだいたい問題ないです。
AvatarというPrefabをアタッチしている箇所にはPhotonNetwork経由で生成する
アバター(同期オブジェクト)を設定しています。
【参考リンク】:【Unity(C#),PUN2】OculusQuestのハンドトラッキング同期実装
Avatar側にも設定が必要なので見ていきます。
SpeakerとPhotonVoiceViewが必要です。
AudioSourceは自動で追加されます。
赤枠で囲った箇所を設定します。
SpeakerInUseは自身のオブジェクトからアタッチします。
これでおおよその設定が完了しました。
音声を口の動きに反映、通信同期
球体が頭部で口は独立したオブジェクトとして頭部の子階層に配置されています。
NormcoreというVR/ARの通信同期実装のためのライブラリから拝借しました。
今回はこの口のオブジェクトを声に合わせてパクパクさせたいと思います。
もともとNormcoreのライブラリで口をパクパクさせるコードが
ドキュメントに公開されていますのでそれをPUN2用に利用してみます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Normal.Realtime;
public class MouthMove : MonoBehaviour {
public Transform mouth;
private RealtimeAvatarVoice _voice;
private float _mouthSize;
void Awake() {
// Get a reference to the RealtimeAvatarVoice component
_voice = GetComponent<RealtimeAvatarVoice>();
}
void Update() {
// Use the current voice volume (a value between 0 - 1) to calculate the target mouth size (between 0.1 and 1.0)
float targetMouthSize = Mathf.Lerp(0.1f, 1.0f, _voice.voiceVolume);
// Animate the mouth size towards the target mouth size to keep the open / close animation smooth
_mouthSize = Mathf.Lerp(_mouthSize, targetMouthSize, 30.0f * Time.deltaTime);
// Apply the mouth size to the scale of the mouth geometry
Vector3 localScale = mouth.localScale;
localScale.y = _mouthSize;
mouth.localScale = localScale;
}
}
コード
実際のコード全文が下記です。
using Photon.Pun;
using Photon.Voice.PUN;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
/// <summary>
/// しゃべると口が動く機能
/// </summary>
public class MouthSyncVoice : MonoBehaviourPun
{
[SerializeField] private Transform _mouth;
private PhotonVoiceView _voice;
private float _mouthSize;
void Start()
{
if (photonView.IsMine)
{
_voice = GetComponent<PhotonVoiceView>();
_voice.RecorderInUse.TransmitEnabled = true;
this.UpdateAsObservable()
.Subscribe(_ =>
{
//口のオブジェクトのY軸のスケールをLerpで滑らかに動かす
float targetMouthSize = Mathf.Lerp(0.1f, 1.0f,100 * _voice.RecorderInUse.LevelMeter.CurrentAvgAmp);
_mouthSize = Mathf.Lerp(_mouthSize, targetMouthSize, 30.0f * Time.deltaTime);
//口の動きを同期通信させる
photonView.RPC(nameof(SyncMouth),RpcTarget.All,_mouthSize);
})
.AddTo(this);
}
}
/// <summary>
/// 口の動きの変化を送信
/// </summary>
/// <param name="mouthSize">口の大きさ</param>
[PunRPC]
private void SyncMouth(float mouthSize)
{
Vector3 localScale = _mouth.localScale;
localScale.y = mouthSize;
_mouth.localScale = localScale;
}
}
_voice.RecorderInUse.TransmitEnabled = true;
の箇所で音声のやり取りを開始しています。
_voice.RecorderInUse.LevelMeter.CurrentAvgAmp
で直前の0.5秒間の平均した音の波形を取得しています。
この波形を利用して口のオブジェクトのY軸方向のスケールを変化させています。
最後のSyncMouth
メソッドの中で口のオブジェクトの大きさの変化を双方のクライアントで同期しています。
デモ
GIFなので音はありませんが、相手の音がHMDから聞こえてきて
口がパクパクしているのを確認できました。
最後に
繋がったり、繋がらなかったり、音声が極端に小さかったりと
不具合まみれだったので原因がわかり次第追記していこうと思います。