N煎ログブログ

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

複数のフラグを1つの変数のみで管理する

目次

はじめに

先日、ある場でプログラミングにおける「フラグ」の扱いについて学ばせていただいたことがあったので、自分の中での復習として、またこの記事を見てくれた読者の方の学習の一助となればと思います。プログラミングに達者な方は「え?そんなの当たり前でしょ?今更?」な話になるかもしれません。ブログタイトルが如し。

フラグとは?

まず、フラグ(flag, 旗)とは何なのかという話ですが、これは単に「〇〇を満たしているか否か」であると考えて行きます。つまり「ONかOFFか」です。

フラグ情報は一つに

今までの私はフラグの管理を以下のように定義、使用していました。

bool flagA; //int の場合もあり

bool flagB;

bool flagC;

...

 しかしこれは必要になった分だけ変数が作られるため非常に非効率であり、管理しにくくなります。定義した変数だけで何十行も使用するなど、余程のことでもない限り避けるべきでしょう(私も人のことを言えた義理ではありませんが...)。

そこで、この対策としてどうするかと言いますと「全フラグを一つにまとめてしまう」事です。具体的には、

unsigned int flags;

の一行で大体のフラグ情報は事足りるようになります。何故かは順を追って説明していきます。

bool[] flags; //intの場合もあり

これでも可能ですが、使用するたびに配列要素の検索・アクセスというオーバーヘッド(ざっくり言えば余分な処理のこと)が発生するため、すっきりするかと言うと怪しくもあります。

[フラグ] = [ON or OFF] = [2通り] = [0 or 1]

 冒頭で、フラグ情報はONかOFFで考えると述べました。つまり、1つのフラグ情報の持つパターンはON,OFFの2パターンしかないのです。そこで、それらフラグを「0 or 1」で考えてみることにします。つまり、一つのフラグは

OFF:0 / ON:1

 となるわけです。

0と非0

ここで余談ですが、boolも内部的には「0 or 1」で処理されています。しかし正しくは「0 or 非0」で処理されています(boolに限らず)。ここで「非0」と言ったのは、「0でない時『正』とみなされる」からです。プログラム本等でwhileを用いたループ処理のコードで、

while(1){ 略 }

というのを見たことないでしょうか。これは「非0」が「正」であるという仕組みを利用しているのです。その為「1が正である」と勘違いされがち(私もそうでした)なのかもしれませんが、実際は「0以外が正である」というのが正しい認識と言えると思います。

その為、bool値をインクリメントした場合、その時点で0ではなくなるため、ifで処理したとき「正」とみなされます。しかし逆に、デクリメントした場合は「偽」とみなされる保証はありません。なぜならば、「非0」が「正」となるため、値が「0」でない可能性もあるからです。

参考:bool (C++)

フラグの2進数化

さて、フラグを「0 or 1」にすることでどのようなメリットがあるのでしょうか。それは「フラグ情報を2進数化することができる」ということに尽きるでしょう。

2進数とは、0と1の集まりです。

www.pc-master.jp

つまり、例えば「生きているか(aliveFlag)」「移動しているか(moveFlag)」「回転しているか(rotateFlag)」「ジャンプしているか(jumpFlag)」等といった情報を、

aliveFlag, moveFlag, rotateFlag, jumpFlag 

true, false, false, true

1, 0, 0, 1

1001

と置き換えることができるのです。

 「1001(2進数)」は整数になおすと「9」です。4つのフラグ情報がたったこれだけになります。整数にできるということはint変数に格納することができます。これが、フラグの2進数化の魅力と言えるでしょう(実際にはマイナス情報はいらないので、符号情報を消すunsingedを付けます)。

フラグ情報へのアクセス

フラグ情報を整数にする仕組みはわかりましたが、ではフラグ情報の追加や、整数からフラグ情報へのアクセスはどのように行うのでしょうか。

ここで「ビット演算」を用います。

ビット演算についてはこの(複雑な演算子)サイトがわかりやすいかと。

フラグの作成(フラグを立てる)

フラグをまとめるには、もちろん1つ1つのフラグ情報が必要です。では、そのフラグ情報を作成するにはどうするかというと「ビットシフト」という仕組みを用います。具体的には、

1 << n

