8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

100人でタワーディフェンス~ライブ動画配信サイト経由で超多人数プレイゲームを作る~

Posted at

ゲームとライブ配信の「みんなで盛り上がる」感を融合

ライブ動画配信は、何千、何万人もの視聴者が同時に同じコンテンツを見て、独特の一体感やライブ感を楽しむことができます。
ゲーム実況の場合は、配信者のプレイをみんなで見守ります。ただ、動画を視聴する側としては基本的には受動的なコンテンツで、コメントを行って配信者とコミュニケーションはできますが、ゲームの中の世界そのものに直接触れることはできません。

ゲームを自分で遊ぶ場合は、1人プレイのゲームから数人で遊べるアクションゲーム、多くのプレイヤーと世界をともにするMMOなどがあります。
ゲームはコントローラーやキーボードで能動的に遊ぶコンテンツですが、MMOであったとしても、レイドバトルなどの盛り上がるシーンを共にできるのは多くで数百人といったところでしょうか(EVE Onlineのような規模が凄まじいゲームもありますが…)

ライブ動画配信の「一つのコンテンツを超大人数で同時に楽しむ」ものと、ゲームのように「能動的に遊ぶコンテンツ」を融合して、どちらともつかない新しい形式のコンテンツを考えてみます。

100人で遊べるタワーディフェンス

今回のデモのイメージとして、以下のサンプルを作りました。

towerdiffence.gif

こちらは、以前投稿した記事「ゲームのライブ配信画面上で視聴者がステージを作って配信者に送れるシステムの構築」のサンプルを改良したものです。

画面の奥から敵が迫ってきますが、柱を立てることで進行を阻止できます。
左側のボタンを操作して5x5のマス目で柱を指定し、「Send Data」で3D空間にもフィールドに同じ形で柱が立ちます。
柱は前進していき、柱に一定以上押された敵は消滅します。

ブラウザ上で動かすゲームに見えますが、実はボタン部分をHTML + cssで描画しており、その他の要素はストリーミング動画です。
ボタンを除いた場合は以下のような画面構成になります。

スクリーンショット 2022-10-07 010447.jpg

まずゲームの3D部分をクラウドサーバー内で描画し、動画ストリーミングデータとして動画配信サービスに送ります。
ユーザーは動画配信サービス経由でこの映像をブラウザで観つつ、動画に重ねて表示されている操作用のボタンを操作します。

一見「クラウドゲーム」のように見えますが、本デモのポイントは1個のゲームexeに対して大量プレイヤーが同時に遊べる点です。
動画配信サービスでゲームの映像を表示しているので、何人でも参加できます。
「柱を立てる」というアクションを大勢のプレイヤーが同時に実行できます。

本投稿では、ゲーム的なインタラクティブ性を、ライブ動画配信サイト上で実装するアプローチを考えます。

  • ライブ動画配信サイトを使って、何もインストールせずブラウザから手軽に参加できる
  • クラウドサーバーで単一の映像をリアルタイムレンダリングし、動画としてブロードキャストする
  • ライブ動画配信サイトでボタン操作などのアクションを実行し、ライブ配信中のコンテンツが動的に変わっていくようにする

ゲームっぽいですが、コントローラーでガンガン動かすプレイヤーはいません。ブラウザ経由で動画を見る人がプレイヤーとなります。
これらのコンテンツの実現方法として、動画配信サイトはTwitch、ゲーム開発ツールはUnity、サーバーサイドはMS Azure、通信ライブラリとしてGenvidを使っていきます。

開発環境

Twitch Extensions

Twitch Extensionsは、動画配信サイト「Twitch」で利用できる機能の一つです。動画の視聴ページに、動画の内容と連動した各種UIを表示できる機能です。
このうち、「Overlay」と呼ばれる、動画の上に文字や画像、ボタンを重ねて表示できる機能を利用します。
https://www.twitch.tv/p/ja-jp/extensions/

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に送信します。また、動画視聴者のブラウザに対してゲーム内のデータをブロードキャストします。

Genvidシステム構成図.png

Unity (2021.3.3f1)

Unity側については、上記「Genvid」の通信プラグインを導入し、動画を流しているブラウザとのデータ通信を行う処理を作ります。
Genvid SDK自体はUnreal Engineにも対応しているため、本記事で解説するタイプのコンテンツをUnreal Engineを使って開発することも可能です。

Microsoft Azure

クラウドサーバーについては、AzureまたはAWSがGenvidでは使用できます。
できることに違いはありませんが、普段筆者がAzureを利用しているため、本投稿の例ではAzureを使用しています。

ゲーム部分の実装

はじめに、Unityを使ってゲーム部分の実装を行っていきます。
基本的には前回の記事と同一です。

前回記事からの変更点

実装非常に単純で、まず一定間隔でスポーンする敵キャラクターを作ります。
敵キャラクターはUnity標準のNavMeshを使い、画面手前の目的地まで前進します。

