Unityな日々(Unity Geek)

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

覚えておくと便利な小技(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");
        }
    }
}

uGUIのテキストにブラーがかかってしまう問題

uGUI(Canvas UIシステム)のTextUI要素にブラーがかかってしまう問題は、既知のバグのようだ。

現時点では、対応策はピクセル解像度の大きなテキストを作り、Scaleを小さくして縮小する、という手しかないようだ。

Text UI要素のインスペクタは次のような感じになる。RectTransformのWidth/Hight、FontSizeを大きくし、RectTransformのScaleを0.1とか0.01とかに小さくする。

f:id:yasuda0404:20150312141620p:plain

しかし、この設定でもすこしぼやけた感じがする。早急な対応をお願いします!

uGUI: スクリプトからUIのパラメータを変更する

uGUIへユーザが操作した際にメソッドを呼び出すのとは逆に、スクリプトからUIの状態やパラメータを変更したい場合もある。代表的な例を下にあげる。

テキスト

uGUIのテキストを変更するには、テキストGameObjectのTextコンポーネントを取得し、このText.textプロパティを変更すればよい。

サンプルスクリプトは次。

ポイントは、UnityEngine.UIネームスペースを追加すること!

using UnityEngine;
using UnityEngine.UI;

public class Example : MonoBehaviour
{

 [SerializeField]Text btnText;
  [SerializeField]Toggle toggle;
  [SerializeField]Slider slider;

  private bool isOn = false;
  
  REIPacket packet=new REIPacket();

  public void OnOff()
  {
      isOn = !isOn;
      if(isOn) {
         btnText.text = "On";
         toggle.isOn  = true;
         slider.value = 1.0f;
         slider.Select();
      } else {
         btnText.text = "Off";
         toggle.isOn  = false;
         slider.value = 0.0f;
         toggle.Select();
      }
  }

トグル(チェックボックス)状態

Toggleコンポーネントbool isOnを変更する。

スライダの値

Sliderコンポーネントvalueを変更する。

フォーカス(共通)

各UIコンポーネント.Select()メソッドを呼ぶ。該当UIにフォーカスが移り、その他はフォーカスが外れる。

※注)ゲームコントローラやキーボードからの操作を可能にするよう、EventSystemのFirst Selectedに、最初にハイライトするUI要素を設定しておくこと!

スクリプトのテンプレートを追加する

スクリプトを新規作成する際に、独自のテンプレートを使いたいことがあるとおもう。

その場合は、Unityのインスールフォルダ/Editor/Data/Resources/ScriptTemplates/の下にある、

  • 80-Javascript-NewBehaviourScript.js.txt
  • 81-C# Script-NewBehaviourScript.cs.txt
  • 82-Boo Script-NewBehaviourScript.boo.txt
  • 83-Shader-NewShader.shader.txt
  • 84-Compute Shader-NewComputeShader.compute.txt

を修正するか、あるいは、新規にテンプレートファイルを追加する。

僕の場合、主に使うC#用テンプレートにヘッダコメントとNamespaceをデフォルトで入れたテンプレートを作っている。

// Title
// First Version 2015/mm/dd by A.Y.@XOOMS
// Last Update   2015/mm/dd by A.Y.@XOOMS
//
using UnityEngine;
using System.Collections;
using Xooms;

namespace Xooms{
    public class #SCRIPTNAME# : MonoBehaviour {

        // Initialization
        void Start () {
    
        }
    
        //
        void Update () {
    
        }
    }
}

これを、オリジナルのC#テンプレートに上書きしてもよいが、オリジナルのテンプレートは残したい場合は、別名で保存する。

ファイル名は、'番号+表示名-スクリプト種類.拡張子.txt'となり、'-'ハイフンより前の、番号+表示名は自由に設定できる。番号は数字のほかアルファベットでもいいようだ。要は番号部分のテキストの小さい順番に、メニューに並ぶ。

たとえば81A-C#XOOMS Script-NewXoomsScript.cs.txtという別の名前で保存すると、正規のC#テンプレートの次に、オリジナルテンプレートが表示される。

f:id:yasuda0404:20150309164902p:plain

Materialを比較する

あるオブジェクトにアサインされている特定のマテリアルを別のマテリアルに置き換えたかったので、次のようなコードを書いた。

[SerializeField]protected Renderer rend; //Target Renderer
[SerializeField]protected Material matFrom; //Change from this material
[SerializeField]protected Material matTo;   //Change to this material

<途中省略>

