Unityな日々(Unity Geek)

Unityで可視化アプリを開発するための試行錯誤の覚書

Unity-C#で数値を扱うクラス

Unity-C#で数値を扱う場合、あまり考えることなく整数だとint、浮動小数点だとfloatをつい使ってしまうが、扱える範囲やサイズに応じていくつかの変数型がある。メモリサイズや速度をギリギリとつめたいときや、逆に大きな数値を扱う場合は、型の最適化を忘れないようにしよう。

整数型

範囲
sbyte型 符号付8bit整数(-128~127)
byte型 符号なし8bit整数(0~255)
short型 符号付16bit整数(-32768~32767)
ushort型 符号なし16bit整数(0~65535)
int型 符号付32bit整数(-2147483648~2147483647)
uint型 符号なし32bit整数(0~4294967295)
long型 符号付64bit整数(-9223372036854775808~9223372036854775807)
ulong型 符号なし64bit整数(0~18446744073709551615)

浮動小数点型

サイズ 表記例
float 符号付32bit浮動小数点数 1.3f
double 符号付64bit浮動小数点数 1.3d
decimal 符号付128bit浮動小数点数 1.3m

2017/5/20追記

数値の暗黙の型

整数の暗黙の型はInt32、非整数の暗黙の型はDouble である。

var defaultInt = 3;    //整数値で型を指定しない場合
Debug.Log(defaultInt.GetType()); //'System.Int32'になる

var defaultNum = 1.0;    //非整数値で型を指定しない場合
Debug.Log(defaultNum.GetType()); //'System.Double'になる

Decimal型

Float/Doubleが数値を2進数として保持するのに対し、Decimal型は数値を10進数として保持する。このため、いわゆる「桁落ち」が生じない(生じにくい)。

一方で、Decimal型の必要バイト数は大きく、数値ひとつで16バイトを消費するにもかかわらず、扱える数の範囲は4バイトのfloatより狭い。

参考:

ufcpp.net

Event使用時のエラー"... can only be called from the main thread"

次のようなエラーがでた。

f:id:yasuda0404:20150826180634p:plain

状況は次のとおり。

オリジナルの'subClass'のスクリプトで、別クラスであるmainClassのイベントReadに、('subClass'のスクリプト内の)Actionメソッドを登録していた。イベントが発行されるとsubClassのAction()メソッドが呼ばれ、そこからさらにMove()メソッドが呼ばれる。Move()は、他クラスのアニメーションの再生(Playメソッドをコール)するものだ。

        //subClass
    void Start () {
        mainClass.Read += Action;   //Register Custom Function. 
    }

    public void Action(string id) //このメソッドがmainClassのイベントを通じて実行される
    {
        Move(id);
    }

ちなみにイベント発行側であるmainClassのデリゲーションは次のように記述。

public class mainClass : MonoBehaviour {
    //Delegate
    public delegate void MyDelegate(string id);
    public event MyDelegate Read;

上を実行したところ、subClassは、mainClassが発行するイベントを受け取り、Action()メソッドも実行されるのだが、Move()内で実際にアニメーションを実行するところでエラーが出ているようだ。

同じsubClass内のUpdateループからActionメソッドを実行する場合は、何も問題がなくアニメーションが実行されることがわかった。すなわち、同じアニメーションを実行するのに、Updateからだと問題なく、イベント・トリガーだとエラーになる。

 void Update()
    {
        if(Input.GetKeyDown("a")) {  //Executed when "a" pressed down
            Action("key a");
        }
    }

このエラーの原因は、非同期処理のイベントを通じて、UnityのアニメーションAPIを呼び出そうとしたことにある。アニメーションAPIに限らずUnityのAPIは、メインスレッド(Awake、Start、Update、FixedUpdateなど)から呼び出さねばならないようだ。なるほど、Unityは基本的にシングルスレッドなのだ。別スレッドからUnity APIは使えない。

この対策として、イベントハンドラAction()では、イベント発行先から送られた情報(この場合、string id)を自クラスの変数に格納するのみにして、update()ループ内でその値を評価してアニメーションメソッド(Move())を実行するようにした。これで解決。

    public void Action(string id)
    {
        Debug.Log("ControllerDirect::Action id="+id);
        currentID = id;
    }

        void Update()
    {
        if(Input.GetKeyDown("a")) {  //テスト用 Key入力は問題なく動作する
            Action("key a");
        }
        Move();
    }
    
