ゲーム配信を見ながら次のステージを作って送り付ける
昨今のアクションゲームなどには、ブロックのようなパーツを組み合わせてステージを作成し、インターネットを介して自作ステージを交換できるものが多くあります。一番有名なのは「マリオメーカー」でしょうか。
個人的にはネット登場以前ですが、「天誅 忍凱旋」のメモリーカード物理やりとりが一番思い入れ深いです。
ゲーム実況文化においても、ゲームの「ステージエディット」機能を使って、動画配信者がファンによるステージに挑戦する「視聴者参加型」配信があると思います。自分が考えたステージを遊んでリアクションしてもらえる!というのはファンにとって非常にうれしいことです。
しかしながら、ステージ制作とその共有機能は、当然ながらゲームソフトの1機能です。
配信の視聴とは別に、事前にゲームを立ち上げて作り込みをやらなくてはなりませんし、ファンの中には経済的な理由などでゲームソフトを購入できない方もいるかもしれません。
そこで、「動画実況を見ているウェブサイト上でステージエディットを実装してしまう」という手法を実験してみました。
左がUnity製のゲームで、右がブラウザで表示しているストリーミング動画+HTML+JavaScriptです。
ゲームを動画としてキャプチャし、ストリーミング動画としてブラウザに流しながら、上からボタン要素を重ねます。
ゲーム動画の上に5x5のグリッド上に配置したボタンを重ねています。このボタンは、ゲーム内で同様に5x5で配置された柱と対応しています。
視聴者がブラウザ上でボタンをクリックすると、「None」と「Pillar」でトグルします。Send Dataボタンを押すと、
ゲーム側にデータが送信され、Pillarと書いてあるボタンの配置が柱としてゲーム内に描画されます。
ゲーム画面に重ねているボタンだけを抜き出すと次のようになります。
本実験では、動画の上にボタンを表示させつつ、ボタン入力結果を受信するサーバーを立ててゲームと通信をさせることで、動画側のボタン操作結果をゲーム側に反映させています。
ボタンの表示は、ゲームのライブ配信プラットフォーム「Twitch」であれば実際のゲーム配信として使用できます。
Twitchは動画の上からボタンや文字を重ねる仕組みを開発者へ提供しています(残念ながらYouTubeにはありません…)。
また、実験ではPCに各ソフトウェアをインストールして実行しますが、実際に配信を行う場合は通信機構などをクラウドサーバー(AWSまたはAzure)で実行する必要があります。
後述するソフトウェアを使えばデプロイ手順はあまり複雑にはなりませんが、バックエンドの知識が少し必要です。
使用する技術の紹介
実験で使用した技術を紹介します。
Twitch Extensions
動画サイトTwitchには、ボタンや文字などのHTML要素を動画の上から重ねる「Twitch Extensions」を利用できます。
Twitch Extensions
https://www.twitch.tv/p/ja-jp/extensions/
ゲーム配信動画の視聴者連携については、コメント欄をコマンドのように使う手法があります。ただし、そのやり方は直感的な操作ではない上、コメント欄が見づらくなってしまいます。
Twitch Extensionsを使うことで、視聴者専用のUIを動画の上から重ねて表示させることができます。
また、外部のサーバーと通信してデータのやり取りもできます。今回のデモでは、視聴者によるデータの送信を外部サーバーで受けて、ゲームに渡す仕組みをとります。
なお、Extensionsの利用には、開発したUI一式をTwitchへ提出し、審査を受ける必要があります。
審査に通ればTwitch公式サイト上で誰でも視聴者専用UIが使えます。
ブラウザ上でのステージエディット機能の事例
実は、このポストで触れている「動画視聴者がステージを作る」というアプローチに似たものは前例があります。
Facebook Gamingで配信されている『PAC-MAN COMMUNITY』では、視聴者をゲームに招待できるゲーム配信者向けの機能「Play with Streamer」があります。
また、このゲームにはFacebook Interactivesを利用した[Watch]タブが用意されています。このタブでは、Facebook Gamingストリーマーによるゲームのライブ配信を楽しめます。Watchモードでは、迷路がUnreal Engineを利用した3Dストリーミングに変わり、視聴者は動画プレイヤーと直接対話してどちらの側に付くかを決めたり、AI PAC-MANやゴーストをパワーアップさせて競い合わせることができます。
このタイトルではブラウザ上のゲームはHTMLで動作し、ゲーム内の「Watch」タブで表示される動画はサーバー上でUnreal Engine 4を使って描画されています。
開発には、後述する「Genvid」が使用されています。
Genvid
動画配信サイトとゲームの通信には「Genvid」を使用します。
Genvidは、インタラクティブなライブ配信を実現するSDKです。
Genvid
https://www.genvidtech.com/ja/
Genvidは3つのパートで構成されています。
- クラウドサーバー上で動かすサーバミドルウェア
- ブラウザ側で動画と同期した通信処理を行うJavaScriptライブラリ
- ゲーム開発環境用のライブラリ
現在は、主にFacebook GamingとTwitch上で使用されています。
動画配信サイト側に特に縛りは無いので、サイト上でJavaScriptモジュールの動作と外部サーバーとの通信が許可されていれば、配信アプリやプラットフォームは問わず使用できます。
逆に言えばYouTubeはこうしたUIを重ねる仕組みが無いので、別途Webサイトを立てて動画埋め込みをするなどの工夫が必要です。
なお、今回のデモではゲーム側の構築にGenvid SDK for Unityを使っていますが、Unreal Engineでも利用可能です。
Genvidのシステムは、プレイヤーがPCなどで遊ぶゲームとは別に、クラウドサーバー上でゲームを動作させて動画データをTwitchに送信します。また、動画視聴者のブラウザに対してゲーム内のデータをブロードキャストします。
※図はCEDEC2022の筆者による登壇「配信者 vs 視聴者多数!ライブ動画配信プラットフォームを活用した、非対称マルチプレイを実現するシステムの構築」より。https://cedil.cesa.or.jp/cedil_sessions/view/2523
クラウドサーバー上で「観戦者モード」的に動作するゲームがあることで、ブラウザ側とゲーム内部の情報をタイムコード情報付きで送受信できます。
これにより、「ゲームの内容と同期した視聴者UIの構築」が可能です。
ゲームの内容でUIを動かす側については今回の投稿では触れませんが、以下の記事で実装解説をしています。
もう一つの大きなメリットとしては、大量の視聴者がいた際のデータ受信の処理です。
Genvidのシステムでは受信部分のサーバーインスタンスを増やしておくことで、数千、数万の視聴者からの通信をさばくことができます。
なお、Genvidは「Windowsクラウドサーバー上でゲームのexeを動作させる」システムなので、開発環境はWindows 10または11となります。
解説範囲について
まず、本手法を試す場合はGenvidの環境構築が必要です。
Genvid SDKを使った開発環境の構築については、Genvidディベロッパーサイトの日本語マニュアルからご確認ください。
https://www.genvidtech.com/ja/%e9%96%8b%e7%99%ba%e8%80%85%e7%94%a8/
SDK同梱の「Cube」サンプル、およびAsset Storeで配布している「Tanks」サンプルで、どのようにGenvidのシステムを利用するかが理解できます。
https://assetstore.unity.com/packages/templates/tutorials/genvidtanks-sample-161598
以降の実装ガイドは、Genvid SDKのインストールと、「Cube」サンプルのUnity版の動作が確認できた環境の上で、そのサンプルコードを改造する形で実装していきます。
Cubeサンプル実行を通じて以下の2点が実施された状態とします。
- genvid-toolboxインストール
- genvid-bastionのローカル環境へのインストール
また、Genvidはクラウドサーバーを活用して配信システムを構築するSDKですが、今回はローカルのPCでゲームとブラウザ側のシステムのみを作るところまでとなります。
クラウドサーバー構築を含めた、実際のサービス開始までの道のりについては以下の配信ガイドをご覧ください。
配信ガイド
https://www.genvidtech.com/doc/ja/SDK-1.36.0/broadcasting_guide.html
Genvid Eventについて
今回紹介するGenvid SDK側で使用する機能は「Genvid Event」と呼ばれる機能です。
「視聴者のブラウザからゲームへ送る」方向の通信を行います。以下の記事でも詳細を紹介しています。
実装解説
今回は動画の上に5x5のグリッドでボタンを表示し、視聴者に「どこに柱を立てるか」を選ばせて、ゲーム側へ情報を送ります。
ゲームではその情報をゲーム内のステージ構成に反映させます。
「Cubeサンプル」のソースを改造
まずは、SDK同梱サンプルソースをコピーして手を入れていきます。
以下の「Genvidセミナー インタラクティブな動画配信を最短実装!」資料の30ページから39ページまでを参考に、Cubeサンプルからのソースコピーと改造を行ってください。
Genvidセミナー インタラクティブな動画配信を最短実装!
https://www.docswell.com/s/ichijo/5N78LZ-genvid_handson_20220318#p32
ブラウザ側の実装
動画を視聴するウェブサイトで表示するボタンなどのパーツは、一般的な動的Webページ同様、HTML, JavaScript, CSSファイルで構成します。Genvidシステムとの通信を行うJavaScriptモジュールとして、Genvid SDKに含まれるUMDモジュールを使用します(ES5もあります)。
ブラウザ側の動作に必要な最低限の要素は
- index.html
- style.css
- ボタンとGenvid通信をつなぎ込むJavaScript
- Genvid通信モジュール(genvid.umd.js)
の4点です。genvid.umd.jsはGenvidインストールフォルダのapi\web\distに配置されています。
残り3つのファイルを順に作っていきます。各ファイルのの名称は難でも構いませんが、以降では「overlay.js」という名前で作ります。
index.html
htmではビデオの表示と、その上に重ねて表示するボタンを配置します。
今回は5x5のグリッドで表示するマップ指定ボタン25個と、その設定をゲームへ送信するSend Dataボタンを置きます。
Genvid通信モジュールであるgenvid.umd.jsと、これから作るoverlay.jsをインポートしています。
<!doctype html>
<html>
<head>
<title>Genvid Overlay</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="video_player"></div>
<button class="buttonsendata" type="sendButton" id="send">Send Data</button>
<button class="button" type="setbutton" id="0">None</button>
<button class="button" type="setbutton" id="1">None</button>
<button class="button" type="setbutton" id="2">None</button>
(中略 ボタンを合計25個配置)
<button class="button" type="setbutton" id="23">None</button>
<button class="button" type="setbutton" id="24">None</button>
<script src="genvid.umd.js"></script>
<script src="overlay.js"></script>
</body>
</html>
style.css
CSSの設定では、動画の上に要素を重ねるため、ボタン類がposition:absolute;になっていることがポイントです。
グリッド状のボタン配置は、通常であればグリッドレイアウト(display: grid)を使うと思うのですが、今回はabsolute状態でjs側で再配置を行います。
body {
background-color: black;
}
.button {
cursor: pointer;
position:absolute;
display:inline-block;
padding: 1vw;
opacity: 0.7;
border-radius: 8px;
top: 15vw;
left:10vw;
height: 6vw;
width: 6vw;
color:black;
font-size: 1.2em;
}
.buttonsendata {
position:absolute;
top: 60vh;
left:3vw;
height: 6vw;
width: 8vw;
color:black;
}
.button:disabled {
background-color: gray;
color:black;
}
ボタン入力とGenvid通信をつなぎ込むJavaScript
HTML側のボタン入力とGenvidへの通信をつなぎ込むoverlay.jsを作っていきます。
まずはGenvidモジュールの初期化です。genvidClientを作り、初期化処理を行います。
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.start();
})
.catch(function (e) { console.log(e) })
次に、ボタンをグリッド状に配置しつつ、ボタンが押されたときの挙動を作っていきます。
querySelectorAllでタイプ指定をしてボタンのリストを取得し、forEachで5x5の配置を行います。
ボタンを押されたら、innerTextを"None"と"Piller"で入れ替えるようにしておきます。
(大変恥ずかしながら、setAttributeでの配置指定が毎回クリアされてしまう現象が良く分からず、ボタンが押されるたびに再設定しています……)
var buttonElements = document.querySelectorAll("[type=setbutton]");
buttonElements.forEach((buttonElement, index) => {
let top = ((Math.floor(index / 5)) * 6) + 3;
let left = ((index % 5) * 6) + 3;
buttonElement.setAttribute('style','top:'+top+'vw;left:'+left+'vw;');
buttonElement.onclick = function () {
if (buttonElement.innerText == "None")
{
buttonElement.innerText = 'Pillar';
buttonElement.setAttribute('style', 'background-color:orange;top:'+top+'vw;left:'+left+'vw;');
}else{
buttonElement.innerText = "None";
buttonElement.setAttribute('style', 'background-color:white;top:'+top+'vw;left:'+left+'vw;');
}
}
});
最後にデータ送信ボタンの設定です。
ボタンが押されたら、さきほど取得したグリッドボタンのinnerTextを見てNoneなら0、Pillarなら1として配列を作ります。
この配列をコンマでJoinして、テキスト情報としてGenvidシステム側へ渡します。
データ形式は「0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1」のような文字列になります。
その後、genvidClient.sendEventを呼んでキーを「levelData」,値を作成したテキスト情報としてデータを送信します。
var sendButtonElement = document.getElementById("send");
sendButtonElement.onclick = function () {
var arr = Array.prototype.map.call(buttonElements, (b) => b.innerText == "None" ? 0 : 1);
var levelDataArrayStr = arr.join(',');
genvidClient.sendEvent([{
"key": ["levelData"],
"value": levelDataArrayStr
}])
};
ブラウザ用データのビルド
シェルでCubeサンプルのソースをコピーしたディレクトリ(package.jsonがあるディレクトリ)に移動してnpm installコマンドを実行し、依存パッケージのインストールを実行して下さい。
Genvid側の設定
ブラウザ側からどんなデータを送信するかが決まったら、データを受け取るサーバーの設定をします。
サーバーはGenvid SDKで管理していきますが、このシステムの場合はゲームとブラウザの通信繋ぎ込み部分である「events.json」の設定が必要です。
設定はJsonスキーマで行います。詳しい構築の仕方は下記にありますので、本投稿ではデモで使ったデータのみ書いておきます。
{
"version": "1.7.0",
"event": {
"game": {
"maps": [
{
"id": "levelData",
"source": "userinput",
"where": {
"key": ["levelData"],
"name": "<levelDataArrayStr>",
"type": "string"
},
"key": [
"levelData",
"<levelDataArrayStr>"
],
"value": 1
}
],
"reductions": [
{
"id": "levelData",
"where": {
"key": [
"levelData",
"<levelDataArrayStr>"
]
},
"key": [
"<levelDataArrayStr>"
],
"value": [
"$sum"
],
"period": 250
}
]
}
}
}
Unity側実装
ここでは、Genvidサーバーから柱を立てる箇所の配列情報を受け取り、それをもとにオブジェクトを配置するスクリプトについて説明します。
シーンの構築やプレイヤーキャラクターの操作については割愛します。
はじめにプロジェクトにGenvid SDKプラグインをインポートします。
Genvid SDKインストールフォルダのengine-integration/unity/genvid_sdk.unitypackageが当該プラグインです。
次にCubeオブジェクトなどで柱を作り、Prefab化しておきます。
下記のPillarCreatorスクリプトをシーンのどこかにアタッチし、pillarPrefabフィールドに作ったPrefabの参照を入れておきます。
using System;
using System.Collections.Generic;
using System.Linq;
using GenvidSDKCSharp;
using UnityEngine;
public class PillarCreator : MonoBehaviour
{
public GameObject pillarPrefab;
private List<GameObject> pillarList = new List<GameObject>();
private void Start()
{
for (int i = 0; i < 25; i++)
{
var pillarObject = Instantiate(pillarPrefab, new Vector3(0f, 2.5f, 0f), Quaternion.identity);
pillarObject.SetActive(false);
pillarList.Add(pillarObject);
}
}
public void SetStageData(string eventId, GenvidSDK.EventResult[] results, int numResult,
IntPtr userData)
{
pillarList.ForEach(p => p.SetActive(false));
var selectedPlayerNumberStr = results[0].key.fields[0];
var strArray = selectedPlayerNumberStr.Split(',').ToList();
int[] intArray = strArray.Select(n => Convert.ToInt32(n)).ToArray();
for (int index = 0; index < intArray.Length; index++)
{
if(intArray[index] == 0) continue;
var posX = 12 - ((index % 5) * 3);
var posY = (index / 5) * 3;
var pillarObject = pillarList.FirstOrDefault(p => p.activeSelf == false);
pillarObject.transform.SetPositionAndRotation(new Vector3(posX, 2.5f, posY), Quaternion.identity);
pillarObject.SetActive(true);
}
}
}
処理はシンプルです。Start関数でオブジェクトプールを作成し、SetStageData関数でGenvidシステムから届いた文字列をコンマで分割し、プールしてあるオブジェクトをグリッド上に配置します。
データはGenvidSDK.EventResult[]型のresults[0].key.fields[0]に格納されています。
シーン上にGenvidの動作に必要なPrefabを配置し、上記のSetStageDataメソッドとGenvid Eventスクリプトとの紐づけを行います。
手順については以下スライドの53-55ページを参考にして下さい。
また、Unityのexeビルド後の手順はページ61-62に記載されています。
Genvidセミナー インタラクティブな動画配信を最短実装!
https://www.docswell.com/s/ichijo/5N78LZ-genvid_handson_20220318#p53
実際のTwitch配信で使用するには
実際にゲーム配信で本システムを使用する場合は、2つの手順が必要です。
まずTwitch環境での開発とデータの提出、審査が必要です。
手順については、マニュアルの以下のページをご覧ください。
Twitch 拡張機能を使用した Twitch ライブ配信
https://www.genvidtech.com/doc/ja/SDK-1.36.0/broadcasting_guide/streaming_platforms/twitch/twitch_extension.html
次に、クラウドサーバーへのシステム一式のデプロイもあります。
マニュアルでは次のページから手順が書いてあります。
配信ガイド
https://www.genvidtech.com/doc/ja/SDK-1.36.0/broadcasting_guide.html
展望と課題
-
配置アイテムのバリエーション作り
今回は柱のありなしのみでしたが、坂や罠などを設置できるようにすれば、より凝ったステージ制作ができそうです。 -
配信者がステージをクリアしたらポイントが貰える
Genvidの特徴は、ゲームからブラウザへデータを配信できることです。
今見ている実況のステージが誰によって作られているかを表示し、それが貴方であった場合、クリアできた場合はポイントが貰える、という仕組みが作れそうです。 -
突破不能なケースをどう弾くか
「マリオメーカー」では、投稿者がクリアしないと面を送信できない仕組みになっていました。
このアプローチの場合はそもそもプレイテストができないため、「積みステージ」を送らせない仕組みが必要です。 -
大量の視聴者からデータが来た場合どう管理するか
何百人もステージデータが送られてきた場合も、なにかしらの管理が必要です。直接ゲームに送るのではなく、一時保管できるサーバーを立てたほうが良いかもしれません。
まとめ
このシステムを応用することで、ゲーム開発者が、ゲームソフトの中ではなく、動画配信サイトの上でも体験の一部を販売することができます。
Twitchには「Twitch Bits」という視聴者による決済システムがあります。これと組み合わせることで、見ている配信者へステージを送りつける権利を100円、といった販売も可能です。
今回はローカルPC上でのテスト方法までの説明ですが、仕組み上は誰でも開発・配信できます。ぜひみなさんの作品にも取り入れてみてはいかがでしょう。
追記:発展形を作りました