で作成します。これは「1」、つまり「0001」という基本のビット情報を「指定整数値分左にずらした」ビット情報を作成するということになります。つまり、前項での「生きているか(aliveFlag)」「移動しているか(moveFlag)」「回転しているか(rotateFlag)」「ジャンプしているか(jumpFlag)」といった情報を例にとると、

1 << 0

であれば、「0001」つまり「jumpFlag」をtrueにし、

1 << 1

であれば、「0010」つまり「rotateFlag」をtrueにすると言った感じで情報を作成できます。

フラグの格納

フラグ情報の作成はできるようになりましたが、では、肝心の「情報を1つにまとめる」にはどうすればよいでしょうか。

ここでビットの「OR演算」というものを使用します。

例えば、以下のような情報があるとします、

1 << 0 => jumpFlagをtrue

1 << 3 => aliveFlagをtrue

 これら情報をunsigned int 型変数「flags」に格納するには、

flags = 0;

flags |= 1 << 0;

flags |= 1 << 3;

 と記述することで各情報を合成し、以下のような流れで格納させることができます。

1. 0000    //flagsを0で初期化(本来はunsigned int なので32個の0がある)

2. 0001    //1 << 0情報(jumpFlagをtrue)を合成

3. 1001    //1 << 3情報(aliveFlagをtrue)を合成

フラグの比較

では、実際に作成したフラグ情報の中に、あるフラグが立っているかどうかを確認するにはどうすればよいでしょうか。

ここでビットの「AND演算」というものを使用します。

 例えば、先ほど作成した変数flagsに格納されている値があるとすると、

flags & 1 << 0    //0001となる

flags & 1 << 1    //0000となる

flags & 1 << 2    //0000となる

flags & 1 << 3    //1000となる

となります。

あとはこれらをif文内で用いればよいわけです。

しかし注意すべき点として、if文の条件式では0か非0かで判断するとは言っても「0以外の値を無条件に受け入れる」というわけではないので、

 if(flags & 1 << 3)

ではなく、

if((flags & 1 << 3) != 0)

等と言うようにきちんと書いてやる必要があります。

(補足)

C++では「 if(flags & 1 << 3)」でも通るようです。

とはいえ関数の返り値をintではなく「bool」にしてると言った場合にはやはり必要となります。

フラグの削除(フラグを折る)

既に存在するフラグ情報から、任意のフラグを折りたい場合はどのようにすればよいでしょうか。

フラグを折る場合は、複数の演算子を組み合わせる必要があります。具体的には、

flags &= ~(1 << 3)

とすれば、以下のような流れで指定したフラグの情報を0(false)にすることが可能となります。

・flags                        //元フラグ                        1001

・1 << 3                     //折りたいフラグ      1000

1. ~(1 << 3)                //折りたいフラグの反転 0111

2. flags &= ~(1 << 3)  //AND演算(1001 & 0111)  0001

まとめ

ビット演算によるフラグの管理の方法をまとめました。

自分自身、最近になるまで演算の方法をよく知りませんでしたので良い勉強になりました。過去の自分のソースコードをぶん殴る機会も出てきそうですね。

また、今回の記事では通しでunsigned int型を使用していましたが、フラグ情報が32個もいらないという場合はもっとビット数の少ない変数で管理するとメモリの節約にもなるかと思われます。

どの型がどのくらいのサイズなのかはここで見れます(1バイト = 8ビット)。

データ型の範囲

【Unity5】エディタ拡張を複数のクラスに渡って記述する方法

目次

はじめに

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

エディタ拡張を使えばある程度自由にInspectorの見た目を変えたりEditorWindowを自作したりできますよね。

でも機能拡張の処理が複雑化したり、処理そのものが長くなってくると、どうしても全体のコードの長さが膨大なものになってしまいます。

そこで拡張処理を機能ごとに複数に分けようと考えるわけですが、そこでいくつかの問題がありました。

処理を分けたい時の問題点

問題1.処理クラス:拡張クラス=1:1 の仕様?

初めは私も、「一つのファイルのソースコードが長くなるのなら複数クラスに分ければいいじゃない」と考え、各処理ごとに拡張クラスを作成(変数定義は実際にオブジェクトにアタッチするクラスに)。各クラスに「CustomEditorアトリビュート」を付与させてみました。

docs.unity3d.com

しかしなんということでしょう。

古い拡張クラスの処理は新しい(後に処理された)拡張クラスに上書きされ、最後に読み込まれた拡張処理しか適用されなかったのです。