    //Custom Function
    private void Move()
    {
        if(latestID != currentID) {
            latestID = id;
            animationNo = (animationNo+1)%5;
            Debug.Log("animationNo "+animationNo);
            Debug.Log("Controller::Action id="+id);
            //
            switch(animationNo) {
            case 0:
                move.ActionStop();
                break;
                <以下略>
                

参考:Unityでスレッドを使いつつUnityAPIを使う Spicy Pixel Concurrency Kit - テラシュールブログ

InputFieldにテキストを設定する際の注意点

Create-UI-InputFieldでInputFieldを作成すると、'InputField'オブジェクトの下に、PlaceholderとTextという名前の2つの'Text'オブジェクトが生成される。

f:id:yasuda0404:20150810171140p:plain

Placeholder, Textの2つの'Text'オブジェクトは、作成時点で親のInputFieldオブジェクトにひもづけされている。

f:id:yasuda0404:20150810171412p:plain

テキストの取得

スクリプトでInputFieldに入力されたテキストを取得する際は、

public InputField inputField;  //InputFieldオブジェクトをアサイン
string text =  inputField.text

としてもよいし、

public Text inputText;  //InputFieldの下のTextオブジェクトをアサイン
string text =  inputText.text

としてもよい。

テキストの設定

逆にInputFieldにテキストを設定したい場合は、

public InputField inputField;  //InputFieldオブジェクト
inputField.text = "Default Input";  //InputFieldにテキストが設定される

はOKだが、

public Text inputText;  //InputFieldの下のTextオブジェクト
inputText.text = "Default Input";  //!!! InputFieldのテキストに変化はない!!

は機能しない。エラーにはならないが、表示は変更されないので注意。

iTweenの参考資料

スクリプトでアニメーションを行う定番ライブラリが、iTweenFlash(Action Script)ライク名表記で多彩なアニメーションを実装できる。

itween.pixelplacement.com

ただ、iTweenの表記方法は結構忘れやすいので(年齢のせいかもしれないが)、リファレンスをまとめておく。メソッドの種類やHashの要素は覚えきれないし、慣れてくると使うメソッドやeasetypeが限られてしまうが、リファレンスを見ると「こういうのもあったのか」と発見もある。リファレンスをみるべし、というのはiTweenに限ったことではないけれど。

itween.pixelplacement.com

www40.atwiki.jp

easing_demo

iTween list of easeTypes - Unity Answers

また、easetypeは種類が多く、また、いちいち変更するのは面倒。次のようなenum変数を定義しておくと、インスペクタからプルダウンで選択できて便利。

enum Easetype {
    easeInQuad,easeOutQuad,easeInOutQuad,easeInCubic,easeOutCubic,easeInOutCubic,
    easeInQuart,easeOutQuart,easeInOutQuart,easeInQuint,easeOutQuint,easeInOutQuint,
    easeInSine,easeOutSine,easeInOutSine,easeInExpo,easeOutExpo,easeInOutExpo,
    easeInCirc,easeOutCirc,easeInOutCirc,linear,spring,
    easeInBounce,easeOutBounce,easeInOutBounce,easeInBack,easeOutBack,easeInOutBack,
    easeInElastic,easeOutElastic,easeInOutElastic
    }
[SerializeField]  Easetype easetype = Easetype.easeOutQuad;

(略)

iTween.ScaleTo(gameObject, iTween.Hash("scale", Vector3.one,  "time", t, "easetype", et.ToString() ));

注1)Easetypeは、publicではエラーになる。[SerializeField] を使用すること。
注2)enum(列挙型)の実態は整数であるため、enumをiTweenのeasetypeに指定するときは、toString()でストリングに変換すること。

Unity5のプロジェクトをUnity4.6で開く

Unity5で作成したプロジェクトを4.6で開きたい場合、次のように設定する。

UnityメニューのEdit/Project Settings/Editorを開き、

  1. vertion controlのmodeを「Visible Meta Files」にする
  2. Asset Serializationのmodeを「Force Text」にする

をおこなってプロジェクトを保存する。

このプロジェクトはUnity4.6で開ける(はず)。

f:id:yasuda0404:20150804170400p:plain

ただし、Unity5で付加された機能や変更された機能は、当然ながらUnity4.6にはもっていけない。たとえば、AnimatorファイルやAnimationファイルはUnity4.6では機能しないようだが、読み込みのエラーは生じない。

iOS用のレスポンシブなUIをつくる

iOSデバイスにはさまざまな解像度、アスペクト比があり、それぞれに適切なUIとなるような工夫がいる。

参考: unitygeek.hatenablog.com

実現したいUI:トリミングとストレッチ

たとえば、次のようなUIデザインを実現したいとする。

  1. 背景の画像は画面の縦サイズにあわせ、横方向はトリミングされる。
  2. ボタンを3つ配置したメニューバーは、画面の横いっぱいに表示し、バー内にボタンを3等分して配置する。
  3. テキストボックスは、横幅一定、高さはテキスト量によって変える。

具体的な完成イメージは次のようなものだ。2つの画面の解像度・アスペクト比はそれぞれ、iPhone5s, iPad Retinaを想定している。 背景の人物の写真は、iPhoneでは左右がトリミングされている。ボトムの黒色のメニューバーは、iPhone, iPadそれぞれに横方向いっぱいに表示され、ボタンは3等分されて配置されている。

f:id:yasuda0404:20150723184213p:plainiPadでの画面イメージ

f:id:yasuda0404:20150723184226p:plainiPhoneでの画面イメージ

実装方法

左右トリミング
  1. まず、カメラ画面全体にひろがるCanvasを作る。 このためには、
  2. CanvasのプロパティRender Modeを'Scressn Space - Overlay'にする。
  3. Canvas ScalerのReference Resolutionを想定するデバイスの画面サイズの最大値にする。 (iOSについては、現状最大のスクリーンサイズはiPad Retinaの1536x2048ピクセルなので、この値を採用。)

参考:iOS - iPhone/iPad解像度(画面サイズ)早見表 - Qiita

  • Canvas ScalerのScreen Match Modeを'Match Width or Height'にし、Height側の端(=1)にする。 (こおオプションは、画面の幅にあわせるか(Width)、高さにあわせるか(Height)を指定するもの。ちなみに、'Expand'とすると画面は画像いっぱいに表示され画像の一部ははみ出し、'Shrink'にすると画像すべてを画面におさめ隙間ができる。)

  • Canvasの下に表示したい画像のUIオブジェクトを作成(ImageまたはRawImage)。このオブジェクトのWidth/Heightを親CanvasのReference Width/Heightにあわせる。

f:id:yasuda0404:20150723185708p:plain

以上で画像は高さ方向にいっぱいに表示され、左右はトリミングされる。

ストレッチ&均等配置するメニューバー

いくつか方法あるが、自動レイアウトのコンポーネントを使う方法が一番簡単。

まず、親UIオブジェクト('ButtonBar')を作り、ストレッチ&均等配置したいオブジェクト('Button')を'ButtonBar'の子にする。画面上のボタンの並び順は、シーンビューでのボタンの並び順と同じになるので、並べたい順番に入れ替える。

'ButtonBar'のRectTransformは、横方向をいっぱいまでストレッチ、縦方向を高さ指定(Height)の設定にする。子オブジェクトであるボタンはこの範囲で均等配置されることになる。PosYはバーのY方向位置が望ましくなるように決める。

f:id:yasuda0404:20150723190605p:plain

'ButtonBar'に、Layout-HorizontalLayoutGroupコンポーネントを付与する。'Child Force Expand'のWidth, Height両方にチェックを入れると、子要素が親要素の大きさいっぱいに広がる。具体例でいうと、子要素がText要素の場合、Widthが親要素と同じになり、子要素自身にWidthを指定できなくなる。

f:id:yasuda0404:20150723185857p:plain

f:id:yasuda0404:20150723185916p:plain

子要素にWidthを指定したい場合は、'Child Force Expand'のWidthのチェックをはずす。これで子要素にLayoutElementを付与できるようになり、PreferredWidthを任意に指定できる。

各Buttonコンポーネントの最小値(Min Width/Height)・推奨値(Preferred Width/Height)・相対的な比率(Flexible Width/Height)は、各ButtonのLayout Propertiesで確認できる。

f:id:yasuda0404:20150727143937p:plain

これらの値を変更したい場合は、Layout-Layout Elementコンポーネントを付加する。たとえば、3つのボタンのサイズ比率を2:1:1にしたい場合は、最初のボタンのFlexibleWidthを2にし、残りの同パラメータを1にすればよい。Layout Propertiesの該当する値が自動的に変わる。

f:id:yasuda0404:20150727144131p:plain

高さ方向可変のテキストボックス

シーンビュー上のText要素に、Layout-Constant Size Fitterコンポーネントを付加する。Vertical Fitを'Preferred Size'に設定。Text要素のPreferred SizeはText量によって変わるため、これに高さ方向をあわせるという考え方。(Preferred HeightとはWidthを固定した場合の高さ。逆にPreferred Widthとはテキストを1行にした時の幅。…ということらしい)

f:id:yasuda0404:20150727144833p:plain

また、ヘッダーのように高さ方向一定の要素は、Layout Element要素を付加し、Preferred Heightに固定値を与える。

f:id:yasuda0404:20150727161317p:plain

f:id:yasuda0404:20150727145012p:plain

上の操作でText要素自体の高さ可変になるが、Text要素やヘッダー要素の親要素であるパネルサイズもあわせて変えたい。このためには親要素に、Layout-Constant Size FitterとLayout−Vertical Layout Groupの2つのコンポーネントを付加する。 Paddingを調整することで、余白を持たせることができる。

f:id:yasuda0404:20150727161638p:plain

完成

シーンビューは次のようになる。
白枠がカメラ描画範囲。背景画像は高さ方向が描画範囲いっぱいになり、アスペクト比が保たれる。
メニューバーは、描画範囲いっぱいに広がり、3つのボタンが均等に配置されている。

f:id:yasuda0404:20150723191050p:plain

Editor上ではカメラの描画範囲が横長のため、カメラの背景(Skybox)が見えてしまっているが、iPhoneiPadのPortraitで見ると、冒頭の目標画面イメージのとおりになっている。

iOSアプリの作成手順

UnityからiOSアプリを書き出す手順についてのメモ。

iOSアプリ開発の前提として、App IDやプロビジョニングファイルといった情報を取得・設定しておく必要がある。これらについては次の記事を参考に。

macdays.hatenablog.com

シミュレータで動かす場合

Unityでの作業

1. アプリ用のシーンを作成。

今回UIとして用いるCanvas

  • RenderMode: Screen Space - Overlay
  • Reference Resolution: 1136 x 640
  • Screen Match Mode: Expand

とし、GameViewのアスペクト比は16:9に設定。

f:id:yasuda0404:20150722092851p:plain

これらはiPhone5sの横モードに対応。

2. Build Settings

iOSを選択。「Player Settings..」をクリック。

f:id:yasuda0404:20150722093313p:plain

PlayerSettings共通

Campany Name Product Name を設定。

Icon画像は別途デバイスごとに設定するが、想定外のデバイスのためにDefault Iconも設定しておくとよい。

f:id:yasuda0404:20150723092137p:plain

Resolution and Presentationタブ
  • Default Orientation: Landscape Left (横長モード)
    f:id:yasuda0404:20150722093617p:plain
Iconタブ

各アイコン画像を登録

Splash Imageタブ

各デバイス用のSplashイメージを登録。各デバイスの画面解像度は、iOS - iPhone/iPad解像度(画面サイズ)早見表 - Qiita などを参考に。

Other Settingsタブ

f:id:yasuda0404:20150722100007p:plain

3. ビルド

Build Settings...ウィンドウでビルドする(現在のシーンを登録するのを忘れないよう)。
Xcodeのプロジェクトファイルができる。
f:id:yasuda0404:20150722100614p:plain

Xcodeでの作業

上で作ったxcodeprojファイルをクリックしてXCodeを起動。

シミュレータでの確認

  • トップメニューの再生・停止ボタンの右横にあるシミュレータ選択プルダウンで、適当なデバイスを選ぶ。
    f:id:yasuda0404:20150722101056p:plain

  • 再生ボタンをおしてシミュレータを起動。 iOSの画面が表示される。 f:id:yasuda0404:20150722101325p:plain

※シミュレータ起動時、デバッグプロセスの制御の確認画面が出た場合は、マシンのパスワードを入力し続行する。 f:id:yasuda0404:20150722101258p:plain

  • 必要に応じてデバイスを変更し、シミュレータでの確認をおこなう。

デバイスの変更はiOSシミュレータのトップメニュー、Hardware-Device-... でも行える。
f:id:yasuda0404:20150722102346p:plain

実機デバイスで動かす場合

Unityでの作業

UnityのPlayer Settings...のiOSタブで、SDK Versionを'DeviceSDK'にしてビルドする。   f:id:yasuda0404:20150722111932p:plain

Xcodeでの作業

Provisioning Fileの設定

ビルドしたxcodeprojを開く。トップバーに’iOS Device'と表示されているはず。
f:id:yasuda0404:20150722112309p:plain

サイドメニューで該当するプロジェクトを選択し、Build Settingsタブをクリック。スクロールして'Code Signing'をみつける。この項目の、

  • Code Signing Identity を iOS Developerにする
  • Provisioning Profile を Automaticにする

f:id:yasuda0404:20150722114122p:plain

実機デバイスの起動

iPhone/iPadをUSBでつなぐと、デバイス名が表示される。
f:id:yasuda0404:20150722112624p:plain

初めてのデバイスの場合は、Symbol Filesをデバイスにコピーするためしばらく時間がかかる。

終了後、XCodeの再生ボタンを押すとiOSデバイス上でアプリが起動する。

参考:

iOS開発ガイド-アプリケーションのビルドと実行

techacademy.jp

Unity5.1でOculus Riftを使う

Unity4.6でOculusRiftを使う場合は、ライブラリをインポートする必要があった。(ライブラリは4.6.3以降で利用可能)Oculus Riftの導入(DK2) - Unityな日々(Unity Geek)

Unity5.1以降では、OculusなどのVRデバイスの標準で対応している。

UnityとOculusを接続する手順は次のとおり。、

1) OculusRiftのRunTimeをインストールする。Unity5との接続には、V0.6.0.1以降が必要なようだ。 f:id:yasuda0404:20150717164030p:plain

f:id:yasuda0404:20150717163658p:plain

f:id:yasuda0404:20150717163648p:plain

2) Edut-Project Settings-Player で、'Virtual Reality Supported'をオンにする。最初、オブジェクトをリインポートする旨のメッセージがでる。