void Change()
{
    Material[] mats = rend.sharedMaterials;
    Debug.Log("ChangeMaterial: mats.Length= "+mats.Length);
    for(int i=0;i<mats.Length;i++) {
        Debug.Log(i+" Spec Int "+mats[i].GetFloat("_SpecInt"));
        if(mats[i] == matFrom) {
            Debug.Log("ChangeMaterial:Material("+i+" changed");
            mats[i]= matTo;     //Replace the material
        }
    }
    rend.materials = mats;  //Reassign Materials
}

ところがこれは機能しない。マテリアルはRunTimeでインスタンス化されるが、インスペクタでアサインしたマテリアルはインスタンスではない。このため、mats[i] == matFromという比較が真にならない。

そこで、マテリアルの名前で比較しようと思い、比較分の箇所を

if((mats[i].name == matFrom.name)...

と書いたが、これもうまくいかない。マテリアルがインスタンス化されると名前も変わるからだ。

結局、

if(mats[i].name == (matFrom.name+" (Instance)")) {

と、元の名前の後に" (Instance)"を加えれば、一応うまくいった。

しかし、もっとスマートな比較方法がないものだろうか…。

"9x9 Slice" Spriteを作成する

UI用のイメージを設定する場合、イメージを拡大しても周囲のラインは画像が荒れるので拡大したくない。これを実現するのが、「9x9スライス」というテクニック。要は、4つの角はスケーリングせず、中間の画像のみ拡大するという手法だ。

これをUnityで実現するには、SpriteとBorderを使う。

まずPhotoshopなどでUI用の画像を作る。シンプルなものなら縦横16pxの正方形にする。PNG形式で書き出す(アルファを使えるPNGがよい)。

f:id:yasuda0404:20150307182000p:plain

Unityで、この画像をアセットして読み込む。

  • Texture Type Sprite(2D and UI)
  • Sprite Mode Single

に設定。

f:id:yasuda0404:20150307182013p:plain

し、Sprite Editorを開き、左下の入力フォームでL/R/B/Tを適当に設定し、9分割のボーダーを引く。Applyで変更を反映。

f:id:yasuda0404:20150307182021p:plain

後は、各UI要素のImageコンポーネントSource Imageに、上で作成したSpriteを適用する。UI要素を拡大しても、エッジラインは保たれる。

f:id:yasuda0404:20150307182601p:plain

CanvasUI(uGUI)要素にアニメーションを設定する

CanvasUI(uGUI)のインタラクティブ要素(Button, Toggle, Sliderなど)は、「操作状態」に応じて「外観」を変更できる。

操作状態

各UI要素の状態には

  • Normal
  • Highlighted
  • Pressed
  • Disabled

の4通りがある。'Highlighted'はフォーカスがあたった状態で、マウスならなくてもよいが、ゲームコントローラで制御する場合は必須。どの要素が選択されているかをフィードバックしなければならないからだ。

外観

外観の変更は各コンポーネントTransitionで行い、

  • none 外観を変えない
  • Color Tine RGBAの変更
  • Sprite Swap 画像の変更
  • Animation アニメーションの付加

の4つの設定がある。

f:id:yasuda0404:20150307155309p:plain

アニメーション

TransitionでAnimationを選択。Animationを使うにはAnimatorコンポーネントが必要。'Auto Generate Animation Controller'をクリックし、AnimationControllerをアセット内に保存する。デフォルトでは、たとえばButton.Controllerのような名前になる。

f:id:yasuda0404:20150307154919p:plain

同時に、AnimatorコンポーネントがUI要素に追加される。

f:id:yasuda0404:20150307160633p:plain

アニメーションはAnimation Window(Window - Animation)で編集する。

f:id:yasuda0404:20150307160901p:plain

Animation Windowの左上、再生ボタンの下をクリックし、編集したいアニメーションを選択する。

Canvas UI(uGUI)のイベント処理

CanvasUI(uGUI)システムでは、ユーザの操作をEventで授受する。たとえばButton要素の場合、インスペクタのButtonスクリプト・ブロックの一番下に、OnClick()という項目がある。ここにOnClick()イベントを受け取る関数と渡す引数を設定する。

f:id:yasuda0404:20150307145538p:plain

タブの"+"をクリックし、左下の枠にイベントを渡すGameObjectを設定し、右上の枠にイベントを送る関数をドロップダウンリストから設定する。左下は関数に渡す引数で、

  • なし
  • float
  • int
  • string
  • bool
  • Object

のいずれか。最後の"Object"はUnityのほとんどのクラス(たとえばGameObject, Component, Monobehaviourなど)のベースクラスなので、実質的にはほとんどのクラスを引数に指定できる。たとえばTransformや、GameObjectそのものも渡すことができる。

ユーザー定義関数を呼び出すサンプル

たとえば、ボタン要素のクリック時のアクションとして、

1) UIControllerのButtonClick()メソッドに、"CustomButton"というstringを渡す場合:

f:id:yasuda0404:20150307142006p:plain

2) ButtonClick2()に、カメラのTransformコンポーネントを渡す場合:

