N煎ログブログ

n番煎じと言っても過言ではない今更な、でも個人的に躓いたUnityやUE4等での開発についての云々を書いていきます

【Unity5】VRゲーム開発に役立つかもしれないアセットを二つ開発したので紹介と解説

目次

前置き

こちらの記事は Unity 2 Advent Calendar 2016 の17日目記事です。

前回はWheetTweetさんの「Unityのコミュニティ活動でMicrosoft MVPを受賞するには?」でした。

VRゲームを実際に作ってみて感じた個人的難点

以前、私個人でHTC Viveを用いてVRゲームを開発してみたことがあります。

ゲームについてはこちら。

isemito.hatenablog.com

で、実際にVRゲームを作ってみて、個人的に「ここは難点だな」と感じた点がありました。

1.  デバッグ

VRゲームを実際に作ってみて、特に不便に感じたのはデバッグでした。

というのも、Unityでは「Debug.Log(出力内容)」で出力させた文字列はUnityエディター内「Console」ウィンドウに表示されるからです。

その為、HMDを付けていては、いつどのタイミングでエラーを吐いたのか、今このタイミング(フレーム)でどのような値になっているのかが瞬時に把握できませんでした。(uGUIで画面内に表示させているなら別)

2. 照準処理

VRゲームやアプリケーションは、私が見聞き、また実際に経験した限りでは、ViveやOculusTouchといったコントローラを使用した銃などで何かを狙い撃ちするゲームでは、点やレーザーポインタが使用されることが多いか、そもそも照準が存在しないようです。しかしこれは「慣れが必要とされ、うまく扱うには多少なりとも時間がかかるな」という風に感じました(あくまで私個人の経験で)。

アセットその1:VRDebugConsole

デバッグに関して、Unity標準のConsole画面と似たようなものを実際のGame画面にも表示したい。そして位置やサイズも自由にさせたいと思い、「VRDebugConsole」なるアセットを開発しました(Collapse切り替えはまだ実装できてません)。

20161205191112

こちら、ウィンドウのスケーリングにも対応しており、ビルボードの設定も行う事が可能となっていますが、まだその程度の機能しかありません。またドラッグの処理もまだ実装できていません。クリック的動作のみ。

f:id:isemito:20161209144825g:plain

下動画は上動画よりちょっと前の開発中の奴ですが、ちゃんとLog,Warning,Errorで表示切替が出来るようになっています。

gyazo.com

そのうち座標や回転のバインド処理も追加しようかなと思っています。

後は透明度とか色々設定できるような開閉可能な設定画面とか取り付けられたらいいな。

使ってみる

こちらからアセットをダウンロードできます(今までGithubで管理していなかったので作業途中からのリポジトリ作成で申し訳ないですが...)。

github.com

使い方

  1. Asset/VRDebugConsole内の「VRDebug」プレハブをInspectorないしSceneビューに置きます。
  2. おしまい

後は、内部で勝手にカメラを取得し、ウィンドウを生成します。

位置が気に入らない場合はSceneビューで大きさを変えたり位置を上下なり左右なり動かして任意の位置にもっていくことが出来ます。

EventSystemに自動追加された「VRDebugInputModule」にポインタとみなすオブジェクトをアタッチすれば、ViveHomeのようなポインタを使用することができ、ウィンドウを操作することができます。

中身の話1:オブジェクトプール

このアセットはオブジェクトプールアルゴリズムを使用しており、ログが高速で増加することによる生成と破棄(増えすぎ防止のため)を極力行わないようにしています。検証は行っていませんが、GCによるスパイクは起きにくくなっているかと思われます。生成の上限数は「VRDebug」プレハブにアタッチされている「VRDebugLogReceiver」コンポーネントから指定できます。

アルゴリズムはこちらにて紹介されていた方法を参考にしました。

tsubakit1.hateblo.jp

中身の話2:Debug.Logの情報を取得する

このアセットは、DebugLogの情報をイベントハンドラに登録して取得しています。

コード全体は以下。

70行目で作成した関数を28行目でハンドラに追加しています。

Application.logMessageReceived += LogCallBackHandler;