gist.github.com

このコードを作成して見るとどうなるでしょう。

おそらく、Inspectorには「intValue1」「intValue2」の両方が表示されているということはないはずです。

これおそらく、一方が拡張処理を割り当てた後で別の拡張処理が実行されるために、前の処理が上書きされているためと考えられます。

つまり、「1クラスに1つしか拡張クラスが割り当てられない」ということです。

問題2.「serializedObject」の罠

次に、serializedObjectについての問題点もあります。

serializedObjectとはEditorクラスを継承したクラス内で用いることのできる変数ですが、これは拡張される側のクラスのシリアライズされた情報が格納されています。

docs.unity3d.com

つまり、「問題1.」関連で「CustomEditorアトリビュート」が付与できるクラスが一つのみという縛りではかなり致命的な問題になります。

どういうことかというと、上記リファレンスリンク内でも記述されているように、拡張される側のクラスのシリアライズされた情報を基にserializedObjectに情報を格納しているため、「CustomEditorアトリビュート」が付与されていない拡張クラスではserializedObjectの中身は拡張されるクラスがどれか分からないためnullとなってしまいます。もちろんUnity側でエラー吐かれます。

解決策

 問題1解決策.一括定義とC#のPropertyの活用

問題1について、要は「1クラスに1つ拡張クラス」が満たせていれば良いので、マネージャクラスなりを作成し、そのクラスにだけ「CustomEditorアトリビュート」を付与させます。

ここで重要なのは、「拡張する変数はマネージャクラスで一括定義する」ということです。

つまり、Hoge1, Hoge2,...等のクラス処理で使用する変数を全て定義するということです。

そして、定義した変数は全てprivate+SerializeField化し、実際に処理を行うクラスからの変数アクセスはC#の機能であるProperty機能を用いてアクセス権限を「取得(get)」のみに留めることでカプセル化を図ります。

どういうことかというと、従来は

f:id:isemito:20170613025615p:plain

のように各クラス(Component)に対してエディタ拡張のクラスを割り当てていたのを、

f:id:isemito:20170613030655p:plain

というように、エディタ拡張をManagerクラス側に任せるようにします。

 これにより、各処理とそれら処理のエディタ拡張処理を別々に記述することが可能になりました。

 問題2解決策.引数で渡す

問題2については簡単です。

マネージャクラスのシリアライズ情報を引数として各クラスに渡してやればよいのです。

これにより「CustomEditorアトリビュート」を付与していないクラスからでもマネージャクラスのシリアライズ情報を取得することが出来ます。

まとめ

各問題点に対する解決策を適用したコード例が以下になります。

gist.github.com

一つのコード内に書いてますが実際に実装する際はもちろん別々のファイルに記述してください。

またLoadProperty等の中で共通処理がある場合Baseクラスなり作って継承させると良いでしょう。

例えば、私はBaseクラスのOnEnable関数に

this.hideFlags = HideFlags.HideInInspector;

と 記述することでInspectorにマネージャクラス以外を表示させないようにしています。

GGJ2017に参加してきました

目次

 GGJとは?

GGJ(Global Game Jam)とは年に一回、世界各地で同時開催されるゲームジャムです。各会場では限られた時間の中で、参加者が開発のアイデアを集め、小規模なチームを形成し、創造的で革新的な新しいゲームを世界中の人やコミュニティに発表します。2016年1月には93カ国632カ所で開催され、36000人以上が参加し、6800個のゲームが開発されました(Wiki抜粋 : グローバルゲームジャム - Wikipedia)

HPは以下。

globalgamejam.org

 GGJでは『テーマ』が存在する

毎年一度行われるGGJでは毎回テーマが存在します。

2017年度の1月20-22日に開催されたGGJでのテーマはWAVESでした。

こんなものを作りました

CONNECT | Global Game Jam®

簡単に説明すると、「いたずら好きの小人が、節電している家庭のコンセントを障害物をよけつつ差し込む」というものになります。より長くコードを消費した(伸ばした)ほうがスコアが高いですが、伸ばしすぎて残り0mになるとそれ以上コードが伸びない、つまり移動できなくなり失敗となりますので、いわばチキンレース的なゲームになります。

プレイヤーは自動で下へ移動し、ユーザーが操作するのは左右の移動のみになります。

