視聴者が「次のステージを投票」できるゲーム動画配信システムを作る

# ゲームの配信動画をインタラクティブなコンテンツにする

ゲーム実況などの動画配信は、視聴者にとって基本的には受動的なコンテンツです。視聴者コメントで配信者とコミュニケーションを行うことはできますが、見ているゲームの世界に対しては干渉できません。
こうした動画配信において、サーバーミドルウェア「Genvid」はゲームの動画配信をインタラクティブにするアプローチが可能となります。

今回は、対戦ゲームなどにおいて「動画の視聴者が投票によって次のステージを選択できる」システムを試作しました。
たとえば4人対戦のアクションゲームやバトロワ系などにおいて、配信者がどんなステージに挑戦するか？といったことを視聴者に問うことができます。
シンプルですが、以下のデモを作りました。

![Genvid Overlay - Google Chrome 2022_06_23 1_46_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/2ce6b474-7ab4-acb3-c6a9-38dc7152f4e3.png)

これはブラウザでゲームからのストリーミング動画を再生してる状態です（クラウドゲーミングではなく、単なる動画です）。

ゲーム側では、以下が描画されています。

![NVIDIA GeForce Overlay DT 2022-06-22 15.59.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/561ec88f-498d-f011-a6eb-799098847668.png)

これを動画としてストリーミング配信し、ブラウザで視聴します。
その際、動画の上から以下のボタン類を重ねて表示させます。

![Genvid Overlay - Google Chrome 2022-06-22 15.59.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/a1e118bd-4e2a-ce5c-eca1-00697defa787.png)

これによって、動画と視聴者のインタラクションが可能なシステムを構築しています。
なお、動画サイトTwitchであれば、こうしたボタンや文字などのHTML要素を動画の上から重ねる「Twitch Extensions」を利用することができます。
ExtensionsはTwitchへの事前審査が必要ですが、これに通ればTwitch公式サイト上で誰でも視聴者専用UIにアクセスできるようになります。

Twitch Extensions
https://www.twitch.tv/p/ja-jp/extensions/

ゲーム配信動画の視聴者連携については、コメントやコメントにPrefixをつける方法がありますが、コメント欄が見づらくなり、大量の視聴者が投票したときにゲーム側の負荷が高くなる欠点があります。
今回はGenvidを使って、「ゲーム動画の上にクリック可能なボタンを表示する」ことと、「サーバー上で投票データをマージしてゲーム側へのインパクトを減らす」アプローチを紹介します。

# Genvidについて
Genvidは、インタラクティブなライブ配信を実現するSDKです。
https://www.genvidtech.com/ja/

Genvidはクラウドサーバー上で動かすサーバミドルウェアと、ブラウザ側で動画と同期した通信処理を行うJavaScriptライブラリで構成されています。
jsライブラリが動作できる環境であれば配信アプリやプラットフォームは問わず使用でき、主にFacebookとTwitch上で使用されています。

最近では、Facebook Gamingで配信されている『PAC-MAN COMMUNITY』のなかで、視聴者をゲームに招待できるゲーム配信者向けの機能「Play with Streamer」で使用されています。
https://www.facebook.com/fbgaminghome/blog/pac-man-community

> また、このゲームにはFacebook Interactivesを利用した[Watch]タブが用意されています。このタブでは、Facebook Gamingストリーマーによるゲームのライブ配信を楽しめます。Watchモードでは、迷路がUnreal Engineを利用した3Dストリーミングに変わり、視聴者は動画プレイヤーと直接対話してどちらの側に付くかを決めたり、AI PAC-MANやゴーストをパワーアップさせて競い合わせることができます。