f:id:yasuda0404:20150717163719p:plain

以上で、Editorでも実行ファイルでもシーンのメインカメラの位置にOculusのカメラが自動的に設定される。(RiftのDirectModeを使っているため、PC上にはシングル画面が表示される)

メインカメラは、

1) 'MainCamera'のタグがついているもの

2) 同タグがない場合、または複数ある場合は、カメラDepthのもっとも大きなもの

の順で決定される。

Unity5でCharacterControllerを使う

方法1:CharacterControllerクラス(コンポーネント)を使う

キャラクターとして動かすGameObjectを作る f:id:yasuda0404:20150716145603p:plain

MainCameraを同GameObjectの子にする

f:id:yasuda0404:20150716150038p:plain

同GameObjectに、Physics-CharacterControllerをアタッチする

f:id:yasuda0404:20150716150230p:plain

キャラクター移動のUIクラスを作る。参考:Unity-API:CharacterController.Move()

これを同GameObjectにアタッチする。

方法2:標準アセットを使う

Assets-ImportPackages...-Characters を読み込む

f:id:yasuda0404:20150716155900p:plain

Assets-StandardAssets-Characters-FirstPersonCharacter-Prefabsの下の、

-FPSController -RigidBodyFPSController 

のどちらかをシーンに加える

FPSControllerには歩行をシミュレートする「ゆれ」とサウンドがついている。「ゆれ」をなくすには、Use Head Bobをオフにする