ちなみに私はコード、移動、クリア、失敗、音のプログラムとステージ構成を担当しました!

DL

play.google.com

こちらからDLできますので是非遊んでみてください!!

【Unity5】AC4系のようなカメラワークを再現する

目次

前置き

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

前回は野生の男さんの記事「Unity 5.4+のVR最適化機能Single Pass Stereo Renderingの使い方と注意点 - Qiita」でした。

何でまた今更こんな記事を?

「Unity カメラワーク」とかで検索すると、よく出てくるのは映画のような動きを作る方法や用語であったり、例えプレイヤーに追従する方のカメラワークでも、

  1. カメラをプレイヤーの子にする(位置、回転共に追従)
  2. カメラにプレイヤーの座標のみを与える(回転はしない)
  3. Leap関数等を使用して少し遅れて追従させる

が主な内容であると感じました。

その為、AC4系のカメラワークを分析して、上記に挙げた方法より少し工夫して何かの参考になればと思った次第です。

注意事項

この記事では説明しやすくするようかなり細かく処理ごとにコンポーネントを切っています。その為スクリプトの数がかなり多めになっています。適宜、処理をまとめた方がよいかもしれません。

また、この記事は「こうしたらそれっぽくなるんじゃね?」のアルゴリズム的な事を書いていくことを主としているので、スクリプトの中身について深く書くことはありませんのであらかじめご了承願います。

ACって?

株式会社FROMSOFTWARE様から発売されている「アーマードコア(ArmoredCore)」シリーズ、略してACと呼ばれます。

本物のカメラワークはどんなの?

アーマードコア4(以下AC4)」「アーマードコア・フォーアンサー(以下ACfa)」にて用いられているカメラワークは、まるで背後から人が見ているような視点(TPS視点)で、かつプレイヤーの動きに遅れてきているような動きをします。

最近のゲームのTPS視点なゲームでのカメラは、プレイヤーの動きに遅れてきているような動きをする物が一般的になりつつあります(全てがそうだとは限りません)。

私の知っている中ではこのAC4系のゲームが特に顕著でスピードの速い機体だと画面外に機体が出てしまうこともしばしば。しかしその分スピード感を感じやすくもあります。

www.youtube.com

最終的にこんな感じなカメラの挙動を目指します。 

1. 準備する

それではここから製作に入ります。

まず、計3つの分けられたオブジェクト群を用意します。

1. 照準用オブジェクトを準備する

[追加オブジェクト]

・照準回転用オブジェクト(空オブジェクト)

・メイン照準用オブジェクト(空オブジェクト)

・サブ照準用オブジェクト(空オブジェクト)

gyazo.com

このオブジェクトらが、照準の位置を決めるものとなります。

ここで、メインがマウス操作する照準。サブが実際に弾等が飛んでいく照準という体で行きます。サブ照準が不必要だという場合は追加する必要はありません。

メイン照準用オブジェクトは、照準用回転オブジェクトの子にしましょう。これが実際に回転する際の基になります。

メインやサブの距離は任意ですが、プレイヤー(後で追加)、回転、メイン用のオブジェクトそれぞれは一直線になるようにしましょう(サブはメインに付いていくようにするので適当でOK)。絶対にプレイヤーの子にしてはいけません!

2. 照準用オブジェクトの追従先を準備する

[追加オブジェクト]

・追従用オブジェクト(空オブジェクト)

gyazo.com

 次に必要となるのは照準の回転用オブジェクトの追従先となるオブジェクトです。このオブジェクトが、全ての回転原点の目標座標になります。画像ではとりあえず頭の上あたりに設置しています。

これはプレイヤーの子にしてください。座標を調整するのを忘れずに。

用意する理由はというと、単に照準の回転用オブジェクトそのものをプレイヤーの子にしてしまうと、プレイヤーの回転と一緒に回ってしまうからです。
このオブジェクトの座標を追いかけるだけにしてあげれば、回転は気にしなくてよくなります。
プログラム側で直接座標を入力する方法等の解決法もありますが、それだと一回一回プレイして確認しなければならない上、数字情報が多くなって管理や理解が面倒になるので、個人的にはこちらの方が手間も省けて楽です。視覚的に追従位置を微調整出来ますし。

3. カメラ追従用オブジェクトを準備する

[追加オブジェクト]

・カメラ回転用オブジェクト(空オブジェクト)