イベントハンドラに登録する関数の引数は、「Log(ログ本文)」「StackTrace(ログに関する情報)」「Type(ErrorやWarning等のログのタイプ)」の三つ。

void LogCallBackHandler(string _log, string _stackTrace, LogType _type){

  //処理させたい内容

}

後は、取得したログ情報を基に値を渡したりしてやるだけです。

処理についてはこちらの記事を参考にしました。

takashicompany.hatenablog.com

中身の話3:オリジナルディレクティブ追加によるコンパイル回避

このアセットはViveに限らず使用できるようにしたかったので、SteamVR(OpenVR?)に依存しないような作りにしています(ただしVive環境以外の環境がないので動作検証は行えていませんので動作の保証はできません)。

その為、Viveを接続している場合は任意のコードをコンパイルし、されていない場合はコンパイルしないという仕組みを作るためにディレクティブという仕組みを利用しています。その処理を行っているのが、40~44行目で行っている処理です。

AssetDatabase.FindAssets("t:script SteamVR").Length

でSteamVRアセットが存在するかどうか(インポートされているかどうか)を確認し、存在すれば、「EXSISTENCE_STEAM_VR」ディレクティブを追加します(EXISTENCEの誤字ですいつか修正します)。

UnityではPlayerSettingからオリジナルのディレクティブを追加することが出来ます。

//現在のディレクティブシンボルを取得

PlayerSettings.GetScriptingDefineSymbolsForGroup(ビルドターゲット);

//ディレクティブシンボルを設定

PlayerSettings.SetScriptingDefineSymbolsForGroup(ビルドターゲット, シンボル名);

ディレクティブについてはこちらの記事を参考にしました。

smartgames.hatenablog.com

中身の話4:VRコントローラの入力

についてはUnity 2 Advent Calendar 2016 の18日目記事にて公開次第更新します。

書きました。

isemito.hatenablog.com

アセットその2:ConeCollider

 照準処理に関して、(個人的に)点やレーザーの何が難しいかって点であるが故に遠くのものを狙いにくく、少しの手のブレで照準が外れてしまうことにありました。というわけで、点やレーザーではなく、「範囲」で対象を取得できるように「ConeCollider」なるアセットを開発しました。

どういうことかというと、Unityに標準で用意されているRayCastやSphereCastでは、距離と感知範囲が比例しないので、遠くなればなるほど、Castの感知範囲はCameraから見て相対的に小さくなります。その為、ConeColliderを作って、疑似的な視界のような円錐型の感知範囲を作ってしまおうという事です。

f:id:isemito:20161209153917p:plain

下動画はAssetStoreに出そうとして投稿したものの一度はじかれてそのままの奴です。


ConeCollider1.0 Samples for Unity3D

動画にもある通り、距離に比例して範囲も広がるので、「ライトの光に当たっているかどうか」だったり、「人の視界に入っているかどうか(MGSみたいな)」を判断するのにも活用することが出来ると思います。

使ってみる

github.com

使い方

  1. 「ConeCollider」コンポーネントを、コライダを付けたいオブジェクトにアタッチします。
  2. おしまい

後はそのまま再生すればコーン状のコライダが自動生成されます。

設定できる項目は「半径」と「距離」と「トリガー判定」。シーンビューでは円と線で生成される位置や大きさ、回転を確認することができます。

gyazo.com

gyazo.com

衝突判定は今まで通りのOnCollision~()やOnTrriger~()関数で取得することができます。

中身の話:コライダを生成する

ConeColliderとは言っても、ただ単純に事前にBlenderとかで作っておいた1x1x1サイズのコーンモデルを用意しておき、Unity再生時にAngleやDistanceに合わせてポリゴンを頂点移動させ、MeshColliderをアタッチしているだけです(MeshColliderは非表示にしています)。なので実際に衝突判定を行っているのはMeshColliderになります。

補足

ちなみに、VRDebugConsoleアセットに関して、AssetStoreには

Technie Virtual Console - Asset Store

というものが既にあり、さらに