歩行時と走行時にカメラのFOVを変えるには、Use FOV Kickをオンにし、各パラメータを調整する

f:id:yasuda0404:20150716162227p:plain

覚えておくと便利な小技(Tips)

Unityの「ビギナーTips」の中に、結構便利そうなもの(で、知らなかったもの)があったのでメモ。

Snap

Ctrl(Macはコマンド)キーを押しながら、移動、拡大・縮小、回転をおこなうと、一定の増分ごとに「スナップ」されて移動、拡大・縮小、回転を行える。

各増分は、

Edit-Snap Settings...

で指定する。

f:id:yasuda0404:20150710183415p:plain

プレビューウィンドウの拡大

モデルなどのアセットを選択すると、インスペクターの下のほうにあるプレビューウィンドウで中身を見ることができる。が、エリアが小さいのでわかりにくい。

プレビューウィンドウを別ウィンドウとして拡大するには、インスペクターのプレビューエアリアの上枠部分を右クリックする。

f:id:yasuda0404:20150710184028p:plain

プレビューウィンドウが別ウィンドウで開かれ、任意のサイズに拡大縮小できる。閉じるときは右上の「×」をクリックする。

f:id:yasuda0404:20150710184036p:plain

フォーラムからAPIリファレンスへのリンク

forum.unity3d.comのサンプルスクリプトの一部は、APIリファレンスへのリンクが貼られている。たとえば次のようなスクリプトの、水色文字、赤文字の一部(すべてではないようだ)をクリックするだけで、該当するAPIリファレンスを開くことができる。