・カメラ座標決定用オブジェクト(空オブジェクト)

gyazo.com

「カメラ座標決定用オブジェクト」は「回転用オブジェクト」の子にしてください。この座標決定用オブジェクトが、カメラが実際に存在するべき座標を決定します。つまり、このオブジェクトを移動させれば、「後ろから見る視点」でも「左右に少しずれる視点」でも「狙い撃ちするときだけプレイヤー近づける挙動」でも動的に再現できるわけです。後、念のため何度も言いますが、回転用オブジェクトは絶対にプレイヤーの子にしてはいけません!

2. 照準用オブジェクトを回す

さて、オブジェクトの準備が整ったところで、実際に動かしていきます。

なお、以後はソースコードの表示を多用するため、埋め込みではなくリンクを貼って進めていきますのであらかじめご了承ください。

まず、照準用オブジェクトを回していきます。

下記スクリプトを作成してください。

【AimRotate.cs】

AimRotate.cs · GitHub

このスクリプトを「AimRotateOrigin」にアタッチすれば、マウスでMainAimオブジェクトを回転により動かせるようになるはずです。

また、Inspectorの「SubAimObj」にSubAimオブジェクトをアタッチすればSubAimがMainAimに遅れてくるようになります。

Inspectorの「Clamp Angle」で角度制限を変更することが可能です(初期値は60度)。

また、サブ照準をここで不必要だという事で追加していない場合はサブ照準の追従処理を削除してください。

gyazo.com

3. プレイヤーを回す

次にプレイヤーを回転させます。

ここが従来のキャラクターの回転方法とは特に異なるところです。普通は、コントローラやマウスの入力による回転は直接プレイヤーオブジェクトに反映していましたが、今回の手法ではプレイヤーの回転は「MainAim」の方向を向くようにスクリプトで処理します。直接的ユーザー入力による回転を行う事はありません。

では、下記スクリプトを作成してください。

【PlayerRotate.cs】

PlayerRotate.cs · GitHub

このスクリプトを「Player」にアタッチすれば、プレイヤーオブジェクトがMainAimの方向に向かって遅れて回転しているはずです。

(下動画ではわかりやすくプレイヤーに鼻を付け、MainAimに球を、SubAimに箱を付けています)

gyazo.com

またこのスクリプトでは回転軸の固定を行うことが出来るので、回転させたくない軸にチェックを入れればその軸は回転しなくなります。その為、照準は自由に動かしたいけど、プレイヤーオブジェクトは横回転(Y軸回転)のみ行わせたい時などに使えます。

(下動画ではX軸とZ軸にチェックを入れ、横方向の回転のみ行うようにしています)

gyazo.com

4. 照準を追従させる

 さて、このままではもちろん照準はその場から動きませんので、照準をプレイヤーに追従させるようにします。

【AimFollow.cs】

AimFollow.cs · GitHub

このスクリプトを「AimRotateOrigin」にアタッチしてやれば、照準の回転原点がプレイヤーの子になっている追従原点「FollowTarget」に追従するようになります。

ただ、そのままFollowTargetに追従すると、照準の回転原点が頭の上になってしまうので、offsetでFollowTargetからの相対位置を出し、FollowTargetからの相対位置を維持します

gyazo.com

5. カメラ追従オブジェクトを移動・回転させる

次に、カメラ追従オブジェクト(橙色)の処理を追加していきます。

横(Y軸)回転はプレイヤーの回転で賄うので、ここで回転させるのは縦(X軸)回転になります。

 【CameraRotate.cs】

CameraRotate.cs · GitHub

このスクリプトを「CameraRotateOrigin」にアタッチしましょう。

Inspectorの「Clamp Angle」で角度制限を変更することが可能です(初期値は60度)。

gyazo.com

6. カメラを遅れて追従させる

次に、カメラの追従処理を追加していきます。

 【CameraFollow.cs】

CameraFollow.cs · GitHub

このスクリプトを「Main Camera」にアタッチしましょう。

これで、照準の回転やプレイヤーの移動によるカメラの挙動が確認できるかと思います。

移動や回転の速度はとりあえず3にしているだけなのでお好みで調整してください。個人的には移動:10, 回転:8が好みです。

gyazo.com

7. カメラの注視点をずらす