また、敵をブロックする役割として、一定速度で前進する「柱」をプレハブとして持っておき、ブラウザ側からの通信に応じてInstantiateするようにしておきます。
この時、柱はNavMesh Obstacleコンポーネントをアタッチすることで、敵キャラクターを押し出します。
デモでは、敵が一定時間柱に押されると消滅します。また、柱も敵にあたり続けると消滅します。判定自体はOnTriggerStayを使った簡易なものですので、ここでは省略します。

towerdiffence2.gif

前回記事では、あらかじめ25個の柱のインスタンスを生成しておき、ブラウザからの配置データが来るたび、すべての柱の位置を削除してから再配置していました。
今回はインスタンスを100個ほど先に作ってから、配置データが来るたびに使われていないインスタンスから順に配置し、敵に破壊されたり視界に出たインスタンスを再利用するオブジェクトプールにしています。

PillarCreator.cs
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 < 100; i++) //100個生成
        {
            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)
    {
        //ここでリストのPillarObjectをすべてSetActive(false)していたが、今回は行わない

        var selectedPlayerNumberStr = results[0].key.fields[0];
        var value = results[0].values[0].value;
        
        Debug.Log("selectedPlayerNumberStr " +selectedPlayerNumberStr +" value " +value);
        
        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);
        }
    }
}

視界外に出たオブジェクトのクリアは、Pillarオブジェクトの中でOnBecameInvisibleコールバックを用いています。

Genvid Unity plug-inを使ったデータ連携部分の実装

ユーザーのブラウザ上での操作を受信し、ゲーム内に反映させる部分についてはGenvid SDKを経由して実装します。

Genvidの通信を処理する「Genvid Session Manager」及び付随するコンポーネントは、Prefabの状態でUnity Plug-inに含まれています。
これをシーンに常駐させます。ブラウザから来たデータは「Genvid Event」ゲームオブジェクトのインスペクタで、どのようなデータ来た時に何の関数を呼ぶかを指定していきます。

NotificationsFront 2022-10-07 12.17.45.png

今回の例では、5x5のマス目のデータを「levelData」としてブラウザから送信していますので、その名前のデータが来た時に「柱をデータに従って配置する」メソッドを呼び出しています。

ブラウザ部分の実装

ブラウザ側のボタン類の実装については、前回記事と同一です。

簡易的に説明をすると、ブラウザ側は「Twitch Extensions」のフォーマットに従って、HTML + CSS + Javascript環境で開発します。

テスト動作時はローカルから直接ロードして動画の上から重ねて表示させますが、実際にサービスを動かすためにはTwitchのウェブサイト上で表示するため、データ一式をTwitchに提出して審査を受ける必要があります。

クラウドサーバー側のセットアップ

本システムではゲームをクラウドサーバー上で動かし、動画としてTwitchにデータを渡し、Twitch経由でブロードキャストします。
クラウドサーバーの機能としては、以下が必要になります。

  • ゲームの動作
  • ゲームの映像をキャプチャし、エンコードしてTwitchに渡すサービス
  • ゲーム内部のデータを配信するサービス(今回は使用しない)
  • ブラウザからくる通信を受けるサービス
  • ブラウザから来た通信をゲーム内部に渡す前にリデュース(データをまとめる)処理をするサービス

Genvid SDKでは、これらのシステムを一気にクラウドサーバー上にデプロイできます。実際の手続きについては以下の記事を参考にしてください。

ブラウザからデータを受信する際の設定

Genvidのシステムにおいては、ブラウザ側からどのような構造のデータが送られてきて、そのデータをどのような頻度で合算するかをJSONスキーマで設定します。
今回は5x5のマス目の設定を、50桁の文字列として送信しています。

{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}

0が何もない位置、1が柱の立っている位置です。さて、ブラウザから大量データが送られてくる場合、そのデータをそのままの頻度でUnityに渡すと処理がパンクします。

そこで、まず受信サーバー側で集計処理を行い、一定間隔でのみデータを受け付ける処理を行います。下記の例では、250msecごとにデータの合計を行っています。

今回ブラウザから送信しているデータは、キーの部分に文字列データとして配置情報を含め、値は「1」としています。このため、合計処理($SUM)を行った場合、キーが同一の場合は値にデータ個数の合計結果が出力されます。

これは、まったく同じ配置のデータを別々のユーザーが送信した場合にデータが集計されることを意味し、データ受信サーバーとUnity間のデータ量の削減が期待できます。

{
  "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
            }
          ]
    }
  }
}

超多人数で遊べる動画コンテンツの応用

前回記事は、「ゲーム実況」の発展形としてプレイヤーが遊んでいるゲームに動画視聴者が参加できるタイプのコンテンツを紹介しました。
今回はさらに、プレイヤーがそもそもいない形で、動画を通じてゲームを遊べるタイプの構築をテストしました。
モチーフはタワーディフェンスとしましたが、他にもいろいろなジャンルを土台として作ることができそうです。

  • 自動で進行する横スクロールゲームに罠を仕掛ける
  • オセロなどのボードゲームを100人同時実行
  • 全員投票で選択肢が決まるアドベンチャーゲーム

Twitch + Genvidはまだまだ開拓できそうな予感があります。ぜひ触ってみてください。

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?