20211230のUnityに関する記事は7件です。

ゲームの実況動画内にブラウザ側で広告画像を合成 with Genvid

ゲーム実況動画内広告を動画視聴者ごとにカスタマイズする ゲーム内に広告を表示する仕組みは以前からあり、ゲームプレイヤーに向けた広告手法としてさまざまなサービスやSDKがありました。 ゲームプレイヤーが対象の広告であれば、セグメンテーションは比較的わかりやすいです(昨今はEUの法律により厳しくなりましたが)。 ゲームのアカウントを持つプレイヤーがどんなセグメントに属しているかを取得し、表示する広告を出し分けるといったことができます。 それでは、ゲームプレイヤーだけではなく、ゲームの動画配信を見るする視聴者にも広告を届けたい、と考えてみます。 ストリーミング動画はたくさんの動画視聴者に見てもらえます。しかし、ゲームの動画内で広告を表示するということは単一の広告になりますから、動画視聴者ごとに内容をカスタマイズすることはできません。 そこで、ストリーミング動画の中に広告を表示するのではなく、ブラウザ上でゲーム動画に広告を出すことをもできます。 単純なバナー表示であれば、動画の下などに表示できますが、動画サイズが狭くなるのでちょっと邪魔です。 そこで発想を変えて、ゲーム動画内の看板エリアなどに広告画像を上から被せて出す形を考えます。 ゲーム内のカメラ位置が変わらないならば、ただその座標に広告を出しておけます。しかし、カメラの位置が変わるゲームはどうなるでしょうか? この記事ではその一つの方法として「Genvid SDK」によるインタラクティブなライブストリーミング技術を使って解決を図りたいと思います。 ゲーム動画の内容に同期して、広告画像の位置が移動する この仕組みは単純に言えば、ゲームから広告を表示する位置情報を、配信動画と同期した形でデータを送信します。 ブラウザ側は、そのデータを受け取って広告を表示する位置を更新します。 たとえば横視点の対戦ゲームがあったとして、ゲームの背景のビルボードに広告が表示される実装を考えてみます。 プレイヤーの操作に応じてカメラの位置は上下左右に動きますが、ゲーム内のビルボードの位置をブラウザに配信するため、ブラウザ側で重ねる画像の位置を同期させることができます。 Unityで作ったデモは次の通りです。 青い画像はブラウザ側で描画しています。その他の要素はストリーミング動画です。 Unityで描画されているものは以上で、これがゲームから動画として配信されます。 その動画に、ブラウザ上でこの広告画像を重ねつつ、位置情報を使って同期させます。 Genvid SDKについて Genvidは、動画ストリーミングとブラウザを介したインタラクティブな体験を組み合わせた「大規模インタラクティブ・ライブ・イベント」(Massive Interactive Live Events)を実現するSDKです。 https://www.genvidtech.com/ja/mile%E3%81%A8%E3%81%AF%EF%BC%9F/ ゲーム技術をベースに、リアルタイムで進行する動画番組に対して、動画視聴者が能動的に参加できるシステムを提供します。 動画番組内のキャラクターが次に何をするかを投票で決めたり、ミニゲームをプレイしてポイントをため、特定のキャラクターを応援するなどの活動を経て、物語が変化していきます。 MILEは、Unityを使いFacebook上で配信されている「Rival Peak」と、Unreal Engine 4を使った「Project Raven」があります。 『RIVAL PEAK』が示す次世代の視聴者参加型デジタルエンタテインメント https://news.yahoo.co.jp/byline/onokenji/20210326-00229353 Genvid SDKの導入については、Genvidディベロッパーサイトの日本語マニュアルからご確認ください。 https://www.genvidtech.com/for-developers/ 今回はGenvid SDK for Unityを使っていますが、Unreal Engineでも利用可能です。 なお、Genvid SDKは広告SDKではなく、上記のシステムを実現するサーバーミドルウェアです。 表示する広告データを取得する仕組みは別途必要になりますので、ご留意ください。 下準備 Genvid SDKのインストールとデモ実行 Genvid SDKの基礎知識とインストール、デモ実行については公式ドキュメントと、以下の記事をご参考ください Genvid Cube サンプル https://www.genvidtech.com/doc/ja/SDK-1.32.0/samples/cube/cube.html GenvidでUnityからストリーミング動画と同期したデータ配信を行う https://qiita.com/Takaaki_Ichijo/items/4fbc70e7efdbdef85459 ブラウザ側(js)の実装 style.css 今回のデモではCanvasを描画して、その位置を動画の上に重ねます。 overlay-layer、overlay-layer canvasをそれぞれ定義します。 canvas要素はpositionプロパティabsoluteでウィンドウ左上からの位置を指定します。 style.css .overlay-layer { position: relative; } .overlay-layer canvas { position: absolute; top: 0; left: 0; } index.html Genvidの動作に必要なgenvid.umd.js,genvid-math.umd.jsのほか、 今回は画像の描画・移動・拡大縮小のため、createJSフレームワークを使用します。スクリプトにはcreatejs.min.jsを指定します。Overlay.jsについてはこの後説明します。 index.html <!doctype html> <html> <head> <title>Genvid Overlay</title> <link rel="stylesheet" href="style.css"> </head> <body style="background:black"> <div class="overlay-layer"> <canvas id="adCanvas" width="500" height="300"></canvas> </div> <div id="video_player"></div> <script src="genvid.umd.js"></script> <script src="genvid-math.umd.js"></script> <script src="https://code.createjs.com/1.0.0/createjs.min.js"></script> <script src="overlay.js"></script> </body> </html> overlay.js オーバーレイ処理を実際に行うjsです。 画像の描画 まずcreateJSでステージ(描画エリア)と画像の読み込み、親子関係の設定を行います。 overlay.js var stage = new createjs.Stage("adCanvas"); var adBitmap = new createjs.Bitmap("ad.png"); stage.addChild(adBitmap); Genvidの初期化とStreamデータの受け取り 次にGenvidの初期化行います。 genvidClient.createGenvidClient後、onStreamsReceivedでGenvidStreamが来た時にJSONとしてパースする設定と、onDrawで描画時に指定の名前のGenvidStreamが来ていたら描画関数を実行する手続きを設定します。 overlay.js 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["adPosition"]; if (gameDataFrame && gameDataFrame.user) { draw(adBitmap, gameDataFrame.user); } }); genvidClient.start(); window.addEventListener("resize", this.resizeStage); this.resizeStage(); }) .catch(function (e) { console.log(e) }); genvidClient.start();のあと、ウィンドウのサイズが更新されるたびにresizeStageという関数を呼んでいます。これはウィンドウサイズに合わせてstageと画像のサイズを調整するための処理です。 画像位置のアップデート genvidClient.onDrawの中で呼んでいる処理です。基本的には前回記事と同じで、ゲームから送信されてきたマトリックスをベースに平面位置の座標を計算します。 ただし、前回はleft,bottomの値から座標設定していましたが、createJSを使っているためleft,topからの座標が必要です。そのためy軸を反転しています。 overlay.js function draw(canvas_element, gameData) { //calculate 2d position of builboard let mat = this.convertMatrix(gameData.matProjView); let b = gameData.adBillboardMatrix; let ap = genvidMath.vec3(b.e03, b.e13, b.e23); let pos_ad_2d = genvidMath.projectPosition(mat, ap); //invert y position pos_ad_2d.y = -pos_ad_2d.y; // Convert from [-1, 1] range to [0, 1]. let vh = genvidMath.vec2(0.5, 0.5); let pos_2d_n = genvidMath.mad2D(pos_ad_2d, vh, vh); // Convert from [0, 1] range to [0, w]. let stage_size = genvidMath.vec2(stage.canvas.width, stage.canvas.height); let pos_in_stage = genvidMath.mul2D(pos_2d_n, stage_size); // Adjust for centering element. let e_size = genvidMath.vec2(canvas_element.image.width * canvas_element.scaleX, canvas_element.image.height * canvas_element.scaleY); let e_offset = genvidMath.muls2D(e_size, -0.5); let pos_centered = genvidMath.add2D(pos_in_stage, e_offset); canvas_element.x = pos_centered.x; canvas_element.y = pos_centered.y; stage.update(); } function convertMatrix(rawmat) { return genvidMath.mat4(genvidMath.vec4(rawmat.e00, rawmat.e01, rawmat.e02, rawmat.e03), genvidMath.vec4(rawmat.e10, rawmat.e11, rawmat.e12, rawmat.e13), genvidMath.vec4(rawmat.e20, rawmat.e21, rawmat.e22, rawmat.e23), genvidMath.vec4(rawmat.e30, rawmat.e31, rawmat.e32, rawmat.e33)); } センタリングを行った座標を画像のcanvasに渡した後、stage.update()を呼んで描画更新を行います。 ウィンドウサイズに合わせて画像を拡大縮小する 今回デモとして用意した画像は720pの解像度でちょうどいい大きさ(400 x 300)にしています。 マジックナンバーになってしまっています、このサイズ設定を元にウィンドウ幅に対して画像を何パーセントに拡縮すればよいかを指定しています。 overlay.js function resizeStage(event) { let videoHeight = document.documentElement.clientWidth / genvidClient.videoAspectRatio; stage.canvas.width = document.documentElement.clientWidth ; stage.canvas.height = videoHeight; console.log("stage " + stage.canvas.width + stage.canvas.height); //広告画像は動画720pでちょうどいい大きさ(400 x 300)なので、この係数で広告画像のスケールを調整する let adScale = stage.canvas.width * 0.00078125; adBitmap.scaleX = adScale; adBitmap.scaleY = adScale; stage.update(); } ちゃんとやるならばUnity側から広告表示エリアの現在の高さ・幅を配信しながら合わせる形ができると思います。 Unity側実装 前回記事とほぼ同一ですので、実装はこちらをご覧ください。 前回記事はボールの位置を送信していましたが、今回はQuadをゲーム内の看板立てに見立てて表示し、その位置をMatrix4x4で送信しています。 ゲームから広告表示位置を送信するデータのプロパティ名は「adBillboardMatrix」として定義しています。 応用 Genvidを使ったインタラクティブ・ストリーミングは、ゲームからの情報を配信するだけではなく、動画を見ているブラウザ側からデータを取得し、ゲームへ送信できます。 たとえば動画視聴者に対して、ゲームのロード中などにアンケートを表示し、その内容を公告配信に反映させるといった仕組みも構築が可能と言えます。(個人情報に関する法的な部分は別問題ですが)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BPMに合わせてオブジェクトを点滅させる(HDRP/lit編)