このタイトルではブラウザ上のゲームはHTMLで動作し、Watchタブで表示される動画はサーバー上でUnreal Engine 4を使って描画されています。
そのほかの事例として、Unityを使いFacebook上で配信されている「[Rival Peak](https://news.yahoo.co.jp/byline/onokenji/20210326-00229353)」と、UE4を使った「[Project Raven](https://www.youtube.com/watch?v=8mu5G8qtafA)」があります。

今回のデモではGenvid SDK for Unityを使っていますが、Unreal Engine 4でも利用可能です。
Genvidのシステムは、AWSまたはAzureのWindowsクラウドサーバー上で動作させ、動画データをTwitchに送信します。また、動画視聴者のブラウザに対してゲーム内のデータをブロードキャストします。
Genvidは「Windowsクラウドサーバー上でゲームのexeを動作させる」システムなので、開発環境はWindows 10または11となります。

# 解説範囲について
Genvid SDKの開発環境構築については、Genvidディベロッパーサイトの日本語マニュアルからご確認ください。
https://www.genvidtech.com/for-developers/

SDK同梱の「Cube」サンプル、およびAsset Storeで配布している「Tanks」サンプルで、どのようにGenvidのシステムを利用するかが理解できます。
https://assetstore.unity.com/packages/templates/tutorials/genvidtanks-sample-161598

以降のガイドは、「Cube」サンプルのUnity版の動作が確認できた環境にて、そのサンプルを改造する形で実装していきます。
また、Genvidはクラウドサーバーを活用して配信システムを構築するSDKですが、今回はゲームとブラウザ側のシステムのみを作るため、サービス構築については省略しています。
AWSとAzureに対応していますので、試作を作ってうまく行きそうであればマニュアルの配信ガイドをご確認ください。

配信ガイド
https://www.genvidtech.com/doc/ja/SDK-1.36.0/broadcasting_guide.html

## Genvid Eventについて
今回紹介するGenvid SDK側で使用する機能は「Genvid Event」と呼ばれるものになります。
Eventが「視聴者のブラウザからゲームへ」通信を行う部分です。以下の記事にて詳細を紹介しています。
https://qiita.com/Takaaki_Ichijo/items/2d4a3fc29c79d2bb6cb0

今回は動画視聴者からゲームへ「どのステージに投票するか」というデータを送信します。

## ブラウザ側の実装
動画を視聴するウェブサイトで表示するボタンなどのパーツは、一般的な動的Webページ同様、html, javascript, cssファイルで構成します。
Genvidシステムとの通信を行うjsからはGenvid SDKに含まれるjsライブラリ(genvid.umd.js)を使用します。
genvid.umd.jsはGenvidインストールフォルダのapi\web\distにあります。

ブラウザ側実装に必要な最低限の要素は
-index.htm
-style.css
-overlay.js
-genvid.umd.js

の4点です。

### index.html
htmlにはビデオの表示と、その上に重ねて表示するボタンとラベル、ラベル表示の下地になるcanvas要素を配置します。
今回は投票を行う3種類のボタン配置となります。

```index.html
<!doctype html>
<html>

<head>
    <title>Genvid Overlay</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div id="video_player"></div>

    <button class="button desert" type="button" id="desert">Vote Desert</button>
    <button class="button ice" type="button" id="ice"> Vote Ice </button>
    <button class="button forest" type="button" id="forest">Vote Forest</button>

    <script src="genvid.umd.js"></script>
    <script src="overlay.js"></script>
</body>

</html>
```

### style.css
cssファイルにはボタン、ラベル、canvasの設定をしていきます。
ポイントはpositionにabsolute属性を指定することで、これによってビデオの上に各要素を重ねて表示させます。
投票ボタンはゲーム側のUIの位置に合わせたいため、横並びになるようleftの値を各ボタンで変更します。

```style.css
body {
    background-color: black;
}
.button {
    cursor: pointer;
    position: absolute;
    display:inline-block;
    padding: 1.5vw;
    opacity: 0.7;
    border-radius: 8px;
    top: 35vw;
    width: 15vw;
    color:white;
    font-size: 1.2em;
}

.button:active {
    opacity: 1;
}

.button.desert {
    background-color: rgb(220, 192, 11);
    left: 12.5vw;
}

.button.ice {
    background-color: rgb(131, 198, 245);
    left: 42.5vw;
}

.button.forest {
    background-color: rgb(73, 159, 73);
    left: 72.5vw;
}

.button:disabled {
    background-color: gray;
    color:black;
}


```

完成系は以下の通りです。
![Genvid Overlay - Google Chrome 2022-06-22 15.59.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/a1e118bd-4e2a-ce5c-eca1-00697defa787.png)

### overlay.js
Genvidとの通信とCanvasの表示などを制御します。
まず冒頭でhtmlのボタンの参照をbuttonElements配列に保存しておき、genvidの初期化処理を書いています。

```overlay.js
var genvidClient;

var buttonElements = document.querySelectorAll("[type=button]");

fetch("/api/public/channels/join", { method: "post" })
    .then(function (data) { return data.json() })
    .then(function (response) {
        genvidClient = genvid.createGenvidClient(response.info, response.uri, response.token, "video_player");
        genvidClient.start();
    })
    .catch(function (e) { console.log(e) })
```

jsの後半では、投票ボタンにGenvidを介したデータ送信の処理を紐づけています。
配列buttonElementsに格納されたボタンへ、クリックしたときにどんなGenvid Eventを送信するかを設定していきます。
buttonElementにはhtml側でidとしてステージ名の「desert」「ice」「forest」が指定してあります。
keyが「vote」、valueが「desert」など、ステージ名を格納したデータをイベントとして送信します。


```overlay.js
Array.prototype.forEach.call(buttonElements, function(buttonElement) {

    buttonElement.onclick = function () {

    genvidClient.sendEvent([{
        "key": ["vote"],
        "value":  buttonElement.id
    }])
}
});
```

## Unity側の実装
UIオブジェクトの配置やテキストの表示については一般的な話のため、省略します。
Genvidとの連携に必要なスクリプトとして、動画サイトから届いたデータをゲーム側で受け取るためのスクリプトを作成します。
ブラウザ側のoverlay.jsで、keyが「vote」、valueが「desert」のデータを送信する処理を書きました。
こんどはそれをUnity側で受信した時にどんなメソッドを呼ぶかを指定していきます。

voteというキーのデータがきたとき、その値のstringを調べて、まずは現在の投票数をフィールドに格納し、その後UIの表示に反映します。

```Main.cs
public class Main : MonoBehaviour
{
    public TMP_Text desertStageVoteCount, iceStageVoteCount, forestStageVoteCount;
    private int desertStageVote, iceStageVote, forestStageVote;

    void Start()
    {
        Clear();
    }
    
    public void SetVotePoint (string eventId, GenvidSDK.EventResult[] results, int numResult, IntPtr userData)
    {
        Debug.Log("points "+results[0].key.fields[0]);
        string voteStageName = results[0].key.fields[0];
        int point = (int)results[0].values[0].value;

        switch (voteStageName)
        {
            case "desert":
                desertStageVote += point;
                desertStageVoteCount.text = desertStageVote.ToString();
                break;
            case "ice":
                iceStageVote += point;
                iceStageVoteCount.text = iceStageVote.ToString();
                break;
            case "forest":
                forestStageVote += point;
                forestStageVoteCount.text = forestStageVote.ToString();
                break;
        }
    }

    void Clear()
    {
        desertStageVoteCount.text = "0";
        iceStageVoteCount.text = "0";
        forestStageVoteCount.text = "0";
    }
    
}
```

Unityのヒエラルキー上では、この「vote」関数がEventが届いた時に呼ばれるよう、Genvid Eventのインスタンスのインスペクターにアタッチします。

![GenvidEventVoteSample - SampleScene - PC, Mac & Li.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/7c98afbf-585c-4c7e-61b9-2ef42d0fcc3f.png)


## Genvid Cluster用の設定
Genvid Eventでは、大量の視聴者から一斉にデータが届いた時のために、クラウドサーバー上でデータ量を小さくしてからゲームに渡す仕組みを持ちます。
簡単に言えば、「キャラクターＡに投票」というデータが1万人の視聴者から届いた時、「キャラクターＡに１万票」という1つのデータにまとめてしまうような処理です。
このまとめる処理の設定を、Genvidではjsonスキーマとして記述し、サーバーに読み込ませます。
詳しくは以下の記事をご参照ください。

https://qiita.com/Takaaki_Ichijo/items/33e46d7849107f0bcfa7

このデモの場合は、「vote」というデータをどのように減らすかについて設定します。

"maps"の部分では、キーがvoteでvalueがstringのデータであることを定義し、"reductions"の部分ではこのデータをどのようにマージするかを指定します。
今回のケースでは、大量の視聴者から投票でたーた「Forestに1票」「Iceに1票」…というデータが来た時、
250ミリ秒に1回のペースでマージを行い、「Forestに25票」「Iceに513票」などのデータ形式に加工したうえでゲーム側に渡します。

```events.json

{
  "version": "1.7.0",
  "event": {
    "game": {
          "maps": [
            {
              "id": "vote",
              "source": "userinput",
              "where": {
                "key": [
                  "vote"
                ],
                "name": "<name>",
                "type": "string"
              },
              "key": [
                "vote",
                "<name>"
              ],
              "value": 1
            }
          ],
          "reductions": [
            {
              "id": "vote",
              "where": {
                "key": [
                  "vote",
                  "<name>"
                ]
              },
              "key": [
                "<name>"
              ],
              "value": [
                "$sum"
              ],
              "period": 250
            }
          ]
    }
  }
}


```


## 動作テスト（ローカル実行）
以上のスクリプトはオーバーレイ部分と、Unityからデータを送信する部分、eventの設定ファイルのみの例です。
Genvidサーバーを動作させるには、クラウドサーバー用意してGenvidシステムを動作させるための各種設定ファイルが必要です。
クラウドサーバーを使わず、PCの中でテスト実行する場合は、SDKに同梱されている「cube」サンプルから設定ファイルを流用することで動作させることができます。

ローカルクラスタの開始
https://www.genvidtech.com/doc/ja/SDK-1.32.0/development_guide/game_integration/utilisation.html

 - Genvid操作用pythonスクリプトのgenvid-toolboxのインストール
 - Genvid bastionのローカルPCでのインストール
 - システム環境変数「GENVID_STATIC_BINDING」をtrueに設定してポートを3000に固定
 - 作業用ディレクトリを作り、{任意のディレクトリ}/publicに作成したhtml,css,jsとgenvid.umd.jsを配置
 - /app/BuildにUnityからビルドしたデータ一式を配置
 - 「cube」サンプルからbackendフォルダ、configフォルダ、templatesフォルダ、package.json、Web.pyをコピーして作業用ディレクトリに配置
 - configフォルダのうちevents.jsonをこの記事のevents.jsonに置き換える
 - templates\local\unity.nomad.tmplの「unity_Cube.exe」をビルドしたexe名に変更
 - web.pyから
```web.py
        dict(name="stream", required=True),
        dict(name="web", required=True),
```
を削除
 - コマンドプロンプトやPowerShellで作業ディレクトリに移動してnpm install
 - Genvid Bastionを立ち上げてpy web.py loadコマンドで設定一式を読み込む

以上の手続きでローカル実行が可能です。

# 今後の拡張

今回はブラウザからデータを送る部分に絞って紹介しましたが、現在の要素のままでは1視聴者が何回も投票できてしまいます。
1回投票したらボタンを不活性化させる、というやり方では、視聴を開始してから1回しか投票できません。そこで、ゲーム側から動画のタイムコードと同期する形で「ゲームのステート情報」をデータ配信し、
ブラウザ側でそのデータに応じてボタンの活性化・不活性化を操作できるように成します。
1回の試合が終わったら「試合終了」ステートをゲームから発行し、ブラウザ側のボタンはそのステートで1回だけ投票ができるようにボタンを操作可能に切り替える、押されたら不活性化する、という処理です。
これらの処理は、Genvid Streamという機能を使うため、別途の記事にて紹介します。