Unity Advent Calendar 2016 - Qiitaの4日目で紹介されている、凹みさんが作られたWindowsデスクトップ画面を表示するプラグインもあります(あれ、自分のAssetいらない子?)。

tips.hecomi.com]

宣伝

改めて宣伝です。

今回紹介したアセットを活用して一つ個人でVRゲームを制作してみました。

というより、今回開発したこれら2アセットはこのVRゲーム制作中に同時制作してました。

isemito.hatenablog.comVRだけでなくPCでも遊べます。

また上記ゲーム内ではこちらの自作アセットも活用しています。

isemito.hatenablog.com

【Unity5】超軽量な経路探索アルゴリズム

別記事で紹介した自作UnityAssetの「Advanced Missile」

isemito.hatenablog.com

このAssetの処理の中には、障害物を回避するための自作経路探索アルゴリズムが入っています。今回はそのことについて書こうかと思います。

と言ってもすでにSlideShareにあげているのでそちらを参照してもらった方がずかいもしているので分かりやすいかと

www.slideshare.net

【Unity5】初の個人製作VRゲームを作りました

以前(2015年)私はJapan VR Hackathon2015に参加させていただいたことがありました。

その際初めて触れたのがHTC Viveでした。

その際はあまりVR関連の処理に振れることが少なかったのですが、とある事情で再びViveのゲーム開発を行う機会が巡り、今度は個人で開発を行ってみました。

動画と本体

ゲーム本体はこの動画の説明文のリンクから

ニコニコ

Youtube


【VR(PCも可)】Victor Driver【HTC Vive】

製作期間はおよそ2ヵ月ほど。

しかしこの開発間に二つのアセットを開発したので実際はもっと短いかと思います。

PC版とVR版で遊べるように用意してるので、ぜひダウンロードして遊んでみてください!

バグが多いのはすみません!

【Unity5】ミサイルを簡単に作れるアセットを開発しました

ミサイル...いいですよね。

ゲーム作ってると、ミサイルとは言わなくとも何かしら飛翔する物体はあるもので、それらの挙動をいちいちプロジェクトごとに作り直したり、また微妙に違う挙動をさせたかったりすると色々と手間がかかります。

そこでこんなものを作ってみました(公開自体は5月ごろ)。

AdvancedMissile - Asset Store

これは主にミサイルの挙動、もしくはその他飛翔関連などの挙動を簡潔に設定可能なアセットです。

設定項目

現バージョン(Ver2.2.1)で設定可能な項目としては、

  • 死亡時間
  • 自然落下
  • コリジョン
  • ターゲット検索
  • 障害物回避
  • ターゲット座標、軌道ズレ
  • 移動方法、速度、角度
  • 効果音
  • エフェクト
  • 衝突後イベント発火

の計10項目。

動画

実際に動かしているのがこちら(AssetStore内動画より)


AdvancedMissile2.2.1 DEMO for Unity3D


AdvancedMissile2.2.1 Samples for Unity3D

エディタ拡張を活用した見た目の最適化

設定は項目ごとに階層構造となっており区分けされているので見た目的にもすっきり。

 

 gyazo.com

 

gyazo.com

まだ修正の余地がたくさんありますが結構自由に動きを設定できるので汎用性は高いと自負しています。

余談

余談ではありますが、ミサイル処理の実装方法について、こちらで解説を行っています。

www.nicovideo.jp

更新予定

今後の更新予定としては、

を追加予定。

アルゴリズム

二つ目の動画内で紹介しているAvoidance Missileのアルゴリズムについて書きました。

isemito.hatenablog.com

isemito.hatenablog.com

【Unity5】エディタ開発(拡張でない)において注意すべき事

皆さんエディタ拡張はしているでしょうか。

しかし今回ここで話すのはエディタ「拡張」ではなく「開発」についてです。

具体的には、エディタ上で編集した値の保存についてです。

 全ての総称として「拡張」と呼ばれていますが、わかりにくいので、私的な区別ですが「拡張」は既にあるUI上の見た目等を変更、改変するのを拡張と呼び、それに比べ、新規にウィンドウやUIを配置して自作のツールを制作するのが「開発」だという体でここでは述べていきますのであしからず。