f:id:yasuda0404:20150307141924p:plain

等というように設定する。

なお、UI要素から呼び出すユーザ定義関数はpublic voidのみ、渡せる引数の個数は0か1であることに注意。

ちなみに受け側の関数は次のようになる。

public void ButtonClick(string name)
    {
        Debug.Log("Button "+name+" clicked.");
    }

    public void ButtonClick2(Transform cameraTransform)
    {
        Debug.Log("Camera Position "+cameraTransform.position);
    }
その他のイベント

OnClick()以外のイベントを生成・処理したい場合は、EventTriggerを使う。

イベントを生成したいUI要素を選択し、Component-Event-EventTriggerを選択。

f:id:yasuda0404:20150307144247p:plain

UI要素にEventTriggerコンポーネントが追加される。Add New Event Typeをクリックして該当するイベントタイプを選択、後はOnClick()と同様に、イベント受信先のGameObject、イベントを受け取る関数、引数を設定する。

f:id:yasuda0404:20150307144540p:plain

EventTriggerを使えば自由にEventを生成できる。たとえば、UIImage要素はデフォルトではイベントを持っていないが、EventTriggerを使ってOnClickPointer Enterイベントを追加することができる。

参考:

qiita.com

Canvas(uGUI)の基本レイアウト

Unity4.6から導入された新GUI、"uGUI"。従来のGUIより柔軟な設定ができるuGUIの導入についてまとめた。

CanvasUI要素をシーンに配置する

ヒエラルキビュー、Create-UIで、CanvasのUI要素が表示される。CanvasUIは、まず土台となるCanvasをシーンに配置し、その上に各UI要素を乗せていくという概念。

しかし、いきなりUI要素を選択しても、自動的にCanvasを配置してくれる。ここではUI要素のPanelを選択してみる。

f:id:yasuda0404:20150307111027p:plain

するとこのように、ヒエラルキビューに、Canvas-Panelが配置される。EventSystemというオブジェクトも自動生成される。

f:id:yasuda0404:20150307111600p:plain

続いて、Create-UIでButtonを選択。こんな感じになる。

f:id:yasuda0404:20150307112753p:plain

f:id:yasuda0404:20150307111019p:plain

Canvasコンポーネント

以下、Canvas特有のコンポーネントについて。

Canvasのインスペクタで指定するRender Modeには、次の3つのモードがある。

  • Screen Space - Overlay すべてのカメラのレンダリング画面上にオーバーレイ表示する
  • Screen Space - Camera 特定のカメラの画角いっぱいになるよう自動拡大される
  • World Space シーン内の3D空間に配置する

OverlayとCameraは機能的には似ているが、OverlayはPost処理としてレンダリング画面に上書きされるのに対し、Cameraはあくまでも3D空間上のオブジェクトとしてカメラの画角いっぱいに正対して表示される。このため、Cameraの場合は、そのカメラのみに有効であることと、カメラの近くにオブジェクトがある場合、Canvasはその背後に表示される、という違いがある。Unity Manal-Canvas

ここではWorld Spaceを選択し、メインカメラから見える位置に移動する。

f:id:yasuda0404:20150307111035p:plain

Rect Transformコンポーネント

CanvasUI要素の配置はRect Transformコンポーネントがつかさどる。これはtransformコンポーネントと似ているが、UI設定に便利なように違いもある。

Scale

移動・回転は一般のGameObjectと同じように扱える。

大きさの変更は"Scale Tool"と"Rect Tool"の2通りの方法がある。"Scale Tool"は文字通りScaleを変更するが、"Rect Tool"をRect Transformコンポーネントを持つオブジェクト(すなわちCanvasUI要素)に適用した場合、Scaleは変わらない。その代わりにWidth/Heightが変更される。スケール変更との違いは、フォントサイズやボーダーライン太さなどに影響しないこと

Pivot