f:id:yasuda0404:20150710184707p:plain

カメラの位置・角度の調整

1) シーンビューの画面に、カメラの画角をあわせたい場合:

カメラを選択し、GameObject-Align with View を選択。(ゲームビューで確認できる)

f:id:yasuda0404:20150710185630p:plain

2) カメラの画角に、シーンビューの画面を合わせたい場合:

カメラを選択し、GameObject-Aligh View with Selected を選択。 シーンビューの画面が、選択したカメラと同一になる。

プレイモード・ティント

エディターでプレイモードを選択していることを忘れてしまうことがある。プレイモードで行った変更は停止すると元に戻るので、無駄な作業になってしまう。

プレイモードにあることを視覚的に明瞭にするために、'Play Mode Tint'というしかけがある。

Edit-Preferences... で Colors'を選択。

'Play mode tint'で任意の色を設定する。

f:id:yasuda0404:20150710190506p:plain

プレイモードではウィンドウの色が変わるので、よほど鈍感でなければプレイモードであることを忘れないだろう。

f:id:yasuda0404:20150710190606p:plain

インスペクタでスライダーを使う

スライダで値を設定するには、スクリプトのPublic Variableの定義の前に、

[Range(min, max)]

とかけばいい。たとえば、

[Range(0,1)] public float a;

とすれば、インスペクタ上に0-1の範囲のスライダが表示される。

f:id:yasuda0404:20150710191503p:plain

インスペクタの入力に数式を使う

インスペクタの入力エリアに、数式を打ち込んでもいい。たとえば、'3*5'と入力すると、15に変換される。

f:id:yasuda0404:20150710191815p:plain

Empty GameObjectをアイコン表示する

UnityシーンではEmpty GameObjectを使うことが多いが、シーンビュー上では透明のため、フォーカスが外れるとどこにあるかわからなくなる。

