Unityと愉快なデバイス達
自分が昨年まで所属していた部活NITMicが今年はAdvent Calendarをやります!
この後も何回か書く予定ですが、とりあえず6日目を担当します
今回はUnityの開発において、どのようなデバイスを使って開発ができるかを紹介していきます
有名なデバイスから、そんなのあったんだ!みたいなものもあるかと思います
Unity初心者の方などはまずUnityで何ができるのか分からない場合も多いかと思いますので、この記事が開発のきっかけになれば幸いです
1.OculusRift,HTCVive
まずは有名なヘッドマウントディスプレイ(HMD)であるこの2つです
用途ですが、主にはゲームをはじめとしたVRコンテンツの開発です、これを通してUnityの世界を自分の視界として捉えることができます
VR開発が盛んになり、こういったHMDが様々なコンテンツの開発で用いられるようになりましたね、値段も発売当初より安くなり企業だけでなく個人の開発者でも所持している人が多くなってきた印象です
2.LeapMotion
2つ目は安価で使いやすいLeapMotionです
用途ですが、簡単に言うと手のモーションを認識です
マウスやコントローラーを介せずにそのまま手をかざし、手を動かすことで直感的に操作ができちゃいます
精度は高い・・・というほどではないですが、この値段で手のモーションを認識できるのであれば十分かなと思います(執筆時点で79ドルほど)
幾つか手のジェスチャー(例えば指でタップする)の判定があらかじめ用意してあり、初心者でもある程度のことができるデバイスですね
3.Kinect
3つ目は体のトラッキングデバイスで有名なKinectです
用途ですが、体のジェスチャーであったり、音声の認識です
3次元での認識することができるので、体全体を使ったゲームなどのコンテンツはもちろんのこと、体の支援システムや3次元復元といった研究分野でも大きく活躍しています
特に有名なのはモーションキャプチャーですね、ミクさんなどの3Dモデルに人間の動きを投影することでより柔軟な表現を可能にしています
(つい最近製造を終了してしまいました、サポートは継続するそうです)
https://developer.microsoft.com/ja-jp/windows/kinect
4.Muse
4つ目は変わり種Museです
用途はここまでとは打って変わり、脳波の状態の計測です
あまり普及はしていませんがUnityをサポートしています、公式サイトに導入の説明があるのでそちらを参考にすると良いかと
脳波を計測できるため医療分野での応用であったり、念じて操作するなど一風変わったコンテンツの開発にも使えるのではないでしょうか
ドッキリするコンテンツで脳波を計測してみるのも面白いかもしれませんね
5.Theta
5つ目は全天球カメラThetaです
用途は、360°範囲の画像や動画の撮影です
カメラデバイスであれば基本的にUnityは認識することができるのですが、その中でもインタラクティブなのが全天球カメラですね
動画配信であったり、パノラマ画像であったりと多くの映像コンテンツで応用されています
中でも360°映像のライブ配信機能は面白く、お祭りや星空、はたまた海の中の配信など様々なコンテンツに応用されています
6.Hololens
6つ目は巷で話題の光学シースルーレンズであるHololensです
用途は、光学シースルーとホログラフィー技術を用いたMRコンテンツです
これを被ることで、仮想空間を映し出すのではなく、あくまでも現実空間にホログラム映像を投影することができます
MR(Mixed Reality)の名の通り現実空間を仮想空間を織り交ぜたコンテンツが作成可能で、とても未来的なデバイスだと思います(あんまり言うとxR警察に怒られるのでこのぐらいの説明で)
ただ、値段が少しネックで個人開発者(特に学生)には若干厳しいものとなっています
https://www.microsoft.com/ja-jp/hololens
7.UnlimitedHand
7つ目は個人的に可能性を感じるUnlimitedHandです
用途は手の筋肉の動きの検出、そして電気刺激による触覚のフィードバックです
簡単に言うと手を動かして触覚や衝撃を感じ取ることができるコントローラーです、電気信号なので疑似的ではありますが触覚型のコントローラーは珍しく大変価値のあるものだと思います
仮想世界の物に触れるといった、近未来的な発想を実現させる夢のあるデバイスです
8.Tobii
最後のデバイスは学術分野での活躍が目立つTobiiです
用途はアイトラッキング、視線の計測です
使えないわけではないですが、ゲームだと若干用途が限定されてしまうかなと思います
先述の通り研究分野で活躍しており、中でも行動観察の分野では多く使われている印象です(スポーツ心理学など)
視線を活かしたコンテンツは少ない印象を受けるので、何かアイデアがあれば応用してみたいですね
いかがでしたでしょうか、長くなってしまいましたが、これらが自分が開発経験があったり興味を持っているデバイス達です
勿論紹介した以外にも様々なデバイスとUnityでの開発の試みがあります
インタラクティブなデバイスとUnityは相性がよく、ゲームだけでなく様々なコンテンツ開発にも応用できるのがわかったかと思います
みなさんもデバイスを用いて、新しい発想のアウトプットをしてみてください
UniRxとPhotonを用いた同期の実装
少し前からUnityの開発を手助けするライブラリとして有名になっているのがUniRxです
そしてオンラインゲームの開発エンジンとして有名なPhotonというものが存在します
今回はこの2つの軽い紹介と、実際に用いて同期ロジックを組み上げてみたものを紹介していきます
UniRx
詳しく説明するとキリがないのでかなりざっくりと説明します
イベントや非同期処理を時間を軸としてフロー化し処理することで、ReactivePrograming(RP)を実現させている実装ライブラリのことをReactiveExtensions(Rx)と呼びます
元は.Netが提供していたライブラリでしたが、様々な分野で評価され、jsやSwift、kotlin等さまざまな言語でも実装がされています
これをUnityリファレンスに向けて作ったのがUniRxというわけです
Photon
Unityをはじめとして 様々なクロスプラットフォームで活躍するネットワーキングエンジンです
マルチプレイヤーでのマッチングシステムや同期などを簡単に実現することができるため、数多くのゲームで採用されています
Unityでマルチプレイヤーのゲームを作る際の定番ですね
実装
説明も軽くしたところで実装です
今回UniRxやPhotonのリファレンス自体の説明は省きますのでその点は自己補完よろしくお願いします
また、簡易的な仮実装なので実際はその開発環境や設計に沿った実装になります
using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx; using Photon; using Events; public class PhotonRPCManager : Photon.MonoBehaviour { [SerializeField] private PhotonView _photonView; public Subject<TestInfo> OnRPCTest = new Subject<TestInfo>(); // イベントストリーム public void TestRPC(int damage, int time, int id, TestType type) { //外部からこれを呼ぶ _photonView.RPC("testRPC", PhotonTargets.All, damage, time, id, type); } [PunRPC] private void testRPC(int damage, int time, int id, TestType type) { //実処理、イベント通知 OnRPCTest.OnNext(new TestInfo(damage, time, id, type)); } }
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Events { public enum TestType { enemy, self } public struct TestInfo { public readonly int Damage; public readonly int Time; public readonly int Id; public readonly TestType Type; public TestInfo(int damage, int time, int id, TestType type) { Damage = damage; Time = time; Id = id; Type = type; } } }
まずはRPC管理側の実装です
Subjectでイベントを管理、RPCでイベントを発行していきます
今回は適当な構造体を作って発行しています
using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx; using Events; public class TestModel : MonoBehaviour { [SerializeField] private PhotonRPCManager _manager; [SerializeField] private PhotonView _photonView; private int _damage = 100; private int _time = 10; private int _id = 0; private void Start() { _manager.OnRPCTest .Where(info => info.Type == TestType.self) .Subscribe(info => Debug.Log(string.Format("{0}:{1}:{2}", info.Damage, info.Time, info.Id))) .AddTo(this); } private void Update() { if (Input.GetKeyDown(KeyCode.A)) { Debug.Log(string.Format("EnemyEvent:{0}",_id)); _manager.TestRPC(_damage, _time, _id, TestType.enemy); UpdateInfo(); } if (Input.GetKeyDown(KeyCode.B)) { Debug.Log(string.Format("SelfEvent:{0}", _id)); _manager.TestRPC(_damage, _time, _id, TestType.self); UpdateInfo(); } } private void UpdateInfo() { _damage += 100; _time += 10; _id++; } }
続いて同期するモデルです、同期するモデル自体にはPhotonViewをアタッチせずに、ストリームを購読することによって同期を行います
また、購読の際にフィルタリングをかけて一部のイベントだけをトリガーとして処理するようにします、今回は仮のenumによって判定を行います
ついでにイベントも簡易的に設定しました、今回は特定のキーを叩くだけですね
今回はかなり簡易的に作っているので考慮がありませんが、フィルタリングやイベント通知に際して通知者をしっかりと設定する必要があります
(同期して生成した場合に1回のイベントを多重に呼ばせないため)
一応結果です、Aキーを押した場合はイベントは起きていますがフィルタリングではじかれているため処理が呼ばれません、しかしBキーを押した場合は処理が呼ばれていることがわかります
このようにして同期オブジェクト側は、適宜同期イベントをストリームから購読し同期処理を行うといった仕組みになります
簡易的かつ最低限の実装ですが、形としてはこういった感じですね
利点や問題点
まず、利点です
一番大きいのは同期処理をストリームとして処理できることでしょう
マルチプレイにおける同期処理では様々なイベントが飛び交い、それらを正しく処理していく必要があります
その際、ストリーム処理であれば、送られてきたイベントから様々な加工を施し処理を行うことができます
遅延処理や、待機処理などを手軽に柔軟にできるのでこの点では大きく助かりますね
次に問題点です
まず本質から外れた問題点ですが、単純にRxを導入するのに様々なコストがかかること
パフォーマンス面もそうですし、導入コスト自体もあります
そして設計の本質的な問題点ですが、様々なイベント処理に対応するにあたり機構が肥大化していくことです
様々なパラメータが存在し、同期イベントが増えるたびにストリームとそれに呼応したメソッドを用意していかなければなりません
共通パラメータが存在するのであれば複数イベントのストリームをまとめてしまいフィルタリングして取得することもできますが、結局ストリームが大きくなりすぎて発行するたびに無駄な記述も増えていくでしょう
これは仮組なので大した肥大化はしないように見えますが、大規模な開発形態になりRPCが複雑に絡まってくるとこういった問題は浮き彫りになってきます
以上をふまえて、開発のケースに合わせて適宜使っていきたいですね
間違っているところの指摘や、これに対するコメントなどお待ちしております
UnityにおけるDIパターンとZenject
DI、プログラミングにおける大事な概念の一つです、最近ちょこちょこ言葉を聞く機会が多くなったなと感じています。(PHPなどを触る方はご存知の方が多いでしょう)
自分も最近仕事の都合で、Unity+DIといった形態の開発をしています、まだ広まりが浅い気はしますが。
今回はその仕事の上で得た知見をまとめていこうかなと思います
DI(Dependency Injection)
そもそもDIってなんやねん、と
DIとは「Dependency Injection」の略で、直訳すると「依存性注入」という意味になります。
ゲーム界隈ではあまり聞きなれませんが、思想自体は結構前からあるものです
最近だとポケモンGOでDI思想によるアーキテクチャが採用されていたといった事例があり、Unity界隈では今ホットになってきたのかな?って感じがあります
実はこれ直訳するのはよろしくなく、あくまでもDependencyを注入するといった方が正しいのですが・・・まぁそれはさておき
じゃあDIってのはどういうことなの?と、いくつかのサイトでも紹介されていますが簡単に言うと「オブジェクトの提供」です(提供って書くと厳密には違う気がしなくもないですけど)
ある機能があったとして、それがあるオブジェクトのメソッドなりなんなりを使って機能している、これが一番簡単な「Dependency」です
hogeクラスがhugaクラスを持っていて、ある関数をhugaクラスの関数や変数に依存して使ったり、そんな感じ
今回はどちらかというとDIパターンとはというよりDIパターンをUnityで応用してみた、という試みのメモ書きなので、分からない人は調べてみてください
DIとUnityとZenject
じゃあDIをUnityでどう使うの?と
もちろん明示的に仕組みを組んで注入することはできますが、できればスマートに、労力なくやりたい
ってことで既存のUnity用DIフレームワークを使用します
今回使用したのは「Zenject」という無料のフレームワークです
ぶっちゃけC#用のDIフレームワークってちらほらあるんですけど(NinjectとかRx制作者のやつとか)Unityをターゲットにするとなるとあんまりないんですね
その中でもZenjectはかなりいい感じに使えるかなと思ったのでまとめていこうかなと
とはいっても紹介するのは触りだけです
まずヒエラルキーにSceneContextを用意しましょう、これはInstallerを管理したりするもの、と考えてもらえば大丈夫です
ヒエラルキーにオブジェクトを追加する感じでZenject→SceneContextというメニューがあると思うので、その手順でSceneContextを用意します
SceneContextはこんな感じ
次にInstallerを用意します、これは所謂注入係です
適当なスクリプトにMonoInstallerを継承しておけばよいです(ここについては後で少し捕捉します)
今回は何も書きませんが、ここでInstallBindingをoverrideし、注入についての設定などをしたりします
これを適当なオブジェクトにアタッチしておいてください、今回はInstallerというオブジェクトを用意しました
アタッチしたら、このInstallerを先ほどのSceneContextのInstallersの部分に入れていきます
では次に流し込むものを用意していきましょう、今回は適当な変数を持ったモデルを用意しました
これを適当なオブジェクトにアタッチして、ZenjectBindingというコンポーネントをアタッチしていきます
ZenjectBindingはBindingとついているように、流し込むコンポーネントをBindするコンポーネントです、Componentsの部分に今回流し込みたいGameModelを入れることでZenject側が自動で流し込む設定を行ってくれます
そしてこのGameModelを提供してもらうクラスを用意します(今回はReaderとしました)
受け取りたいクラスを変数として用意して、アトリビュートで[Inject]とつけてあげます
こうすることで、Zenject側が自動でこの_modelに、Bindingで指定したGameModelを流し込んでくれるようになります
SceneContext,Installer,GameModel,Readerの4つのオブジェクトがそろったところででは再生をしてみます、すると!
流し込んだ値を取得することができました!
このようにして、Injection周りを自分で書くことなく、オブジェクトを取得できるのです
Bindするためにシーンに置いとかないといけないじゃん、動的にやるにはどうするの?という疑問がわくかと思いますが、そもそもInstallerを用いた注入はあくまでもラップされた方法であり、動的にBindして注入することも全然可能です
その際コンテナにBindしてそれを流し込む(Resolve)といった一連の流れをケースに合うように書かないといけませんが・・・、まぁそれもそんなに手間じゃないので正直シングルトン参照とかよりよっぽど散らからないと思います
また、先ほどスルーしましたがSceneContextにあるPrefabInstallerならPrefab,ScriptableObjectInstallerならScriptableObjectをBindして流し込めちゃいます
Zenject様様ですね
Unity設計におけるDI思想
ここまでDIをUnityで!みたいなことを書いてきたわけなんですが実際どう有効なのか
例えばUnityで様々なゲームモデルのパラメータを扱うとき、よくシングルトンのマネージャクラスなどで置き、そこから取得するといった方法がよく使われると思います。
確かにシングルトンは便利なんですが、実質グローバルであり、かなりの密結合だといえます
依存関係なども分かりづらくなり、オブジェクト指向的な観点からも、おや?となる部分が多くなってしまいます
そういった点で、DI思想というものはグローバルにアクセスするのではなく、あくまでモジュール単位に切り離してオブジェクトを渡せば良いため、密になる部分を減らすことができます。
より良い設計を、より柔軟な設計を、と追い求めていくならば、Unity設計において知っておいて損はないと思います(かといってなんでもかんでも注入すればいいじゃんとなってコンポーネント思想がぐらつくのはあれですが)
正直自分も学びたての設計なので、突っ込みどころがあるかと思いますが、その時はコメントでガンガン突っ込んでください、勉強になります
後、DIといえばパフォーマンス関係も気になるところで、その点においてRx制作者の方が作っていた「MicroResolver」も試してみたいところですね
長ったらしくなりましたが、ようはZenject使うと捗るよってことです
意見やコメントお待ちしております
参考にしたリンク
DIとは?DIコンテナとは?試してみた(前編)[PHP][DI] - あざらし備忘録。
DI・DIコンテナ、ちゃんと理解出来てる・・? - Qiita
neue cc - MicroResolver - C#最速のDIコンテナライブラリと、最速を支えるメタプログラミングテクニック
技術メモの始まり
最近ゲームを作っていくうえで色々な人の話を聞きます。
その中でもやはり、継続したアウトプットの力強さをひしひしと感じる機会が多いですね、毎日コミットやgitstarやアプリリリースなど・・・
そんな中で自分も少しずつ何かを発信していきたいなと思い、メモを書くことにしました
Unity中心になるかと思いますが少しでも継続して技術を発信していきたいなと思います