また、Hierarchy上やProject上のアセットやオブジェクトを直接弄るようなものではなく、エディタ内部の値を保存させる方法について話していきます。

シリアライズ

Unity上では値をシリアライズすることによってその値を永久なものにするようになっています。

具体的には、以下の書き方で変数をシリアライズします。

public int hoge1;

もしくは

[SerializeField]

int hoge2;

publicな変数は自動的にシリアライズされるため、なにも考えなくても良いですが、publicをつけていない、もしくはprivateな変数の場合、「[SerializeField]」を変数の上行へ付ければシリアライズされるようになります。

シリアライズしていないとどうなるか?

シリアライズしていない変数の場合、Unityの再生ボタンを押した際に値が初期化(nullに)されます。これは、Unityがエディタを再生させた際にシーンをロードしているためだそう。

そのため、privateな変数等のシリアライズされていない変数は、Start関数やAwake関数で初期化するか、null判定するなどして対処しなければ、思わぬところでnullエラーを吐かれることになります。

ところがどっこいおもわぬ罠

 さて、これで自作エディタで色々した後、再生すればエディタ内部で編集した値が保存されると思いきや、そうはなりません。

Editor,EditorWindowを継承しているクラス上の変数は、publicだろうが[SerialiseField]を付けていようがお構いなしに値を初期化してくれやがります。

Hierarchyに置かれていなければ保存されない

 実は、publicや[SerialiseField]を使用してシリアライズして値を保存してくれるのは、Hierarchyに置かれているオブジェクトのコンポーネントのみです。細かくいえば、MonobehaviourやScriptableObjectを継承しているクラスです。

つまり、EditorやEditorWindowを継承しているクラスはどうあがこうがそのクラス内部の値は初期化されてしまいます(Hierarchyにも置けない)。

さらに、例えMonobehaviourを継承していても、やはりHierarchyに置かれていなければ、どうやら初期化されてしまうようです。

エディタ開発で何が困るの?

さて、これらを述べたうえで、値の初期化がなぜ困るのか。

一番困るのは、Hierarchy上やProject上のアセットやオブジェクトを一時的に弄り完結するようなエディタではなく、エディタ内部で値をUnityの再生/停止を跨いで長期的に扱う必要のあるエディタです。

例えば、わかりやすいのが、よくある状態遷移エディタあたりでしょうか。

あれは(アセットによりますが)再生する前にエディタ上で「ノード」と呼ばれる矩形のUIを配置して、それらを線でつなぎ、再生時の動作を作成/制御するというものですUE4(UnrealEngine4)のBP(BluePrint)がそれです。

しかし、エディタで作成したノードや線の情報をEditor/EditorWindow継承のクラスで管理すると、Unityを再生した際に初期化されてnullになり、画面は消え、コンソール画面には何行ものnullエラーが並びます(ツライ)。

その為、エディタ上で編集した値を保存したい場合には、MonobehaviourもしくはScriptableObject継承のクラスを用いて、Hierarchyに配置、もしくはScriptableObjectのアセットを作成するなどして値を保存するようにしましょう。

【Unity5】名前指定でAddComponent

過去『AddComponent("クラス名")』でコンポーネントを追加することができましたが、既にそれは『[使用しないでください]』と注意されるように、使用することが禁じられています。

 

じゃあ名前文字列で指定して追加するにはどうすればよいかというと、『Type.GetType(string typeName)』関数を使用します。

これは、文字列指定したクラスをType型にしてくれる関数です。

AddComponentを文字列で使用することは出来なくなりましたが、Type型で指定することは可能です。

具体的には、

using System;

(略)

this.AddComponent(Type.GetType("追加したいクラス名")) ;

で可能。

注意するべき点として、「namespace」を使用している場合は、キチンと「(namespace名).(追加したクラス名)」と書かないとnullを吐きます。

 

私だけかもしれませんが、ここ地味に躓きました。

しかもよく見れば引数の一覧?にちゃんと「Type型使えるよー」って教えてくれてるんですよね。なぜ気づけなかったのか。