PivotはUI要素の2次元平面上の回転中心。左下が(0,0)、右上が(1,1)の相対座標であたえる。ヒエラルキビュー上では、Pivot点は中抜きの青丸で表示される。PivotモードではPivotを中心とした回転に、Centerモードではオブジェクト中心を基準に回転させることができる。

"Rect Tool"は、シーン上のUI要素の辺または角をつかんでドラッグすることで行える。また、

f:id:yasuda0404:20150307111002p:plain f:id:yasuda0404:20150307111011p:plain

Anchor

CanvasUIの要素の位置はすべて親要素にたいする相対位置で指定する。この相対位置を定義するのがAnchor。ヒエラルキビュー上は(4つの)中抜きひし形で示される。

AnchorにはMax/Minの2つの座標がある。

Max/Minを同じ値にすると、Anchorha一点になる。たとえば、親要素の右下コーナーにAnchorを設定すると、親要素のサイズが変わっても、子要素の右下からの位置は一定に保たれる。

Max/Minに違う値を設定すると、位置とともに子要素のサイズも変わる。たとえば、親要素の左下と右下にAnchorを設定すれば、親要素の幅が変わったときに、親要素との左右のすき間が一定となるように、子要素の幅が変わる。

Unity APIにあるアニメーションが参考になる。

なお、Anchorにはプリセットが用意されているので、通常はここから選べば事足りる(と思う)。厳密に指定したい場合はインスペクタで数値を入力することもできる。

f:id:yasuda0404:20150307121623p:plain

余談

Overlayモードでは、Canvasはシーン上でZ方向にしか動かせないらしい。もっともどこに配置してもいいわけだが。また、Z座標が±1000を越えるとカメラスクリーンに表示されなくなるようなので、Z座標で表示非表示を変更できる?

ジェネリックリストのジェネリックリスト(ジェネリックリストの入れ子)を作る

以前、「Unityで使える広義の配列」で書いたように、最近のC#では、ArrayListよりGeneric Listが推奨されている。

要素の型を指定できるので、よりロバストなコードがかけるGeneric Listだが、Generic Listの要素にGeneric Listを指定する、すなわち、Generic Listの入れ子はできるのだろうか?

答えはYes.

もともとGeneric ListはJavascript配列のように後から要素を追加(Addメソッドを使う)できるのが便利。なので、Generic ListのGeneric Listが可能なら、いわゆる「ジャグ配列」のようなリストを作ることができる。

これをやりたかった理由として、ツリー階層構造にあるGameObjectから、下の階層のGameObjectを自動的に取り出してGeneric List化したかったことがある。たとえば、

f:id:yasuda0404:20150306214040p:plain

のようなGameObjectの階層があり、このトップのMenuObjectのみから下の子GameObjectを自動的にリスト化したかった(なお、今回は階層数は固定とする)。

このスクリプトが次。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Test2 : MonoBehaviour {

    [SerializeField] protected Transform top;  //'MenuObject'を指定

    private List<GameObject> layer1;
    private List<List<GameObject>> layer2;

    // Use this for initialization
    void Start () {
        layer1 = new List<GameObject>();    //第1階層のGneric List
        layer2 = new List<List<GameObject>>();   //第2階層のGeneric List
        foreach(Transform elem1 in top) {
            layer1.Add(elem1.gameObject);
            List<GameObject> list = new List<GameObject>();
            foreach(Transform elem2 in elem1) {
                list.Add(elem2.gameObject);
            }
            layer2.Add(list);
        }

        //チェック1 Generic List的に扱う
        foreach(List<GameObject> list in layer2) {
            foreach(GameObject elem in list) {
                Debug.Log("child:"+elem.name);  //第2階層の要素
            }
        }

        //チェック2 ArryList的に扱う
        for(int i = 0; i < layer1.Count; i++) {
            Debug.Log("layer1["+i+"] = "+layer1[i].name);   //第1階層の要素
            for(int j = 0; j < layer2[i].Count; j++) {
                Debug.Log("layer2["+i+"]["+j+"] = "+layer2[i][j].name); //第2階層の要素
            }
        }
    }
    
<<<以下略>>>

layer1が第1階層のGameObjectのGeneric List、layer2が第2階層のGameObjectからなるGeneric Listになる。

上の"Check Process"は、layer1, layer2の要素のGameObjectの名前をDebug.Logで出力するものだが、Generic ListはArrayListのように要素インデックスでも指定できる。(たとえば、layer2[i][j])。場合によってはインデックスで指定したほうが便利なときもあるので、使い分ければよいと思う。