ところで、AC4系カメラの特徴として、横にQB(クイックブースト、瞬間的な高速移動)した際、カメラは完全に正面を向かず、若干機体の方向を追うように向いています(4:39辺りの横へQB移動した時が特に分かりやすいかと)。

【参考動画】


ACfa テスト動画 ホワイトグリント戦(HARD) 720p 60fps

また、これもAC4系カメラの特徴として、横移動時「機体の横が見えます」。

どういうことかというと、機体は平行に向き、かつ移動しているにもかかわらず、カメラは機体の横っ腹が見えるような方向を向いています。かといって完全に機体の方向を見ているわけではないようです。

という事は、AC4系でのカメラにおける注視点は、プレイヤー正面方向ではなく別の場所であることが考えられます。

ではカメラはいったいどこを目標として見ているのか、以下の項目で検証してみました。

  1. プレイヤー正面方向にカメラを向け(初期位置・初期向き)、横移動
  2. プレイヤーより高い位置から見下ろし、横移動
  3. プレイヤーより低い位置から見上げ、横移動

以上の3項目の結果を検証してみたところ、どうやらカメラは機体の移動方向とは逆方向の位置を注視しているようでした。下図は予想。

f:id:isemito:20161221180744p:plain

これは私の予測ですが、カメラの回転軸とカメラ移動先は機体よりも遅れて、かつ、一定距離上は離れないようになっているのかもしれません。

f:id:isemito:20161225073428p:plain

 【CameraOffset.cs】

CameraOffset.cs · GitHub

このスクリプトを「CameraRotateOrigin」にアタッチしましょう。

ついでに、

 【PlayerMove.cs】

PlayerMove.cs · GitHub

こちらのスクリプトをプレイヤーオブジェクトにアタッチしましょう。

Rigidbodyの「Drag」は2にしておきます。

後はテラインで地面作って色々なスピードをいい感じに調節して...

gyazo.com

こうなります。

WASDで移動。SPACEでQB。QT(クイックターン)は無し。

8. 壁埋まりを防ぐ

さて、このままだとカメラが地面に埋まってしまいます。

gyazo.com

カメラが建物や地面に埋まって裏側が見えてしまうのはどうかと思うので、壁埋まりを防ぐ仕組みを作ります。

前に作ったCameraFollow.csを以下のように修正してください。

また、PlayerオブジェクトにPlayerレイヤーを割り当てることを忘れずに

【CameraFollow.cs】

CameraFollow.cs · GitHub

これで、障害物にカメラが埋まらなくなりました。

gyazo.com

gyazo.com

9. 結論

まぁ、今回のように少し凝ったことをするとなると多少なりとも工夫は必要ですが、単純に少しだけ遅れてくるだけで良いという場合は「Leapで遅れて追従させる」で事足ります。時間的コストも少ないですし。

10. おまけ

クリスマスに相手がいる人なんていませんよね?

【Unity5】Sceneビューに任意にコメントを置けるアセットを開発しました

目次

きっかけは突然に

ある時、Slackに「シーンにコメント付加できる拡張」と書かれているのを見て、便利そうだと話が立ったのがきっかけ。思い立ったが吉日。

というわけで作りました。

github.com

使い方

  1. Hierarchy上で右クリック > 「Create Scene Comment」で作成
  2. Inspectorでコメントの編集
  3. 自動追加される「SceneCommentEditor」で諸々の設定を行う
  4. おしまい

gyazo.com

もちろん改良にも対応

gyazo.com

オブジェクトにもアタッチ可

追加されたコメントクリックすればどのオブジェクトに対応しているのかも一目瞭然

gyazo.com

距離による拡大・縮小も実装済

gyazo.com

それにしても、これ5時間程で作ったのですが、自己最短記録でした。

【Unity5】Input Moduleを自作した話

目次

前置き

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

前回は私自身の記事「【Unity5】VRゲーム開発に役立つかもしれないアセットを二つ開発したので紹介と解説」でした。

また、その記事で自作アセットである「VRDebugConsole」について書きましたが、そのアセットではInputModuleを自作しています。今回はそのいきさつや中身について書くので、前日記事と合わせて拝読されることをお勧めします。

Input Moduleってなに?

さっそくですが、まずInput Moduleとは何か?

これは調べれば多数情報が出てきますが、要約すると、

ある入力に対するイベントの送信を行うもの

であると言えるでしょう(間違っていたら指摘をお願いします...)。

