//tips
//unity音声コミュニケーション理解
VivoxVoiceManagerの中身を確認していく。
列挙でオプションの選択を可能にした後にParticipantValueChangedHandlerを作っている。
public delegate void ParticipantValueChangedHandler(string username, ChannelId channel, bool value);
public event ParticipantValueChangedHandler OnSpeechDetectedEvent;
ParticipantValueChangedHandlerデリゲートの宣言を行なっている。
delegate 戻り値の型 デリゲート名 (関数が引き受ける仮引数);
event は、一般的なコールバックを 、delegate の機能を使って実現する。
delegate型の変数を宣言する際, アクセス修飾子に event をつけると他のオブジェクトから変数へアクセス(+= or -=)はできるが実行はできなくなり、他の想定してない場所から呼ばれる事を防ぐことができるよう。
また、eventを使うことで、複数のコールバックを登録できるようになる。
下記、イベント発生元とイベント処理実行を分けて考えることで、デリゲートの挿入処理という機能理解がうまくできるようになった。
// コールバックテスト
public class MyCallbackTest : MonoBehaviour {
// スタート時に呼ばれる
void Start () {
処理クラスの呼び出し
MyProcess myProcess = new MyProcess();
処理内容のCompleteHandlerに追加する
myProcess.CompleteHandler += MyCallbackMethod;
処理の実行
myProcess.ExeMyProcess();
}
// コールバック時に呼ばれる
public void MyCallbackMethod(string result) {
Debug.Log("処理完了 : " + result);
}
}
// 処理クラス
public class MyProcess {
resultを持ったCompleteHandlerイベントを作っている
public delegate void OnCompleteDelegate(string result);
public event OnCompleteDelegate CompleteHandler;
// 処理実行
public void ExeMyProcess() {
// 処理実行
Debug.Log("処理実行");
// コールバック実行
CompleteHandler?.Invoke("成功");
}
}
デリゲートは差し込み処理と考えるとわかりやすくなり、ParticipantValueChangedHandler OnSpeechDetectedEventは音声が聞こえた時に音声認識処理を発生させたいためにデリゲートをさせているのではないかと考えられるようになった。
https://note.com/npaka/n/n988adf1ecdb4
https://loumo.jp/archives/5309#event
https://qiita.com/kamanii24/items/b1e93b2d36a26c06530e
https://qiita.com/hibara/items/9fd56a5d594c000a5df0
https://csharp.keicode.com/basic/events-delegate-problems.php
https://itthestudy.com/c-sharp-event-handler/
_serverの部分にuriをセットすれば、文字列に直してセットしてくれるメソッドを作っている。
private Uri _serverUri
{
get => new Uri(_server);
set
{
_server = value.ToString();
}
}
VivoxUnityの型を使用。
private Client _client = new Client();
private AccountId _accountId;
下記を実行することで、もし存在していなければVivoxVoiceManagerによりシングルトンオブジェクトを作成し、そこにvivoxvoicemanagerスクリプトをアタッチする。
シングルトンパターンで実装されたクラスは、その仕様として実行時に一つしかインスタンスを作ることができないように設計され、プログラム上では常に同一のインスタンスを参照するように強制する。
今回はVivoxVoiceManagerオブジェクトをヒエラルキーに作成し、そこにコンポーネントをすでにアタッチしていたのでそれがm_Instance部分として認識され代入されている。
DontDestroyOnLoadで指定し、シーン遷移してもオブジェクトは削除されず残り続けるようにしている。
public static VivoxVoiceManager Instance
{
get
{
lock (m_Lock)
{
if (m_Instance == null)
{
// Search for existing instance.
m_Instance = (VivoxVoiceManager)FindObjectOfType(typeof(VivoxVoiceManager));
// Create new instance if one doesn't already exist.
if (m_Instance == null)
{
// Need to create a new GameObject to attach the singleton to.
var singletonObject = new GameObject();
m_Instance = singletonObject.AddComponent<VivoxVoiceManager>();
singletonObject.name = typeof(VivoxVoiceManager).ToString() + " (Singleton)";
}
}
// Make instance persistent even if its already in the scene
DontDestroyOnLoad(m_Instance.gameObject);
return m_Instance;
}
}
}
//?.Invoke()という書き方
?はnull条件演算子といい、? をつけたオブジェクトがnullならnullを、nullでなければ、続きのコードを実行した結果を返すもので。
?.Invokeという書き方は、 ? の前のオブジェクトがnullではなければ、Invokeで渡したメソッドを、メインスレッド(UIスレッド)で実行する、ということを意味している。
この書き方は、たいてい[イベント]?.Invoke([デリゲート])のかたちで使われ、イベントが起きたら、UIに結果を反映する。
//$”stringの内容”
str.Contains(target)は、 文字列strの中に特定の文字列targetが含まれているかを調べる方法で、$ 特殊文字を使用することで、変数を文字列への挿入文言として組み込むことができるようになる。
string name = "Mark";
var date = DateTime.Now;
// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.
わかりやすいシンプルな例が下記。
public class ContainTest : MonoBehaviour
{
private void Start()
{
string str = "目の前にスライムが現れた。";
string target = "スライム";
if (str.Contains(target))
{
Debug.Log($"{target}が文章の中に含まれていました。");
}
else
{
Debug.Log($"{target}は文章の中に含まれていません。");
}
}
}
https://qiita.com/Nossa/items/c2226232b31d7665267f
段々C#のそもそもの知識が必要とされるようになってきた。
Namespace vivoxunityに書かれている内容をオリジナルでかけるようになると、かなり独自のシステム作りに近づけそう。
VivoxVoiceManagerはvivoxunityのメソッドなどをベースに書かれているものなので、シンプルな動作内容しか書かれておらず、その動作内容を構成する背景はvivoxunityの方にある。
例えば、VivoxVoiceManagerのメソッドpublic IChannelSession TransmittingSessionのgetされた場合のreturnである_client.GetLoginSession(_accountId).ChannelSessions.FirstOrDefault(x => x.IsTransmitting);はVivoxUnityname spaceに下記のように定義されており、
public ILoginSession GetLoginSession(AccountId accountId)
{
if (AccountId.IsNullOrEmpty(accountId))
throw new ArgumentNullException(nameof(accountId));
CheckInitialized();
if (_loginSessions.ContainsKey(accountId))
{
return _loginSessions[accountId];
}
var loginSession = new LoginSession(this, accountId);
_loginSessions[accountId] = loginSession;
loginSession.PropertyChanged += delegate (object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName != nameof(loginSession.State)) return;
if (loginSession.State == LoginState.LoggedOut)
_loginSessions.Remove(accountId);
};
return loginSession;
}
これを理解するためのベースとなるILoginSession は別スクリプトなど参照する必要があり、20スクリプト以上に関係箇所が点在しているので、何を行なっているのかをコードから読み解くというのは現実的ではなく、先に何をすべきか知っていてコードを後付けで探すという流れじゃないとやたら時間がかかることがわかった。
仕様書を読んでみる。
//GUIDとは
Globally Unique Identifierの略で、Unity等で広く使われている識別子。128ビットの整数値からなり、データを一意に識別するために用いられる。
時刻やMACアドレスなどを含むデータとして生成され、重複があっては困る状況で利用される。実用上はほぼ、世界中に唯一とみなして扱って困難がないといわれている。
生成はGuid.NewGuidを実行するだけ。
//Guidの構造体生成
Guid guid = Guid.NewGuid();
//生成されたGUIDを確認
Console.WriteLine("GUID : " + guid.ToString());
このように簡単にID生成できる。
//throwについて
throwは、例外が起こったことを知らせるもので、 throw 文は想定外のことが起こった場所に挿入される。throwされると、正常動作部の処理は中断され、例外処理部が呼び出される。
throw 文によって投げられる例外は、 System.Exception クラスの派生クラスのインスタンスとなり、 それ以外のクラスのインスタンスを throw することは出来ない。
//VIVOX仕様書の外観
基本的に着信などincomeに対して、doBind部分のboolとclientの関係で切り替えを行なっているよう。
using UnityEngine;
using VivoxUnity;
class EventExample : MonoBehaviour
{
. . .
// Bind or unbind event handler for Client AudioInputDevices property changes
private void BindHandlers(bool doBind, Client client)
{
if (doBind)
{
client.AudioInputDevices.PropertyChanged += onAudioInputDeviceChanged;
}
else
{
client.AudioInputDevices.PropertyChanged -= onAudioInputDeviceChanged;
}
}
private void onAudioInputDeviceChanged(object sender,System.ComponentModel.PropertyChangedEventArgs propertyChangedEventArgs)
{
. . .
}
. . .
}
これをみるとクライアントのAudioInputDevicesなどに接続する処理とその状態を変化させる処理を行う必要があることがわかる。
またベースとしてVivoxUnityでは非同期処理を実行しているので、コールバックが使われているが、そもそもなぜvivoxで非同期処理が必要となっているのかを考える。コールバックとは、非同期処理として実行したものが完了した際に実行する別の処理のこと。
非同期処理はマルチプレイヤーがいる際に用いられるが、それは処理の優先順位を決めることと、並行して処理を行うことに重きを置いており、
using UnityEngine;
using VivoxUnity;
class AsyncExample : MonoBehaviour
{
. . .
// Example of connecting to a channel
channelSession.BeginConnect(joinAudio, joinText, shouldTransmit, connectToken, ar =>
{
try
{
channelSession.EndConnect(result);
}
catch (Exception e)
{
// Handle error
}
});
. . .
}
チャネルに接続する、オーディオに接続する、テキストに接続するなどの処理を分離させて処理することでいちいち全ての作業が止まらなくてよくなりそう。
確かに、話しながら、文字を打つ場合に、話す音声内容が優先されて断続的に伝え続けられないと非常に不便かもしれない。
このSDKを使うのに必要なものがclientの初期化で、これを行わないと他のサブシステムが使えないと記載されている。
Client _client = new Client();
_client.Initialize();
You must create and initialize the voice client before other SDK actions can take place. During the voice client initialization process, the SDK sets up and starts all of its different subsystems. This can cause additional shared libraries to be loaded, and allows developers to customize a variety of SDK options before connecting to Vivox services.
同時にuninitializeもきちんとしてくださいとのこと。
The game must uninitialize the SDK by calling Client.Uninitialize() before exiting the process or before calling Client.Initialize() again
SDKを初期化した後にやっとプレイヤーが入れるようになり、参加の際にはAccountIdがプレイヤーに割り当てられる。
After you initialize the Vivox SDK, you can sign a user in to the Vivox instance assigned to your game by using an AccountId object. An AccountId is generated with a Vivox access token issuer, a username that is selected by the game, and the host for your Vivox domain.
An AccountId should have one-to-one mapping to a player.
Use the same AccountId for the same player every time they play the game.
AccountIdが作られた後に、ILoginSession.BeginLogin methodが呼ばれ、待機画面のバックグラウンドで非同期の登録処理完了を待つ。待機画面をloading画面などにしたいからだろうか。確かにフリーズ状態だと心配にはなる。
After the AccountId is created, call the ILoginSession.BeginLogin method to sign in to Vivox.
The last argument of ILoginSession.BeginLogin is the AsyncCallback that is called after the sign in is complete.