GameObjectにアイコン(ギズモ)を設定するには、インスペクタの左上にある「カラーキューブ」をクリック。

f:id:yasuda0404:20150710192231p:plain

さまざまなカラーのタグや小さな丸を選ぶことができる。

f:id:yasuda0404:20150710192552p:plain

Playモード中の変更を適用する

通常、Playモードで行った変更はPlayモードを終了すると、元の状態にリセットされる。

Playモード中の変更を適用するには、該当するコンポーネントの歯車アイコンをクリックし、'Copy Component'を選択する。

f:id:yasuda0404:20150710193108p:plain

Playモードを停止した後(変更した値はリセットされる)、該当コンポーネントで 'Paste Component Values'を選択すると、Playモード中の変更が適用される。

f:id:yasuda0404:20150710193220p:plain

複数コンポーネントに対する変更をPlayモード終了後に適用するには、Playモード中に、'Edit-Copy'

f:id:yasuda0404:20150710193425p:plain

Playモード終了後、該当オブジェクトをシーンから削除し、'Edit-Paste'.

インスペクタの値ごとペーストされる。

#

Asset Storeのダウンロードフォルダを変更する

AssetStoreからたくさんのアセットをダウンロードすると、いつの間にかCドライブがいっぱいになってしまう。僕の場合、もともとCドライブに空きが少ないので危険な状態になってしまっていた。

f:id:yasuda0404:20150605160010p:plain

Asset Storeで購入したアセットの保存先を変更しようと思ったのだが、Unityの設定ではできないようだ。

Unity Feedback - Ability to change the download directory of the Asset Storeを参考に、シンボリック・リンクを貼ることで対応した。

その手順は次のとおり。

  1. 既存のアセット保存フォルダをコピーする。 Asset Storeで購入したアセットはデフォルト状態では、 C:\Users\ユーザー名\AppData\Roaming\Unity の下にある、 Asset Store-5.x (Unity5でダウンロードしたアセット) Asset Store (Unity4.x以前でダウンロードしたアセット) に保存されている。 これらのフォルダを、新しい保存先にコピーする。

  2. コピー後、C:\Users\ユーザー名\AppData\Roaming\Unityの下の2つのアセットフォルダを削除。

  3. デフォルト保存フォルダから新規保存フォルダへシンボリックリンクを貼る。  コマンドプロンプトを開いて、次のようにタイプする。

mklink /j "C:\Users\YourUsername\AppData\Roaming\Unity\Asset Store" "X:\WhateverLocationYouWant1"
mklink /j "C:\Users\YourUsername\AppData\Roaming\Unity\Asset Store-5.x" "X:\WhateverLocationYouWant2"

X:\WhateverLocationYouWant1/2は新しい保存先フォルダ。

以上で、AssetStoreからダウンロードしたアセットは、新規保存先へ保存される。

Oculus Riftの導入(DK2)

Oculus Riftは初代DK1から使っているが、DK2も出てSDKもどんどんアップデートされている。ので、このあたりでUnity導入時の覚書を残しておくことにした。


OculusのDeveloperページ。"Oculus PC SDK"をクリック(この次元では、0.5.0.1 betaが最新だった)。

f:id:yasuda0404:20150510165427p:plain

Oculus Runtime for Windowsをダウンロード f:id:yasuda0404:20150510165449p:plain

SDKのLicense Agreementのページが表示されるので…

f:id:yasuda0404:20150510165507p:plain

チェックボックスにチェックし、”DOWNLOAD NOW”を押してファイルをダウンロードする。

f:id:yasuda0404:20150510171405p:plain

インストールの実行ウィンドウが表示されるので、「実行」。

f:id:yasuda0404:20150510182453p:plain

インストールの開始。'Nect>'

f:id:yasuda0404:20150510182533p:plain

ライセンスへの同意。'I accept ...'を選択して、'Next>' f:id:yasuda0404:20150510182542p:plain

インストール先フォルダ。必要なら変更する。

f:id:yasuda0404:20150510182548p:plain

'Next>'を押すとインストールが始まる。

f:id:yasuda0404:20150510182556p:plain

’Oculus Display Driver(Install Only)'という子ウィンドウが開いてしばらくとまるが我慢して待つ!しびれを切らしてCnacelはおさないように!

f:id:yasuda0404:20150510182602p:plain

途中、「このドライバをインストールしますか」という日本語のウィンドウが開くので「インストール」をクリック。(私の場合、この画面が2度出た。手順が間違っていたのか、そういうものなのか不明)

f:id:yasuda0404:20150510182608p:plain

インストールが完了。'Finish' f:id:yasuda0404:20150510182614p:plain

デフォルトでReadMeテキストが表示される。

f:id:yasuda0404:20150510182621p:plain

