ゲームのライブ配信動画で「データ放送」を行う
ゲームのライブ動画(実況配信)では、プレイヤーがスクリーンに表示している映像がそのままストリーミング動画として配信されます。
そのため、対戦ゲームにおける戦績表やキャラクターのビルド情報などは、プレイヤーがメニュー画面を開いた時や、対戦が終わった時だけ表示されることになります。
配信を途中から見始めた動画視聴者は、「この大会の戦績は今どうなっているんだろう?」「このプレイヤーのキャラビルドはどうなんだろう?」と思っても、配信者がその画面を表示しない限りは分かりません。
ライブ動画はゲームプレイのキャプチャですから、当たり前といえばその通りです。しかし、動画を再生しているブラウザ上で、上から文字や画像を重ねて情報を表示できるとしたらどうでしょうか?
簡単なデモを作りました。
このデモは、カプセル君が剣で刺しあうだけの非常にシンプルなゲームです。Unityで動作しており、その様子をキャプチャして、ストリーミング動画としてブラウザで視聴しています。
左上の「Show Record」ボタンはブラウザ上で描画されているものです。
動画視聴者は、このボタンを好きなときにクリックして、ゲームの情報を閲覧できます。このデモではプレイヤー同士が相手を攻撃した回数をリアルタイムで配信しています。
これを拡張すると、動画視聴者が好きな時に戦績を見たり、キャラクターのビルドを見たり、その他ゲーム内部のリアルタイム情報を自由に閲覧できる「インタラクティブなゲームの動画配信」が実現できます。
本投稿では「Genvid SDK」を使い、ゲームのライブ動画に視聴者だけが見れる「戦績」を重ねて表示する方法について紹介します。
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」と、UE4を使った「Project Raven」があります。
今回のデモではGenvid SDK for Unityを使っていますが、Unreal Engine 4でも利用可能です。
Genvidのシステムは、AWSまたはAzureのWindowsクラウドサーバー上で動作させ、動画データをTwitchに送信します。また、動画視聴者のブラウザに対してゲーム内のデータをブロードキャストします。
Windowsを使う理由はここでゲームのexeも動作させるからです。GenvidSDKがサーバー上で動画のエンコードとTwitchへの送信、動画視聴者のブラウザへのデータ配信や、逆にブラウザからのデータを収集を行い、ゲームexeに渡します。
プレイヤーが遊ぶゲームは、ローカルのPCやスマホ、ゲーム機などです。Windowsサーバー上のゲームとは、マルチプレイヤーと同じ仕組みで接続します。P2PでもPhoton Cloudのようなサービスでもかまいません。
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
Genvid Streamについて
今回紹介するGenvid SDK側で使用する機能は「Genvid Stream」と呼ばれるものになります。
以前書いた以下の投稿でも「Genvid Stream」を使用しています。
GenvidでUnityからストリーミング動画と同期したデータ配信を行う
https://qiita.com/Takaaki_Ichijo/items/4fbc70e7efdbdef85459
この例では、プレイヤーの位置データを動画と一緒に配信して、ブラウザ上でプレイヤーの位置と同期したラベルを出しています。
デモ
カプセル君が剣で刺しあうだけです。UIは何もありませんが、それぞれのカプセル君は攻撃がヒットした回数を内部的に保持しています。
その内部データを動画と一緒にデータ配信し、ブラウザ上で表示させます。
ブラウザ側の実装
動画視聴サイトは一般的な動的Webページ同様、html, javascript, cssファイルで構成します。
TwitchでGenvidを使用する場合は、「Twitch Extensions」と呼ばれる仕組みを使って実装します。
それぞれのファイルとGenvidの通信モジュールであるgenvid.umd.jsは同一ディレクトリにあるものとします。
(genvid.umd.jsはGenvidインストールフォルダのapi\web\distにあります。)
index.html
htmlにはビデオの表示と、その上に重ねて表示するボタンとラベル、ラベル表示の下地になるcanvas要素を配置します。
"record_area"ブロック単位で表示・非表示を管理します。
<!doctype html>
<html>
<head>
<title>Genvid Overlay</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="overlay-layer">
<div id="video_player"></div>
<div id="record_area">
<canvas id="record"></canvas>
<div class="label" id="player1_score">Player1:10</div>
<div class="label" id="player2_score">Player2:10</div>
</div>
<div class="button" id="show_record">Show Record</div>
</div>
<script src="genvid.umd.js"></script>
<script src="overlay.js"></script>
</body>
</html>
style.css
cssファイルにはボタン、ラベル、canvasの設定をしていきます。
ポイントはpositionにabsolute属性を指定することで、これによってビデオの上に各要素を重ねて表示させます。
body {
background-color: black;
}
.button {
cursor: pointer;
position: absolute;
top: 10px;
left:10px;
padding: 4px;
opacity: 0.7;
border-radius: 8px;
background-color:greenyellow;
font-size: medium;
}
.button:active {
opacity: 1;
}
.label {
position: absolute;
top:50px;
left:10px;
font-size: xx-large;
margin-top: 0px;
}
.overlay-layer {
position: relative;
}
.overlay-layer canvas {
position: absolute;
top: 0;
left: 0;
}
overlay.js
Genvidとの通信とCanvasの表示などを制御します。
まずは、canvasの描画とボタンとラベルの設定です。
canvasをfillStyleで灰色の半透明色に塗りつぶし、サイズをウィンドウの横幅基準で調整します。
また、プレイヤーの成績を表示するラベルの要素を取得し、位置調整をします。
最後にrecord_areaブロックをいったん非表示にし、ボタンのクリックで表示・非表示をスイッチさせます。
var canvas = document.getElementById('record');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.fillStyle = "rgba(" + [200, 200, 200, 0.7] + ")";
ctx.fillRect(10, 10, window.innerWidth * 0.6, window.innerWidth * 1.6);
}
var player1_score = document.getElementById('player1_score');
var player2_score = document.getElementById('player2_score');
player2_score.style.marginTop = "40px";
var menu = document.getElementById('record_area');
menu.hidden = true;
var button = document.getElementById('show_record');
button.addEventListener("click", function () {
menu.hidden = !menu.hidden;
})
その後、Genvidの初期化とラベルの書き換え処理を指定します。
genvidClientを作成し、onStreamsReceivedでゲームからJSONデータが送信されてきた際にパースを行い、onDraw(描画更新タイミング)でdraw関数をコールします。
draw関数では、先ほど取得したラベルの参照に対して文字を書き換える処理を行っています。
var genvidClient;
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.onStreamsReceived(function (dataStreams) {
for (let stream of [...dataStreams.streams, ...dataStreams.annotations]) {
for (let frame of stream.frames) {
try {
frame.user = JSON.parse(frame.data);
}
catch (e) {
console.log(e, frame.data);
}
}
}
});
genvidClient.onDraw(function (frame) {
let gameDataFrame = frame.streams["GameRecord"];
if (gameDataFrame && gameDataFrame.user) {
draw(gameDataFrame.user);
}
});
genvidClient.start();
})
.catch(function (e) { console.log(e) });
function draw(gameData)
{
player1_score.innerText = "Player1: " + gameData.player1Score;
player2_score.innerText = "Player2: " + gameData.player2Score;
}
Unity側の実装
カプセルのキャラクター制御はNavMeshAgentで適当に動かしているだけなので、説明は省略します。
プレイヤーの点数を管理する「PlayerScore」クラスのパブリックフィールド「Score」に攻撃を当てた回数を保持しているものとします。
public class MatchController : MonoBehaviour
{
public List<PlayerScore> playerScoreList = new List<PlayerScore>();
public void SubmitGameRecord(string streamId)
{
if (GenvidSessionManager.IsInitialized && GenvidSessionManager.Instance.enabled)
{
GameRecord gameRecord = new GameRecord () {
player1Score = playerScoreList[0].Score,
player2Score = playerScoreList[1].Score,
};
GenvidSessionManager.Instance.Session.Streams.SubmitGameDataJSON (streamId, gameRecord);
}
}
[System.Serializable]
public struct GameRecord
{
[SerializeField]
public int player1Score, player2Score;
}
}
SubmitGameRecordメソッドは、GenvidのGenvidStreamsインスタンスから呼び出すものです。GameRecord構造体を経由してJSONに変換し、サーバーへ送信します。
サーバーは、タイムコードを加えてブラウザへ動画と共にデータを配信します。
シーンに配置したGenvidStreamsプレハブで、ID「GameRecord」を作成し、On Submit Streamのリスト経由でSubmitGameRecordメソッドを呼ぶように設定します。
動作テスト
以上のスクリプトはオーバーレイ部分と、Unityからデータを送信する部分のみの例です。
Genvidサーバーを動作させるには、webサーバーを立てたり、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のインストール
- システム環境変数「GENVID_STATIC_BINDING」をtrueに設定してポート固定
- 作業用ディレクトリを作り、/publicに作成したhtml,css,js,genvid.umd.jsを配置
- /app/BuildにUnityからビルドしたデータ一式を配置
- 「cube」サンプルからbackendフォルダ、configフォルダ、templatesフォルダ、package.json、Web.pyをコピーして作業用ディレクトリに配置
- templates\local\unity.nomad.tmplの「unity_Cube.exe」をビルドしたexe名に変更
- web.pyから
dict(name="stream", required=True),
dict(name="web", required=True),
を削除
- コマンドプロンプトやPowerShellで作業ディレクトリに移動してnpm install
応用
今回はゲームからデータを配信し、ブラウザ上で表示させるものでした。Genvidでは、動画視聴者の入力を収集し、ゲーム側に渡すこともできます。
これらを組み合わせることによって、ゲームの動画配信をインタラクティブなコンテンツに昇華することが可能です。