HDRPでオブジェクトを点滅させたい させたいですよね。 最初はHDRP/litの_EmissiveIntensityをスクリプトから操作して点滅させようとしましたが、値は変わるのにGameViewに反映されませんでした。 あきらめて、_BaseColorで点滅させられるように作りました。 EmissiveColorを前もって明度の高い色(白)にしておけば、 ぴかぴか点滅してくれます。 色はスクリプト上でいじります。 インスペクタ―で設定できるのはBPMだけです。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class EmissionBlinking : MonoBehaviour { private Material material; private float length = 1.0f; public int bpm = 120; void Start() { material = GetComponent<Renderer>().material; } void Update() { float span = bpm / 60; //1秒に何ビートか? float val = Mathf.PingPong(Time.time * span * 2f, length); //BPMの拍に合わせて点灯させるために *2 する Color color = new Color(1.0f - val * val, 0f, 0f); //RGBで任意の色にする material.SetColor("_BaseColor", color); } } プログラミングなんてできねえ! と思いながら始めたC#学習も、なんとかかんとか触りだけはこなせました。 よい映像制作につながるように来年もがんばります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRChatプレイヤーによるParticle System解説 ~Texture Sheet Animation Module編~

この記事はUnity 2019.4.29f1の記事です Triggers, Sub Emitters Module編 << ここ >> 次 はじめに この記事はちょっとParticle System触れるようになったけどまだまだ知らないパラメーターだらけで抵抗感がある人向けにじゃあ全項目解説しようという記事です。 パーティクル?シェーダー?憧れるけどよくわかんない…という人は最近はVRCUnity勉強会などで初歩の初歩から解説してくれる講座などが解説されているのでそこに参加してみたりするといいでしょう。 その他にこの記事はアドベントカレンダーにしてParticle Systemの記事を増やしてパーティクルへの抵抗感を減らしてやろうという企みもあります。 Texture Sheet Animation Moduleってどこ? これ Texture Sheet Animation パーティクルのテクスチャをパラパラ漫画のように再生させるためのModule Mode デフォルト Grid Gridはテクスチャをn×n分割して扱い、SpritesはSpriteを複数枚入れて再生させる、基本操作はほぼ同じなのでここではGridの場合のみ解説する Tiles デフォルト (1,1) テクスチャを何分割してパラパラ漫画のように再生するか決めるパラメーター アセットのものとかでやるとわかりやすい Animation デフォルト Whole Sheet 再生するアニメーションのモード選択 Whole Sheetは普通に通しで全部再生する、Single Rowは横の行ごとに分割して別々のアニメーションとして扱うまたSingle Rowを選択するとRow Modeを選択することになる Row Mode デフォルト Random どの行を再生するか選ぶ方法を決めるパラメーター Randomはパーティクル1つ1つ毎にランダム、Customは何番目か指定、Mesh IndexはRender ModeをMeshにした際のメッシュのリストの順番 Time Mode デフォルト Lifetime アニメーションの再生速度の基準を決めるパラメーター Lifetimeは選択するとFrame over Lifetimeのグラフが出てきてパーティクル1つ1つが出てる間にどのようにアニメーションを再生するか決められる Speedを選択するとSpeed Rangeのパラメーターが出てきて速度に応じて1パラメーターを再生できる FPSを選択すると1秒間に何フレーム再生するかで決めれる、再生が終わるともう一回再生される Start Frame デフォルト 0 何番目2のフレームからアニメーションを再生するか決めるパラメーター 小数は一見意味がなさそうだけど上のグラフとかで扱われてるから微妙に再生タイミングが変わったりとかする Cycles デフォルト 1 (0.0001~100000) 再生時間の内何回アニメーションを再生するか決めるパラメーター 再生時間とはLifetimeで言うパーティクルの存在時間 Affected UV Channels デフォルト Everything テクスチャを分割する際メッシュのUVに影響されるか決めるパラメーター 基本デフォルトでいいし変える必要があるなら説明しなくてもわかるのでは…? おわり というわけでTexture Sheet Animation Moduleの解説でした アドベントカレンダー僕一人しか参加してなくて失踪しかけましたがなんとか一人で頑張ってます 次はLight Moduleです Triggers, Sub Emitters Module編 << ここ >> 次 ここと似たパラメーターは以前解説した為省略、Lifetimeのデフォルトの左右端を選んでる感じ https://qiita.com/abcde_kind/items/96710d4e401ca88abbb5 ↩ Unityは基本的に0番目から数字を数える ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Asset "Mobile Notifications"の使い方 (和訳)

Unity Asset作家Gleyさんによる有料アセット達のDocumentationを和訳するシリーズです。今回は「Mobile Notifications」というアセットです。 こちらのセットにも同梱されています。 このアセットでできること 通知を表示させることでゲームへのエンゲージメントを獲得できる。 たった1行のコードでローカル通知のスケジュールを予約できる。 通知をクリックすることで開始するアプリのセッションを監視するために、自分でカスタマイズしたデバッグメッセージを返すコールバックを設定できる 通知アイコンをカスタマイズできる。 通知内容をカスタマイズできる。 デバイスの再起動に対応。 AndroidとiOSに対応。どちらのOSでも同じコードのままビルドできる。 初期設定 Window > Package Manager > My Assets を開き、本アセット "Mobile Notifications" をインストールする。 Window > Package Manager > Unity Registry を開き、Unity公式の "Mobile Notifications" パッケージをインストールする。 Window > Gley > Notifications を開くと、Settingウィンドウが表示される。 Select your platforms でビルドしたいプラットフォームを選択し、Saveボタンを押す。 Edit > Project Settings > Mobile Notifications を開くか、今開いたSettingウィンドウで "Open Mobile Notification Settings" ボタンを押す。 Android 設定 デバイスを再起動した後も通知を送りたい場合は "Reschedule Notifications on Device Restart" を有効にする。 カスタム通知アイコンを使用したい場合は、SmallおよびLargeサイズのアイコンを選択してそれぞれリストに追加する。ここでカスタム通知アイコンを指定しない場合は、アプリアイコンが代わりに使用される。(このリストに追加しない代わりに、res/drawableフォルダに手動で保存することでも設定できる。) Smallサイズは48x48pixel以上、Largeサイズは192x192pixel以上である必要がある。Smallサイズのアイコンには白pixelで絵を描画して背景を透過させたものを使用する。Largeサイズのアイコンでは色の制約はない。 iOS 設定 初回アプリ起動時に「〇〇(アプリ名)は通知を送信します。よろしいですか?」ダイアログを表示したい場合は "Request Authorization on App Launch" を有効にする。 Script作成 初期化 // このメソッドは新しい通知チャンネルを作成し、現在保留中の通知をすべてキャンセルします。 // ユーザーがアプリを起動するたびに毎回呼び出されるメソッドです。 GleyNotifications.Initialize(); 初期化(保留中の通知をキャンセルしない) // 引数をfalseにすると、現在保留中のすべての通知をキャンセルさせずに初期化できます。 // これらの通知はユーザーがアプリを起動中でもおかまいなしに表示されます。 GleyNotifications.Initialize(false); 通知のスケジュール予約 GleyNotifications.SendNotification( string title, string text, System.TimeSpan timeDelayFromNow, string smallIcon = null, string largeIcon = null, string customData = ""); 型 引数名 説明 string title 通知タイトル string text 通知メッセージ内容 System.TimeSpan timeDelayFromNow 通知の送信を遅延させる時間。現在時刻に加算される string smallIcon 使用したいSmallサイズのカスタム通知アイコンの名前(Identifier) string largeIcon 使用したいLargeサイズのカスタム通知アイコンの名前(Identifier) string customData ユーザーが通知をタップしてアプリを開いた場合に取得されるデータ 通知のスケジュール予約(反復) GleyNotifications.SendRepeatNotification( string title, string text, System.TimeSpan timeDelayFromNow, System.TimeSpan repeatInterval, string smallIcon = null, string largeIcon = null, string customData = ""); 型 引数名 説明 System.TimeSpan repeatInterval 次回の通知が送信されるまでの時間 通知がタップされたかどうかを取得する // このメソッドは、現在のセッションが通知をタップして開かれた場合にはcustomDataを返し、それ以外の場合にはnullを返します。 string GleyNotifications.AppWasOpenFromNotification() 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityの学習まとめとして超簡単なRPGを作ってみた!

はじめに 普段はWebアプリ関連のお仕事をしているのですが、「久しぶりにUnity触りたい!」という気持ちになったので約2週間程度ではありますが集中的にUnityを勉強して超簡単なRPGを作ってみました。 (一応2年半前にも一度Unity熱が上がり、少し勉強していた時期はあるので今回が完全に初見という感じではありません。) ゲームの内容 ゲームの内容としては、単純にドラゴンを倒して村を救うぞ!という感じの王道なやつです。 以下に作ったゲームの簡単な解説を記載していきます。 ゲームスタート画面 プレイヤーのパワーアップイベント 簡単なオープニングが終わった後、フィールドへプレイヤーが配置されます。 さあ、ボスであるドラゴンを倒しにいくぞ!と言いたいところですが、いきなりボス戦というのも何かなぁ...と思ったので一度村に行って装備を最新にするというイベントを挟む必要があるようにしてみました。(村に行かなくてもドラゴンとは戦えますが確実に負けるようになっています。) ポツンと存在する村 村にてパワーアップイベント 村でのパワーアップイベント後にはHPや攻撃力が増えています。 ※フィールドではランダムでスライムが出てきます。 パワーアップ前 パワーアップ後 ドラゴン戦(ボス戦) 森を駆け巡り、ドラゴン(ボス戦)へ辿り着きます。 エンディング 無事に倒したら簡単なエンディングが始まり、ゲーム完了となります。 実装内容 ここからは簡単に実装を振り返ってみたいと思います。 フィールドでの敵とのエンカウント エンカウントについては以下の2つの種類があるかと思います。 ランダムエンカウント →フィールドを歩いていたら一定確率で敵と遭遇(止まっているときは遭遇しない) シンボルエンカウント →敵があらかじめ見えており、戦いたい敵をある程度選べる 今回はファイナルファンタジーのようなエンカウントをイメージしていたので前者のランダムエンカウント方式にしてみました。 ロジックは色々と考えてみたのですが、単純にプレイヤーが動いている かつ ランダムで特定の数字が出るという内容にしてみました。 FieldManager.cs var playerSpeed = Player.GetComponent<Animator>().GetFloat("Speed"); var RateEncount = Random.Range(0, 1200); if(playerSpeed > 1.0f && RateEncount == 50) { PlayerFieldLocation = Player.transform.position; // 位置保存 Player.GetComponent<PlayerController>().MoveSpeed = 0; // シーン遷移中(フェードイン中)は動かないようにする。 fade.FadeIn(2.0f, () => { // 戦闘シーンへの遷移処理 }); } シーン遷移のトランジション 敵とエンカウントした際は戦闘シーンに移るかと思いますが、ただフェードインするだけ、というのも物足りないので渦を巻くトランジションを入れてみました。 敵とエンカウントすると以下のようにトランジションが出てフェードインします。 こちらも特に凝ったプログラムを書いているわけではなく、tsubaki_t1さんが作成してくれたトランジション用のライブラリとFor youさんが提供してくれているルール画像を利用することですんなりと作成できました。 ターン制バトル 敵とエンカウントした場合はバトルシーンに移り、ターン制のバトルが始まります。 ターン制のロジックについては「ゲージがたまったら攻撃アクションを選択できる」ようにしました。 「ゲージをためる」「攻撃アクションを選択後の動作」についてはそれぞれ以下のようにプログラムを書きました。 ゲージをためる SlideController.cs private void FixedUpdate() { // 省略 // ゲージはUnityEngine.UIのスライダーを用い、一定量ずつ増加します。 playerSlider.value += 0.004f; // ゲージがたまり、まだ戦闘アクションが選択されていなかったらパネル表示をします。 if(playerSlider.value == 1 && !AttackButtonController.HasPushButton) { PlayerPanel.SetActive(true); } } 攻撃アクションを選択後の動作 AttackButtonController.cs public void PushAttackButton() { HasPushButton = true; Panel.SetActive(false); // 攻撃アクション選択後はパネルを非表示にする。 var attack = Random.Range(PlayerManager.Attack - 15, PlayerManager.Attack + 15); // 与えるダメージは攻撃力の±15 // 攻撃内容が決まったゲームオブジェクトと与えるダメージをDictionaryで管理 // 敵も同様の実装を行う BattleManager.TurnOrders.Add(Player, attack); } BattleManager.cs void Update() { // プレイヤーも敵も攻撃情報がない場合は何もバトル処理を行わない if (!TurnOrders.Any()) return; // 省略 // バトルターン順に攻撃を行っていく。 var turnCharactor = TurnOrders.First(); if (!IsDuringMotion) { // 省略 StartCoroutine(DelayCoroutine(0.4f, () => { turnCharactor.Key.GetComponent<Animator>().SetTrigger("Attack"); })); } } 会話イベント RPGにかかわらず、ゲームにおいて会話イベントを必要とする場面は多いと思いますが、こちらはFungusというとても便利なアセットがあったのでこちらを利用しました。 あくまで例ではありますが、村での会話シーンは以下のように設定しています。 基本的にはUI上でセリフを設定したり、選択UIも設定できます。状況によってセリフを変更したい場合はif文も入れ込むことができるのでとても便利です。 もしプログラムを実行したいときはCall Methodを選択することでプログラム側のメソッドを呼び出すことも可能です。 おわりに すごく久しぶりにUnityを触ってみましたが、やはりUnityを使うとサクッとゲームが作れるのでとても便利ですね! 無料のアセットも充実していたので、アニメーションや村作成についてはすごく助かりました。 今後の展望としては、パーティーを組んだり攻撃以外のアクション(魔法やアイテム使用、レベルアップなど)も入れていきたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

18000km離れたサンパウロのGCP上で動いているUnity HDRP/ROS2シミュレータのカメラをAWS Kinesis Video Streams WebRTC/DataChannelを使って遠隔操縦してみた

はじめに この記事は、2021年ROSAdvent Calendarの24日目の記事です。 投稿が遅くなりすみません。 ROS2、遠隔操縦、Unityでのシミュレーションが流行ってきているように感じたので、勉強のためにタイトルのようなものを作って見ました。 下記のような内容に興味がある方の参考になれば幸いです。 UnityのHDRPでROS2のシミュレータ環境構築(Ros2-For-Unity) GCP/DockerでのHeadlessなHDRP Unityアプリケーションの実行 AWS Kinesis Video Streamsを使ったWebRTC C/JSのクライアントの作成 実行の様子 実際に遠隔操縦している様子が下記になります。 UnityのHDRP環境はサンプルで用意されたものをそのまま利用していますが、非常に綺麗で動かしていて楽しいです。サンパウロは東京から18000km離れていますが、相手がクラウド上なので、比較的安定的に遠隔操縦できました(解像度は640x480)。ただシミュレータなので、サンパウロで実行されていようが、あまり分からないのが残念です。 ROS2で構築しており、カメラトピックが出力できて、cmd_vel を受け付けるロボットだとロボットの種類を問わず操作できます。 動かすために必要なコードはこちらです。 https://github.com/otamachan/ros2-unity-kvm-webrtc 構築方法は、最後に記載したので興味のある方は見てみてください(すみません、徐々に追記します)。 構成 以下のような構成になっています。 通信の流れはおおよそ以下のようになっています。 まずは、カメラ画像です。 Unityシミュレータではロボットに配置されたカメラ画像を、定期的にキャプチャし、 Ros2 For Unityを使用して、sensor_msgs/Image として非圧縮でROS2のトピックとして出力します。 別のノードで、その画像を受信し、NVENCを利用しh264にエンコードした後、Amazon Kinesis Video Streams C WebRTC SDK を使い作成されたWebRTCのPeerConnectionに流し込みます。 ブラウザ側では、同様にAmazon Kinesis Video Streams WebRTC SDK for JavaScriptを使って作成したPeerConnection経由で画像を受信し、Videoとして表示しています。必要に応じて、TURNが利用されるため、P2Pで接続できない場合でも、転送できます。 操縦側です。 Unityシミュレータは、Ros2 For Unityを利用して、geometry_msgs/Twist をSubscribeして、速度に応じて移動できるようしておきます。 ブラウザ側では、仮想のJoystickで操作量を作成し、カメラ画像で使ったPeerConnectionにはったDataChannelにJSON形式で送信します。画像を配信するノードが同様に、DataChannel経由でJSONを受け取りそれをROS2のgeometry_msgs/Twistとして発行しなおすことで、ブラウザ側から操作できるようにしています。 UnityシミュレータをGCPで実行するには、Nvidiaドライバが有効になったxserverを起動する必要があります。今回は、xserverをDocker内に起動することで、ホストOSにxserverを起動する必要なく、UnityシミュレータをDockerで実行させています。この方法は、ホストOSにxserverを上げられないk8s等の環境でも、Unityのアプリケーション等xserverが必要なアプリケーションが利用できるようになるので、色々と便利です。 一方、コンテナ内にホストのNvidiaドライバとまったく同じバージョンのNvidiaドライバ(のユーザランドライブラリ)をインストールする必要があり、そこは注意が必要です。 終わりに 組み合わせるだけかと思いきや色々大変でした。特にUnityは慣れていない世界だったので、C#の書き方から始まって苦労しました。 それでは2022年もLet's enjoy robot programming! ついでに。 PreferredRobotics社では、ROS/ROS2/AWS/GCPに興味があり、ロボットの製品化を一緒に推進していただける仲間を積極的に募集しております。 興味のある方は是非話を聞きに来てください! 構築手順 構築環境 実行環境は、下記を想定しています。 Ubuntu 20.04 CUDA 10.2 ROS2 Foxy Unity 2020.03(LTS) 必要なコードの取得 GitHubから必要なコードをクローンしておきます。 git clone https://github.com/otamachan/ros2-unity-kvm-webrt.git UnityでHDRPサンプルプロジェクトの作成とビルド HDRPをするために、Vulkan環境も構築しておきます。2020では、Vulkan環境がなくてもHDRPが実行できる気がしますが、2019では必要だったので念の為にいれておきます。 sudo apt install vulkan-utils UnityHubの右上「新しいプロジェクト」をクリックし、HDRPのサンプルである、3D Sample Scene(HDRP) を選び、保存場所、プロジェクト名を適当につけて作成します。 メニューの「File」→「Build and Run」からビルドして実行できることを確認してください。実行すると以下のような画面がでて、キーボードやマウスで視点の移動ができるようになります。 ビルドにはかなり時間を要するので、気長にまちます。 Ros2ForUnityのビルド環境準備 今回、ROS2とUnityの通信として、Ros2 For Unity を使用しました。Unityの公式は、ROS TCP Connectorを使う方法ですが、今回画像トピックを扱うため、容量やレイテンシを考慮し、直接ROS2のトピックが発行できるRos2 For Unityを使いました。 DotNetのインストール https://docs.microsoft.com/ja-jp/dotnet/core/install/linux-ubuntu#2004- に従ってdotnetのSDKをインストールします。 各種ROS2のビルドツールのインストール また、Ros2ForUnityのビルドに必要なビルドツールをインストールしておきます。 sudo apt install python3-vcstool python3-colcon-common-extensions Ros2 For Unityのビルド https://github.com/RobotecAI/ros2-for-unity をクローンしビルドします。 以下の手順でビルドします。基本的には、README-UBUNTU.mdの通りです。 create_unity_package.shはUnityEditorがインストールされているパスを指定する必要があるので、注意してください。 source /opt/ros/foxy/setup.bash ./pull_repositories.sh ./build.sh source install/setup.bash # 重要 ./create_unity_package.sh -u ~/<path to unity editors>/2020.3.25f1/Editor/Unity ビルド後、先程のサンプルUnityプロジェクトで メニュー「Assets」→「Import Package」→「Custom Package...」から、ros2-for-unity/install/unity_package/Ros2ForUnity.unitypackage を選択し、ビルドしたパッケージを取り込みます。 これもhttps://github.com/RobotecAI/ros2-for-unity#usage に書かれた手順通りです。 Unityの再起動 Ros2 For Unity は、ROS2の実行環境に依存するため、ROS2の環境変数が設定された状態で、Unityを実行する必要があります。ターミナルから、以下のように、/opt/ros/foxy/setup.bash を読み込みUnityを再起動しておきます。UnityHubが常駐していた場合は、再起動前に一度終了させておいてください。 source /opt/ros/foxy/setup.bash ~/<path to unity editors>/2020.3.25f1/Editor/Unity 必要なスクリプトの配置 一番最初にクローンしておいた https://github.com/otamachan/ros2-unity-kvm-webrtc にある、UnitySampleRobot を、UnityプロジェクトのAssets以下にコピーします。 カメラを配置し、ROS2のgeometry_msgs/Twist型のトピックから動かせるようにする シーンにカメラを配置します 実行時に警告がでるので、「Audio Listener」のチェックをはずしておきます 「Add Component」で「ROS2 Unity Component」を追加します さらに「Add Component」で「RobotController」を追加します。これでcmd_velでカメラを移動できるようになります。 cmd_vel トピックでカメラが移動できるか、確認します。 Unityでアプリケーションを実行したあと、ターミナルで以下を実行しカメラがその場回転することを確認してください。 source /opt/ros/foxy/setup.bash ros2 topic pub /cmd_vel geometry_msgs/Twist '{linear: {x: 0.0}, angular: {z: 0.3}}' RobotControllerのコードはこちらです。 https://github.com/otamachan/ros2-unity-kvm-webrtc/blob/main/UnitySampleRobot/Assets/Scripts/UnitySampleRobot/RobotController.cs カメラの画像をROS2のsensor_msgs/Image型のトピックとして発行できるようにする HDRPで、レンダリング結果を取得したり、加工する場合は、CustomPassを利用し独自処理を書きます。今回は、Blit メソッドを使用しレンダリングテクスチャをY軸方向に反転して別のテクスチャにコピーします。別テクスチャにコピーしたあとは、RequestAsyncReadbackを利用し、GPUからCPUメモリに非同期でコピーします。 CameraPublisherのコードはこちらです。 https://github.com/otamachan/ros2-unity-kvm-webrtc/blob/main/UnitySampleRobot/Assets/Scripts/UnitySampleRobot/CameraPublisher.cs カメラのアンチエイリアスを有効にしておきます。 CustomPassVolumeを追加し、InjectionPointをAfterPostProcessにし、CameraPublisherをCustomPassとして追加します。これで、Cameraの画像がPublishされるようになります。 Unityでアプリケーションを実行したあと、Rvizでカメラ画像がPublishされていることを確認してください。 ros2 run rviz2 rviz2 シミュレータのビルド 最後に、シミュレータをビルドします。 メニューから「File」→「Build Settings...」でビルドメニューを開き、「Build」でビルドします。 ここまでで、Unityシミュレータの作成は完了です。 ↓にビルド結果をおいておきます。 https://drive.google.com/drive/u/0/folders/1QrvWHnIiJ9i2hPu0spXhjiOHHhJ8Lhsy ROS2/WebRTCプロキシノードのビルド 次は、ROS2の画像をSubscribeし、AWS Kinesis Video Streams WebRTCを使いWebRTCとして、転送するノードをビルドします。 処理の概要を説明します。 AWS Kinesis Video Streams WebRTC自体は、画像のエンコード処理が入っていないため、NVIDIA Video Codec SDKを使い、h264にエンコーディングを行いました。エンコードAPIの呼び出しに関しては、Nvidiaが提供しているサンプル実装(https://github.com/NVIDIA/video-sdk-samples )をそのまま呼び出しています。 AWS Kinesis Video Streams WebRTCに関しても、提供されているAmazon Kinesis Video Streams C WebRTC SDKのサンプルである、kvsWebRTCClientMasterをほぼ再利用して、実装しています。 ポイントとしては、 NVENCは、RGB系の入力として NV_ENC_BUFFER_FORMAT_ARGB の4byteフォーマットが必要であるため、Subscribeした画像はそのままエンコードできず、一度コピーを実施しています NVENCは、デフォルトでIDRフレームを生成しない設定になっていたので、適当なフレームで生成するような設定にしています(gopLength, idrPeriod, repeatSPSPPS)。これを設定しないと、途中から再接続した場合に、動画がデコードできなくなります。Amazon Kinesis Video Streams C WebRTC SDKは、FIR/PLIのコールバックが記述できるようになっているので、本来はこのコールバックでIDRフレーム等を生成させればよいのですが、今回はシンプルにするために、定期的に生成するだけにしています。 ros2_kvm_webrtc_sample のコードはこちらです。複数のサンプルを無理やりくっつけて実装しているので、きれいではないですが、500行程度で実装できました。 https://github.com/otamachan/ros2-unity-kvm-webrtc/blob/main/ros2_kvm_webrtc_sample/src/main.cpp ビルド方法は、適当な場所にROS2のビルドワークスペースを作り、colconを使ってビルドします。 依存ライブラリでのビルド警告がでますが、無視します。 mkdir -p ws/src cd ws/src ln -s ../../ros2_kvm_webrtc_sample source /opt/ros/foxy/setup.bash colcon build 実行には、AWSのAWS Kinesis Video Streams WebRTC周りの権限が必要なので、必要な権限をもったIAMユーザを作成し、適当にキーを用意して実行してください。 export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY export AWS_DEFAULT_REGION=ap-northeast-1 source install/setup.bash ros2 run ros2_kvm_webrtc_sample ros2_kvm_webrtc_sample_node 権限に問題がなければ、AWS Kinesis Video Streams WebRTCに「ScaryTestChannel」(SDKのサンプル通り)というチャネルが作られて、接続されます。 [KVS Master] Using trickleICE by default [KVS Master] Created signaling channel ScaryTestChannel [KVS Master] Finished setting audio and video handlers [KVS Master] KVS WebRTC initialization completed successfully [2021/12/31 12:08:10:9870] N: LWS: 4.2.1-v4.2.2, loglevel 7 [2021/12/31 12:08:10:9870] N: NET CLI H1 H2 WS ConMon IPv6-absent [2021/12/31 12:08:10:9871] N: ++ [wsi|0|pipe] (1) [2021/12/31 12:08:10:9871] N: ++ [vh|0|netlink] (1) [2021/12/31 12:08:10:9871] N: ++ [vh|1|default||-1] (2) [KVS Master] Signaling client created successfully [2021/12/31 12:08:10:9876] N: ++ [wsicli|0|POST/h1/kinesisvideo.ap-northeast-1.amazonaws.com] (1) [2021/12/31 12:08:11:0732] N: -- [wsicli|0|POST/h1/kinesisvideo.ap-northeast-1.amazonaws.com] (0) 85.593ms [2021/12/31 12:08:11:0735] N: ++ [wsicli|1|POST/h1/kinesisvideo.ap-northeast-1.amazonaws.com] (1) [2021/12/31 12:08:11:1478] N: -- [wsicli|1|POST/h1/kinesisvideo.ap-northeast-1.amazonaws.com] (0) 74.288ms [2021/12/31 12:08:11:1484] N: ++ [wsicli|2|POST/h1/r-2c136a55.kinesisvideo.ap-northeast-1.amazo] (1) [2021/12/31 12:08:11:3729] N: -- [wsicli|2|POST/h1/r-2c136a55.kinesisvideo.ap-northeast-1.amazo] (0) 224.424ms [2021/12/31 12:08:11:3735] N: ++ [wsicli|3|WS/h1/m-26d02974.kinesisvideo.ap-northeast-1.amazona] (1) [KVS Master] Signaling client connection to socket established [KVS Master] Channel ScaryTestChannel set up done GPU in use: NVIDIA GeForce GTX 1080 AWS Kinesis Video Streams WebRTCと接続確認 ROS2のトピックが、正しくPublishできているか確認します。 まず適当なUSBカメラから、image_rawトピックを発行します。 sudo apt install ros-foxy-v4l2-camera source /opt/ros/foxy.setup.bash ros2 run ros2 run v4l2_camera v4l2_camera_node 次に上述の通り、ROS2/WebRTCプロキシノードを起動します。 export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY export AWS_DEFAULT_REGION=ap-northeast-1 source install/setup.bash ros2 run ros2_kvm_webrtc_sample ros2_kvm_webrtc_sample_node AWSコンソールを開き、 該当チャネル(ScaryTestChannel)を選択後、メディア再生ビューワーで再生してみてください。 カメラ動画が再生できるのが、確認できると思います。 ROS2/xserver/CUDA入りDockerイメージの作成 上記で作成した、UnityアプリをGCPで実行するための、Dockerイメージを作成します。 ポイントとしては ホストのNvidiaドライバと同じバージョンのNvidiaパッケージをインストールします。今回は、GCPのcommon-cu110-ubuntu-2004 をホストのイメージとして使うため、予めインスタンスを一度起動し、パッケージのバージョンを調べておきました。460.91.03-0ubuntu1 を今回は使用しています。 xorg.confには、NVidiaグラフィックボードのBUS IDを指定する必要がありますが、インスタンスによって変わる可能性があるため、実行字にnvidia-smi を使用し取得し、実行時生成しています。 まず、Dockerイメージを作成し、Pushします。同等のイメージがすでに、https://hub.docker.com/r/otamachan/gcp-ros2-xserver にPushされているので、そちらを使用しても構いません。 cd gcp/docker docker build -t 適当なpush先 . docker push 適当なpush先 GCP上でDockerを使ってxserverを起動し、Unityシミュレータを立ち上げる 次に、GCPのGPU入りインスタンスを起動します。予めProjectを作成し、GPUの割当申請を済ませて、gcloudコマンドラインのインストールをしておきてください(詳細は割愛します) まず、GCPのインスタンスを起動して、Dockerを利用して、xserverを上げます。 gcp/launch_instance.shを開いて、各種設定を確認して必要に応じて変更してください。以下の内容でインスタンスを起動します。 インスタンス名: ros2-unity-xserver ゾーン: asia-northeast1-a (サンパウロ起動する場合は、southamerica-east1-cに変更します) マシンタイプ: n1-standard-4 GPU: nvidia-tesla-t4-vws, count=1 (T4を1つ使います) 確認ができたら、以下のコマンドでインスタンスを起動します。起動後から課金が始まるので注意してください。 cd gcp ./launch_instance.sh 起動すると、Nvidiaドライバのインストールや、Docker image pullと起動がされます。かなり時間がかかる(10分以上)なので、気長に待ってください。 以下のコマンドでログが確認できます。NVidiaドライバのインストール後、一度再起動されるので、注意してください。 gcloud compute ssh ros2-unity-xserver -- sudo journalctl -f 以下のコマンドでdockerでxserverが開始されていたら、起動完了です。 $ gcloud compute ssh ros2-unity-xserver -- sudo docker ps No zone specified. Using zone [asia-northeast1-a] for instance: [ros2-unity-xserver]. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 843ae3d37ea1 otamachan/gcp-ros2-xserver "bash /run.sh" 6 minutes ago Up 6 minutes 0.0.0.0:8081->8081/tcp gcp-ros2-xserver 起動ができたら、xserverに接続してみます。 gcloud compute ssh ros2-unity-xserver -- -L8081:localhost:8081 でインスタンスにログイン後、 http://localhost:8081/vnc.html にアクセスしてください。 起動したxserverにブラウザでアクセスできます。 力付きてきたので、以下徐々に追記します。 GCP上にUnityアプリとROS2/WebRTCプロキシノードを起動する WS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYを指定して、プロキシのビルド、Unityアプリの実行、ノードの実行を行います。 gcloud compute ssh ros2-unity-xserver -- AWS_ACCESS_KEY_ID=XXXXXX AWS_SECRET_ACCESS_KEY=YYYYYYYY /opt/sim/run.sh -L8081:localhost:8081 http://localhost:8081/vnc.html に起動し、シミュレータが起動していることを Webクライアントをブラウザから読み込み遠隔操作してみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity2021.2.7f1でShaderGraphのCustomFunctionがUndeclared Identifierする場合のメモ

はじめに タイトルのバージョンだと,Unity公式のドキュメントや有志のブログ記事どおりに進めても一行エラーを起こす箇所があるのでその説明. 該当箇所 Unity公式のドキュメントなどのテンプレートはこれ #define MYFUNCTION_INCLUDED #ifdef MYFUNCTION_INCLUDED { } #endif が,https://forum.unity.com/threads/custom-function-undeclared-identifier-help.1157867/ このフォーラムによると2020の時点で,#ifdefがバグで使えないらしく#if defined()を使う必要がある. ということで治すとこうなる. #define MYFUNCTION_INCLUDED #if defined(MYFUNCTION_INCLUDED) { } #endif 追記:ifdefの箇所を修正したことでエラーが消えた後,元に戻したらなんか普通に動いてた わけわかめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む