ドライバを有効にするには再起動が必要。 f:id:yasuda0404:20150510182627p:plain

Unity4.6以降の場合は、Unity 4 Integrationをダウンロードする

f:id:yasuda0404:20150510171226p:plain

【追記】 現在はつぎのようなインターフェースになっている。

f:id:yasuda0404:20150728205305p:plain

Unity5は、V0.1.0-betaをダウンロード。次のような画面になる。なお、以前のものも'Unity Legacy Integration V0.6.0.1-beta'としてダウンロード可能。

f:id:yasuda0404:20150728195442p:plain

ダウンロードしたZIPファイルを解凍すると、中にOculusUnityIntegration.unitypackageというUnityパッケージがあるので...

f:id:yasuda0404:20150510171605p:plain

このパッケージをUnityに読み込む(Unityのバージョンは4.6.3以上が必要)

f:id:yasuda0404:20150510173033p:plain

Player設定 (Edit>Project Settings...>Player)

f:id:yasuda0404:20150510173326p:plain

※Dirext11はオフにする。

f:id:yasuda0404:20150729181629p:plain

Quality設定 (Edit>Project Settings...>Quality)

f:id:yasuda0404:20150510173345p:plain

Unityとのインテグレーションの場合、Extended Modeにする。詳細は、

qiita.com

www.youtube.com

また、Edit-Preferences... の'Oculus VR'タブで、'Optimize Build for Oculus'にチェックを入れる。

f:id:yasuda0404:20150819141755p:plain

Unity Editor上かどうかで処理内容を変える

Unityの開発はUnity Editorで行うが、Editor上と実際の実行プラットフォーム(Windows, Mac, LinuxiOS, Androidなど)とで処理を変えたい場合がある。これには次の2つの方法がある。

1. "Application.platform"を使う方法

実行時にスクリプト内で現在の実行プラットフォームを見て、処理を変える方法。

docs.unity3d.com

プラットフォームのクラス変数は、"RuntimePlatform"に定義されている。

docs.unity3d.com

2. プリプロセッサ"UNITY_EDITOR"を使う方法

コンパイル時に(つまり実行前に)ターゲットとなるプラットフォームを評価して、実行する処理を選択する方法。実行時に動的に評価するのではなく、プラットフォームに応じて実行ファイル自体を変えてしまうもの。

#if UNITY_EDITOR エディターでの処理 #else エディター以外での処理 #endif

のように記述する。

さらに、エディター上でも、実行中か否かで別処理にしたい場合はApplication.isPlayingと組み合わせて、たとえば、

#if UNITY_EDITOR
    nextBlendTime = Application.isPlaying ? blendTime : 0f;
#else
    nextBlendTime = blendTime;
#endif

のように書けばよい。

より細かく、

  • UNITY_EDITOR_WIN :Windowsのエディタ環境
  • UNITY_EDITOR_OSXMac(OS X)のエディタ環境

などの区別も可能。

  

また、

  • UNITY_4_6

など、Unityのバージョンによって処理を変えることもできる。

プリプロセッサで使用できるプラットフォーム種類の変数は次を参照。

docs.unity3d.com

C#からJavascriptのクラスを呼びだせない時

C#からJavascriptのクラスは、通常のC#クラスと同じように呼び出せる。たとえば、

[SerializeField]protected FPSInputControllerREI fpsInputController; //Javascript Class

ところが、これでは「該当クラスがない」というエラーが出る。

