WebGL(by Unity)×Agora Web SDK(WebRTC)で音声通話アプリの開発

ブラウザ用メタバースアプリ開発でよく利用されるWebGLとAgora Web SDKを組み合わせて簡易な音声通話アプリを作ったので備忘録として記事にします。

## 環境
- Unity 2021.20f1
- Agora Web SDK 4.16.1
- VScode

## 実際の画面
![スクリーンショット 0005-03-09 15.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/7829a2a4-757c-6979-f202-5613eec9ddfb.png)
Joinボタンをクリックすると通話開始するシンプルなアプリです。
他拠点の入室があればそのユーザーIDがInformationに表示されます。

## 利用する技術
### グラフィック部分(WebGL)
Unityで作成します。ButtonオブジェクトとTextオブジェクトを配置しました。
### 通話部分(JavaScript)
Agora Web SDKを使ってJavaScriptで実装しています。
### グラフィックと通話の連携
WebGL側からJavaScriptへの呼び出しはjslib経由で行います。
JavaScript側からWebGLへの呼び出しはunityInstance.SendMessage()で行います。
詳細はこちらのUnityの[ドキュメント](https://docs.unity3d.com/ja/2018.4/Manual/webgl-interactingwithbrowserscripting.html)をご参照ください。

## 実装の詳細
### WebGL側からJavaScriptへの呼び出し
ButtonオブジェクトにはC#スクリプトを割り当てています。クリック時にJavaScriptを呼び出すようにしています。
jslibを利用する為にAssets/Plugins以下にrtc.jslibファイルを作成しました。
```javascript:Assets/Plugins/rtc.jslib
mergeInto(LibraryManager.library, {
  CallExternalJS: function () {
    join();//JavaScript側で定義した関数
  },
});
```
C#スクリプト側の実装は以下の通り。
```c#:ButtionScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class ButtonScript : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void CallExternalJS();
    Button button;

    // 入室処理
    public void Join()
    {
        CallExternalJS();
    }

    void Start()
    {
        button = GetComponent<Button>();
        button.onClick.AddListener(Join);
    }

}
```
この段階で、UnityエディタでWebGLアプリケーションとしてビルドします。生成されたフォルダにJavaScriptを追加していきます。
![スクリーンショット 0005-03-09 15.44.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/d10d9005-885c-0674-45da-bd275be32bb9.png)
今回はjsフォルダを作成し、配下にAgora Web SDKとrtc.jsを配置しました。
```html:index.html
    <script src="js/AgoraRTC_N-4.16.1.js"></script>
    <script src="js/rtc.js"></script>
```
JavaScript側の実装です。AgoraのSDKに含まれているサンプルをほぼ利用します。
```javascript:rtc.js
var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
var localTracks = {
  audioTrack: null
};
var remoteUsers = {};
var options = {
  appid: YOUR APP ID,
  channel: YOUR CHANNEL ID,
  uid: null,
  token: null
};

async function join() {
  client.on("user-published", handleUserPublished);
  client.on("user-unpublished", handleUserUnpublished);
  [ options.uid, localTracks.audioTrack] = await Promise.all([
    client.join(options.appid, options.channel, options.token || null),
    AgoraRTC.createMicrophoneAudioTrack()
  ]);
  await client.publish(Object.values(localTracks));
}

async function subscribe(user, mediaType) {
  const uid = user.uid;
  await client.subscribe(user, mediaType);
  if (mediaType === 'audio') {
    user.audioTrack.play();
  }
}

function handleUserPublished(user, mediaType) {
  const id = user.uid;
  remoteUsers[id] = user;
  subscribe(user, mediaType);
}

function handleUserUnpublished(user) {
  const id = user.uid;
  delete remoteUsers[id];
}
```
WebGL側からJavaScriptの呼び出しはこれで完了です。ButtonクリックでAgoraの音声通話に参加できます。

### JavaScript側からWebGLへの呼び出し
他拠点のユーザーが入室した事を知らせる為にメッセージをWebGL内に表示させます。
まずは、unityInstance.SendMessage()を利用するためにindex.htmlを改修します。
```javascript:index.html
      var myGameInstance = null; //追記
      var script = document.createElement("script");
      script.src = loaderUrl;
      script.onload = () => {
        createUnityInstance(canvas, config, (progress) => {
          progressBarFull.style.width = 100 * progress + "%";
        }).then((unityInstance) => {
          myGameInstance = unityInstance; //追記
          loadingBar.style.display = "none";
          fullscreenButton.onclick = () => {
            unityInstance.SetFullscreen(1);
          };
        }).catch((message) => {
          alert(message);
        });
      };
```
次にAgora Web SDKで他拠点入室があった際のイベントにWebGLの呼び出しを実装します。

```javascript:rtc.js

async function subscribe(user, mediaType) {
  const uid = user.uid;
  await client.subscribe(user, mediaType);
  console.log("subscribe success");
  if (mediaType === 'audio') {
    user.audioTrack.play();
  }
  var message = "User join:"+user.uid;//追記
  callWebGL(message);//追記
}

function callWebGL(message) {//追記
  myGameInstance.SendMessage('Text', 'SetText', message);
}
```
最後にUnityのC#スクリプト側の実装です。
Textオブジェクトに以下のスクリプトを割り当ててます。テキストを更新するだけのシンプルな実装です。
```c#:TextScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class TextScript : MonoBehaviour
{
    [SerializeField]
    TextMeshProUGUI informationText;
    void Start()
    {
    }
    void Update()
    {        
    }
    void SetText(string text){
        informationText.text = text;
    }
}
```
他拠点の入室の際、ユーザーIDが表示されます。
![スクリーンショット 0005-03-09 15.59.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/fffc6bd0-c00c-9940-fae5-a40d14cbb777.png)

今回の実装は非常に簡単なものですがWebGLとJavaScript間の連携についてご参考になるかと思います。

## 最後に
[**agora.ioに関するお問い合わせはこちらから**](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=5fa671beba7202bd730a)
[<img width="912" alt="Agoraの詳細はこちら" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/5865441c-9d31-c702-3698-3aeb84927f28.png">](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=5fa671beba7202bd730a)