例えば、クリックやタップ、ドラッグやピンチアウト/イン等が当てはまるでしょう。クリックされた際に、選択されたボタン等のuGUIに対してイベントを送信、つまり、

いんぷっともじゅーる「おい、ぼたん君よ、君クリックされたで」

ぼたん「まじか処理実行するわ、あざす」

みたいな感じです。

実際には間にGraphic RaycasterなるuGUI検出用のレイキャスターが入るので、

もじゅーる「れいきゃすたー君、今ポインタは何かのuGUIにいる?」

れいきゃすたー「ちょいまち(レイ照射)...ぼたんの上にあるで」

もじゅーる「あざす」

もじゅーる「おい、ぼたん君よ、君クリックされたで」

ぼたん「まじか処理実行するわ、あざす」

みたいな流れだと思われます。

Unityには標準で搭載されており、それぞれ「Standalone Input Module」と「Touch Input Module」がありました。が、いつの間にかTouch Input Moduleは既にStandalone Input Moduleに統合されたようです。なのでStandalone Input Module一つでPCやモバイルの入力を賄えるようです(これ書くために調べるまで気づかなかった..)

このコンポーネントは、uGUIを追加した際に自動生成される「EventSystem」にアタッチされています。

Input Moduleはつくれる

「Input Moduleは自作することが可能です。」

と、いうことを知った当時の私はVRDebugConsoleをVRデバイスで操作できるようにするために色々調べてみました。するとこちらの記事が見つかりました。

tips.hecomi.com

細かい関数の中身についてはこちらの記事を参考にしていただくとして、大まかにStandalone Input Moduleには以下の要素が含まれ、また記述されていることが分かります。

gist.github.com

ここで、Standalone Input ModuleはPointer Input Moduleを継承していますが、これはマウスカーソルの情報を扱っているクラスのようです。そのため、マウス情報を用いない今回の場合はPointer Input Moduleは継承せず、そのさらに基底クラスである「Base Input Module」を継承します。これが、Input Moduleの大本となります。

どうやってInput ModuleはuGUIを判定しているのか?

ところで、Standalone Input ModuleはWorld座標に置いたuGUIに対してもイベントを送信出来ます(割と周知の事だったりするんでしょうか...私は最近気づきました)。

では、『Worldに置かれてようが置かれていまいがuGUIの上にマウスポインタがあるという認識をしている部分がどこかにあるはずだ』という事で、Pointer Input Moduleを見てみると、このようなコードがあります。

gist.github.com

コードを見る限り、「PointerEventData」なる型の変数がマウスの情報となる変数群を持っているようです。そこに、マウスの座標や移動量などを割り当て、「eventSystem.RaycastAll(データ, レイキャスト結果のリスト配列)」に渡しレイキャストを飛ばす(Graphic Raycaster)ことで、uGUIを取得しています。

ということは、PointerEventDataの座標に対して、スクリーン座標を渡してやれば、任意にレイキャストを飛ばし、uGUIを取得することが出来るようになるという事です。後は、先ほどの「ExecuteEvents.Execute」関数に取得したuGUIと、作ったデータと、送信するイベントのタイプ(選択とかドラッグとか)を指定してやれば、uGUIの選択判定を実装できるはずです。

PointerEventDataを使う

というわけで実際にInput Moduleを自作したのが以下になります。

(他スクリプトと情報の受け渡しを行っているのでこれ単体では動きませんが...)

gist.github.com

163行目で取得したuGUIに対してイベントを送信しているのがわかるかと思います。他にも、ラインレンダラで線を描画したりして選択個所を可視化しています。

結果、このようになります。

20161205191112

ただ、ポインタが今存在している位置を取得するためにuGUIに対してレイキャストを行って上動画のようにしたかったのですが、どうやらuGUIに普通のRaycastは衝突を検出しません。まぁGraphic Raycasterがわざわざ存在してるので当たり前なのかもしれませんが。

なので、VRDebugConsoleにウィンドウの形に沿ったコライダを追加し、そこにRaycastを飛ばして衝突検出させています。 他に何か良い方法があれば教えていただけると幸いです。まだ試していませんがGraphic Raycasterを使えばできるのかも?とは思っていますがどうなんでしょう...。

https://cloud.githubusercontent.com/assets/3947216/21094453/6f4d85d4-c09a-11e6-82ae-0a133701504b.gif

【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