Assets/Scripts/REIControllerDangeon.cs(15,43): error CS0246: The type or namespace name `FPSInputControllerREI' could not be found. Are you missing a using directive or an assembly reference?

この原因は、アセットの読み込み順番で、C#スクリプトと実行する際にJavascriptが読み込まれていないことにある。

Javascriptのクラスを先に読み込ませるために、読み込ませたいJSファイルは、

  • Plugins
  • StandardAssets

などのフォルダにいれる。これでエラーはなくなるはず。

角度指定と外部呼出しに関するベンチマーク

Unityでは、角度は、オイラー角とクォータニオンの2つの方法で扱える。また、回転を設定する際、角度の絶対値と、Rotate()で相対角変化を与える方法がある。いったいどれがもっとも効率的、すなわち高速なのだろうか?

また、あるオブジェクトのクラスから他のオブジェクトを操作する際、操作する側のクラスに操作される側のGameObjectなりTransformコンポーネントを保持して操作を加えるのと、操作される側にクラスを作ってそのPublicメソッドを呼び出すのと、どちらが速いのだろう。

そういう疑問がおきたので、ベンチマークしてみた。

比較結果

ベンチマークスクリプトは下に記す。各方法とも10万回ループの経過時間。

No. 処理内容 時間(mSec)
1-1 オイラー角で設定する 130
1-2 Rotate()関数を使って相対的にまわす 146
1-3 クォータニオンで設定する 123
1-11 ターゲット側のPublic関数に角度をを渡す(外部関数の中身は1-1と同等) 129
1-12 ターゲット側のPublic関数に角度ベクトルをを渡す(外部関数の中身は1-1と同等) 130
1-1A 1-1と同じ処理で、毎回transformコンポーネントを探す 139
1-12A 1-12と同じ処理でループの中で毎回同階層にある外部クラス(コンポーネント)を接続する 191
1-12B 1-12と同じ処理でループの中で毎回外部GameObjectを探す 241
結論
  • 角度の指定はEulerでもQuaternionでも、違いはそんなにない。Quaternionのほうが若干(5%程度) 速い程度。
  • 回転の際、角度を直接指定するより、Rotate()を使うほうが10%程度時間がかかる。
  • Public関数の呼び出しはコストがかからない。つまり、外部コンポーネントを自クラスに取り込んで自クラス内で操作しても、外部コンポーネントのPublic関数を呼び出してその中で操作しても、時間に差はない。
  • (やはり)外部コンポーネント・外部クラスの呼び出しは時間がかかる。呼び出しは最初にやって内部変数にリンクを格納しておくのがベター
ベンチマークスクリプト
// Bench Mark for Rotation & Function Call
// First Version 2015/03/14 by A.Y.@XOOMS
// Last Update   2015/mm/dd by A.Y.@XOOMS
//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Xooms;

namespace Xooms{
    public class BenchMark1 : MonoBehaviour {
        
        //Public Variables
        public Transform targetTrans;

        //Private Variables
        GameObject targetGameObject;
        bool firstTime = true;
        Stopwatch sw = new Stopwatch();
        int maxItr = 100000;
        float delA = 1.0f;
        DummyCube dummyCube;

        //Public Functions

        //--------------
        // Initialization
        void Start () {
            targetGameObject = targetTrans.gameObject;
            dummyCube = targetTrans.GetComponent<DummyCube>();
        }
    
        //
        void Update () {
            if(firstTime) {
                firstTime = false;
                //1.回転関連ベンチマーク
                //1-1 オイラー角で設定する
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    Vector3 v = new Vector3(0.0f, i*delA, 0.0f);
                    targetTrans.eulerAngles = v;
                }
                sw.Stop();
                DebugOut("1-1",sw.ElapsedMilliseconds);
                //
                //1-2 Rotate()関数を使って相対的にまわす
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    targetTrans.Rotate(i*delA*Vector3.up);
                }
                sw.Stop();
                DebugOut("1-2", sw.ElapsedMilliseconds);
                //
                //1-3 クォータニオンで設定する
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    targetTrans.rotation = Quaternion.Euler(0.0f, i*delA, 0.0f);
                }
                sw.Stop();
                DebugOut("1-3", sw.ElapsedMilliseconds);
                //
                //1-1A 1-1と同じ処理で、毎回transformコンポーネントを探す
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    Vector3 v = new Vector3(0.0f, i*delA, 0.0f);
                    targetGameObject.transform.eulerAngles = v;
                }
                sw.Stop();
                DebugOut("1-1A",sw.ElapsedMilliseconds);

                //
                //1-11 ターゲット側のPublic関数に角度をを渡す(外部関数の中身は1-1と同等)
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    dummyCube.RotateByAngle(i*delA);
                }
                sw.Stop();
                DebugOut("1-11",sw.ElapsedMilliseconds);

                //1-12 ターゲット側のPublic関数に角度ベクトルをを渡す(外部関数の中身は1-1と同等)
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    Vector3 v = new Vector3(0.0f, i*delA, 0.0f);
                    dummyCube.RotateByVector(v);
                }
                sw.Stop();
                DebugOut("1-12",sw.ElapsedMilliseconds);

                //1-12A 1-12と同じ処理でループの中で毎回外部クラス(コンポーネント)を探す
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    DummyCube target = targetTrans.GetComponent<DummyCube>();
                    Vector3 v = new Vector3(0.0f, i*delA, 0.0f);
                    target.RotateByVector(v);
                }
                sw.Stop();
                DebugOut("1-12A",sw.ElapsedMilliseconds);

                //1-12B 1-12と同じ処理でループの中で毎回外部GameObjectを探す
                sw.Reset();
                sw.Start();
                for(int i = 0; i < maxItr; i++) {
                    GameObject go = GameObject.Find("DummyCube");
                    DummyCube target =go.GetComponent<DummyCube>();
                    Vector3 v = new Vector3(0.0f, i*delA, 0.0f);
                    target.RotateByVector(v);
                }
                sw.Stop();
                DebugOut("1-12B",sw.ElapsedMilliseconds);
            }
        }

        private void DebugOut(string title, float timeMsec){
            UnityEngine.Debug.Log(title+ ": "+timeMsec.ToString("f1")+" msec");
        }
    }
}