GMEとUnityでゲーム内ボイスチャットを試す

# はじめに
皆さんこんにちは！ 人生 2回目の記事投稿となる POTIです！
今回は、Qiita Advent Calender 2022 のプレゼントカレンダー に参加しました。
[GMEのボイスチャットやボイスチェンジャーなどをUnreal Engine５・Unityで試してみた by Tencent Advent Calendar 2022](https://qiita.com/advent-calendar/2022/tencentcloud02 "プレゼント欲しい！")

# 使ってみたかった...
先に言います。**私は使えませんでした**
どうやら、使用するには本人確認がいるんですよねぇ...
パスポートも免許証も持っていない私はここで呆気なくダウンです。
しかし一応、アカウント登録までの手順は紹介できるので、実際の手順を、画像を交えて解説し、
その後、Unityデモの起動方法とSDKの使用方法を、*公式ドキュメントの要約* というかたちで解説していきます。
よろしくおねがいします。

# Tencent Cloud の GMEサービスとは
GME とは ***Game Multimedia Engine*** の略で、
ゲーム向けにリアルタイムの音声通信や、リアルタイムの文字起こし等の機能を提供している*Tencent Cloud* のサービスです。

以下は製品紹介の引用です
> *GME* は、ワンストップの音声ソリューションを提供します。
> ゲームシーンに関して、リアルタイム音声・音声メッセージ・ボイスツーテキスト変換などのサービスを提供し、
> FPS・MOBA・MMORPG・オンラインテーブルゲームなど、さまざまなゲームプレイタイプに適用され、
> モバイルゲーム・コンシューマーゲーム・およびブラウザーゲームのプラットフォーム間の相互運用をサポートします。

要するに、様々な機能を、様々なゲーム様式に、様々なプラットフォームで使えますよ。 ということです。
自分でイチから実装しようとすると、ある程度のネットワーク関連の知識や、それを利用するプログラムを習得しなければならないものですが、
提供されているGMEのSDKを利用すると、それが基本的な数行のプログラムだけで実装できてしまうという優れもの！

↓これらがGMEで提供されている主な機能達です
| 機能 | 概要 | ユースケース |
| - | - | - |
| リアルタイム音声 | 多人数音声チャットのリアルタイム通話 | ルーム内でのボイスチャット |
| 音声メッセージ | レコーディングされた音声メッセージをオフラインで一定期間取得できる | 友達チャット |
| ボイスtoテキスト変換 | ボイスファイル・音声メッセージを、最大125言語のテキストに変換 | チャンネルチャット |
| リアルタイムバーチャルヒューマン | カメラ取得の顔表情/モーションキャプチャ、モーションデータと音声ストリームの同期送信、モデルの駆動などの機能 | メタバースゲーム |
| 音声タグ | AIモデルに基づいて、音声データから年齢・性別などのタグを付ける | ユーザープロパティの分析 |
| 音声と映像のレコーディング | 音声と映像をクライアントやサーバーに記録する | コンテンツの作成 |

サポートするプラットフォーム
* Android 6.0 以上
* iOS 9.0 以上
* macOS 10.10 以上
* Windows 2007 / XP / 10 / 11
* Unity 4.7 以上
* Unreal Engine 4.2 / 5.0 以上
* Firefox 67 / Chrome 75 / Safari 12.1 / Edge 18
* Play Station 4 / 5
* Nintendo Switch
* Xbox One / Series
等々...

一般的なゲーム環境は全てサポートしています。

↓詳しい概要は、こちらの公式ドキュメントをご覧ください
[製品概要](https://www.tencentcloud.com/jp/document/product/607/10835)

# アカウントを登録
GMEサービスを使用するためには、もちろんですがアカウントの登録が必要です。
もしかしたら課金が必要なのかなぁ～ と思っていましたが、
どうやら、月間通話時間が **10,000分未満** なら無料枠らしいです。やったね

| 音質 | 月間通話時間 | 単価 ( 米ドル / 分 ) |
| - | - | - |
| 高音質 | 10,000分未満 | 無料 |
| 高音質 | 10,000分以上 | 0.00094 |

:::note info
ちなみに、月間 10,000分 は日換算だと 約 322分 / 日、つまり 1日 5時間ぐらいなら大丈夫ということです。太っ腹！
:::
まず、Tencent Cloud GME の公式サイトにアクセスします。
[Tencent Cloud GME](https://www.tencentcloud.com/products/gme)

アクセスできたら、***無料で始める*** ( ***Get Started Free*** ) ボタンを押して、サインアップします。
![Tencent Cloud GMEの公式サイト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/5f3308f0-fa76-8b6d-7833-123faeb40c1a.jpeg "Tencent Cloud GMEの公式サイト")

すると、サインイン画面にとびますが、まだアカウントを持っていないので ***Sign up*** もしくは ***Sign in with Google*** します。
![Screenshot_2022-12-24_18-30-33.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/a2edbb97-479a-e965-dd10-4ed4a7cd37a7.jpeg)

サインアップ画面になるので、住んでいる国とパスワードを入力して登録を確定します。

:::note info
アカウントタイプ (左側) は、**個人** にします
:::
![Screenshot_2022-12-24_18-30-45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/92af21e1-355f-a562-4c7f-8fc87c15ca31.jpeg)

次は、アカウントの設定になるので、必要な情報を入力して進みます。

:::note info
住所と右側のチェックボックス達の入力は必須ではありません
:::
![Screenshot_2022-12-24_18-32-10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/59579690-147a-1919-39b7-8223b23ec5c7.jpeg)

クレジットカード / デビットカードの情報がきかれるので、これも入力します。
![Screenshot_2022-12-24_19-12-38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/50ac6714-01c5-d0a6-071f-f08d69c66d4b.jpeg)

すると、登録が完了するので、一度 GMEのコンソールに戻ります。
![Screenshot_2022-12-24_20-27-13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/b43fd8ec-7d16-e9e9-6607-cac145c3f462.jpeg)

ここから、***Create now*** を押して、アプリケーションの登録を始めるわけですが、
前述の通り、本人確認が必要になります。

:::note warn
使用にはパスポート・免許証による本人確認が必要
:::
![Screenshot_2022-12-24_20-33-53.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/62202bbb-db07-29b3-3dc4-fbbe26313cfe.jpeg)
![Screenshot_2022-12-24_20-37-59.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/236b02d6-903e-fd3d-9d2c-12d079c628c5.jpeg)

仕方がないので、ここからは他記事や公式ドキュメントから自分なりにまとめていきます。
( 実際に試したわけではないので、手順の正確性は保証できません )

# アプリケーションの登録

まずこちらがアプリケーションの登録画面になります。
入力する項目は以下の通りです。
* アプリケーション名 : 自分の好きな名前にしよう！ ( あとから変えられるはず... )
* Real-time Voice Service : ボイスチャットに必須の機能です。無料枠があるので Enabled にしましょう。
* Voice Messaging Service : 音声を録音しておくやつです。デモで使用しますが、無料ではないので必要な方のみ
* Voice-to-Text : 音声を文字起こししてくれるやつ。同じく必要な方のみ
最後に一番下にチェックをつければ、登録が完了します。

:::note info
ここで登録したアプリケーションのIDを使用してデモを動かします
:::
![Screenshot_2022-12-24_23-31-18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/5598b9a1-8120-bd67-df2e-2dbae0f72a13.jpeg)

# デモを動かす
では早速、デモを動かしていきましょう！

### デモをダウンロードする
まずは肝心のデモ本体が無いと話になりませんので、公式が提供しているUnityのデモをダウンロードします。
以下のサイトから***Unity*** の***Demo Download*** を押すと、直接ダウンロードが始まります。
https://www.tencentcloud.com/document/product/607/18521
![Screenshot_2022-12-25_00-36-35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/ae130059-3653-0cde-2d17-ace58d704d01.jpeg)

### Unityにインポートする
ダウンロードしたZipファイル解凍し、以下のファイル達を Unityのプロジェクトフォルダに直接入れます。
* Assets : Unity用のデモ本体です。この中にSDKやデモシーンなどが入っています。
![Screenshot_2022-12-25_01-11-38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/5d4236a2-354d-7253-1128-b540cdb0d1b2.jpeg)

インポート出来たら、AppIDを適用していきます。
現状では、デモコードのコメントアウトがうまく出来ておらず、コンパイルエラーだらけなので、
正しくIDを入力して直していきます。

テキストエディタで***UserConfig.cs*** を開き、
こちらの画像で選択されている部分 ( 30行あたりの***AppID / AppKey To Replace Here*** の部分 ) を
自分が登録したアプリケーションのIDに書き換えます。
( どうでもいいけどコメント中国語なんですね... せめて英語にして欲しいところ )

:::note warn
*AppKey* は、コンソールにある*Permission Key* のことです
:::

![Screenshot_2022-12-25_00-46-27.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740248/4af4219a-23c6-3721-7a0b-cef86e7e558e.jpeg)

これで、コンパイルエラーがなくなれば成功です。
デモシーンを開いて、再生すれば、入退室・音声の通信がテストできます。
私は実際にデモ動かすことが出来ないので、これ以上説明することが出来ません。
↓詳しい使い方はこちら
[Free Demo | Tencent Cloud](https://www.tencentcloud.com/document/product/607/38535)

# SDKの利用
さて、デモを動かしたところで、次は実際にSDKを使用して自分で実装してみましょう！
ここでは、これらのAPIを解説していきます。

1. GMEの初期化
2. 入退出の通知
3. チャットルームに入る
4. マイクの有効 / 無効
5. スピーカーの有効 / 無効
6. チャットルームから出る

まずは、SDK利用の準備をしましょう。
SDK本体も以下のサイトから***Unity*** の***SDK Download*** を押して、直接ダウンロードします。
ダウンロードできたら解凍し、これらのファイル達をUnityのプロジェクトフォルダにブチ込みます。

* Plugins : SDK本体、これが無いと意味がない
* GMESDK : API達、これが無いと動かせない

無事にインポートできたら、早速プログラムを書いていきます。

### ヘッダファイルのインポート
まずは、SDKのAPIを呼び出せるようにしないといけません。
``` csharp
using TencentMobileGaming;
```
これで、APIが呼び出せるようになりました。

### 1. インスタンスの取得と初期化
```GetInstance()```でインスタンスを取得し、そいつを```Init()```で初期化します。
``` csharp
int ret = ITMGContext.GetInstance().Init (sdkAppID, openID);
```
```sdkAppID```には登録したアプリケーションのIDを、```openID```にはユーザーを表す任意のIDをそれぞれ**文字列で**指定します。

:::note warn
openIDはプロジェクを表すInt64型のIDです。
プロジェクト全体で統一してください。
:::
```Init```関数の定義は以下のようになっています。
``` csharp
//class ITMGContext
public abstract int Init (string sdkAppID, string openID);
```
そして、最終的な初期化のプログラムは以下のようになります。
``` csharp
int ret = ITMGContext.GetInstance().Init (sdkAppID, openID);

// 初期化が成功したかを戻り値で判別
if (ret != QAVError.OK)
{
    Debug.LogError ("SDK Initialization Failed:" + ret);
    return;
}
```

### 2. 入退出のイベント
これらはイベントとして登録できます。
``` csharp
// Unityおなじみのスタート
private void Start()
{

    ~~~

    ITMGContext.GetInstance().OnEnterRoomCompleteEvent += new QAVEnterRoomComplete (OnRoomEnter); // 入室イベント
    ITMGContext.GetInstance().OnExitRoomCompleteEvent += new QAVExitRoomComplete (OnRoomExit); // 退出イベント
}

private void OnRoomEnter (int result, string error_info)
{
    if (result == 0) // 正常に入室できた場合
        Debug.Log ("Entered the Room Successfully!");
    else // 正常に入室できなかった場合
        Debug.LogError ("Error Code : " + result + ", Error message : " + error_info);
}

private void OnRoomExit()
{
    Debug.Log ("Exited the Room Successfully");
}
```
まず、Unityおなじみのスタート関数にイベントの登録を行います。
```QAVEnterRoomComplete```のインスタンスを作成し、引数にコールバックを受け取りたいメソッドを渡しています。
( イベント処理についてはこちらを参考にしてください→ [[C#] イベントの使い方 - デリゲートと何が違う？](https://yaspage.com/cs-event) )


:::note info
イベントの登録は、初期化したGMEインスタンスに対して行うため、
初期化した後に登録する必要があります。
:::

```OnEnterRoomCompleteEvent```の引数には、以下の値が渡されます。
* ```result``` : 正常な場合は 0、エラーならエラーコード
* ```error_info``` : GMEからのエラーメッセージ

```OnExitRoomCompleteEvent```の引数には、渡される引数はありません。

### 3. チャットルームに入る
それでは、ルームに入ってチャットを開始しましょう。
とその前に、まずは部屋に入るための準備をしなければいけません。

#### 認証
入室するためには認証コードが必要なので、ID達を集めて認証コードをもらいましょう。
``` csharp
byte[] authByte = QAVAuthBuffer.GenAuthBuffer (int.Parce(appID), roomID, openID, key);
```
こちらが```GenAuthBuffer```関数の定義です。
``` csharp
QAVAuthBuffer GenAuthBuffer (int appID, string roomID, string openID, string key)
```

渡す引数は以下のようになっています。
* ```appID``` : コンソールで登録したアプリケーションのID
* ```roomID``` : 入室するルームを表す任意の文字列。たぶんルーム名のこと
* ```openID``` : ユーザーを表す任意のID
* ```key``` : 登録したアプリケーションの*Permission Key*

また、戻り値として```bype```型の配列が返されるので、それを変数に格納して保持しておきます。
( 後でルームへの入室に認証コードとして使用します )

#### 入室
やっとルームに入れます。実装していきましょう。
``` csharp
ITMGContext.GetInstance().EnterRoom (roomID, ITMGRoomType.ITMG_ROOM_TYPE_FLUENCY, authByte);
```
関数の定義はこのようになっています
``` csharp
ITMGContext EnterRoom(string roomId, int roomType, byte[] authBuffer)
```
引数には以下を渡します
* ```roomID``` : 入室するルームを表す文字列
* ```roomType``` : ルームの音質を指定する ( *ITMG_ROOM_TYPE_FLUENCY* のみ )
* ```authByte``` : さっき取得した認証コード

### 4. マイクを有効化する
入室しても、デフォルトではマイクは有効ではないので、ボイスチャットをするには有効化する必要があります。
``` csharp
ITMGContext.GetInstance().GetAudioCtrl().EnableMic(true);
```
この文を入室時の登録したイベントコールバック内に追記して、自動で有効化するか、
UI のボタン押下イベントに書くことで、任意に有効化できます。

:::note info
無効化するときは、```EnableMic()``` の引数を```false```に変えます。
:::

### 5. スピーカーを有効化する
スピーカーもマイクと同様、有効化できます。
``` csharp
TMGContext.GetInstance().GetAudioCtrl().EnableSpeaker(true);
```
使い方もマイクと同様です。

### 6. チャットルームから退出する
``` csharp
ITMGContext.GetInstance().ExitRoom();
```
これでキレイに退出ができます。

# まとめ
これで、恐らく一連のGMEを使用する方法は書けたと思います。
正直言ってたぶんこの記事の情報だけでは、正常に動きません。はい。
あくまで公式ドキュメントを自分なりに解釈してまとめただけですので、
正確性や動作の保証はできません。申し訳ないです。
(公式ドキュメントだから大丈夫だと思う...)
この記事を見て、少しでもリアルタイム通信に興味を持ってみていただけたら幸いです。

# 参考
[Quick Integration of SDK for Unity | Tencent Cloud](https://www.tencentcloud.com/document/product/607/44544)
[SDK for Unity - Voice Chat | Tencent Cloud](https://www.tencentcloud.com/document/product/607/15228?lang=en#triggering-event-callback)
[GME VoiceChat と MLAPI ではじめるネットワークゲーム](https://qiita.com/TKTK360/items/d920dc8b9e43f55103d5)

# 終わりに
えー、現在 12/25 20:40 なわけですが、私はクリスマスに何をしているんでしょうか...
別に暇ではないんでね、えぇ。こっちだって自分なりのクリスマスの過ごし方をしているわけですから。えぇ ; ;
( 1日中必死こいて記事を書いているだけですけど )