読者です 読者をやめる 読者になる 読者になる

Unityな日々(Unity Geek)

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

Unityエディタをバッチで起動し、プロジェクトにコマンドライン引数を渡す

エディター操作

Unityエディタをバッチファイルから起動するには、

"C:\Program Files\Unity543\Editor\Unity.exe"

と書いたバッチファイルを作って、ダブルクリックすればよい。""でくくっているのは、フォルダ名Program Filesがスペースを含んでいるため。

ちなみに、Unityエディタのショートカットのプロパティを見ると、やはり""でくくられているはず。これをコピペすればいい)

f:id:yasuda0404:20170117175514p:plain

特定のプロジェクトを開くには、-projectPath プロジェクトのパスをつかって

"C:\Program Files\Unity543\Editor\Unity.exe" -projectPath G:\_project\unity\MyProject

とする。ただし、パスは絶対パスでないと通らない(ようだ)。

パス以外に、さらにコマンドライン引数を加えてやれば、そのコマンドライン引数はプロジェクトから参照できる。たとえば、

"C:\Program Files\Unity543\Editor\Unity.exe" -projectPath G:\_project\unity\MyProject -DataDir G:\Data

とすると、エディタ内でプレイモードを起動したときに、コマンドライン引数 -DataDir G:\Dataが渡される。

オブジェクトをきれいに配置する

エディター操作

等間隔スナップ

オブジェクトを等間隔で並べたり、スケーリングしたりする方法。

設定

Edit => Snap Settings... で各軸のPositionおよびScaleのスナップ幅を設定する

移動

Control(MacはCommand)キーを押しながら、Sceneビューでオブジェクトをドラッグする。

下は、X軸のSnapを3、Z軸のSnapを2に設定し、座標(0,0,0)に複数のオブジェクトを複数Duplicateして、それらを、Controlキーを押しながらX方向、Z方向に移動したもの。

f:id:yasuda0404:20161028133644p:plain

スケール

Control(MacはCommand)キーを押しながら、Sceneビューで各軸のスケールを増減する。

頂点スナップ

2つのオブジェクトの特定の頂点座標をあわせて配置する方法。

Vキーを押しながら、移動させたいオブジェクトの、基準にしたい頂点を選択。直交矢印の中心部が黄色の正方形に変わる。

f:id:yasuda0404:20161028135016p:plain

正方形をドラッグして、他のオブジェクトのあわせたい頂点に重ねる。

f:id:yasuda0404:20161028135211p:plain

表面スナップ

移動モードで、Control+Shiftキーを押す。直行矢印の中心部分が黄色の正方形に変わるので、ここをドラッグして他のオブジェクトに近づける。あるところで面に接したところにスナップされる。(調整が少し難しい)

f:id:yasuda0404:20161028141930p:plain

**表面スナップは、Centerモードでしかうまく行かない?Pivotではめり込んだ状態でスナップされてしまう。操作方法が悪いのだろうか?

f:id:yasuda0404:20161028142021p:plain

オブジェクトのフェードイン・フェードアウト

script-基本

Standard Shaderを適用したモデルのフェードイン・フェードアウトは、Colorのアルファ値を変更することによって、行う。

Color値を変えるメソッドは、

Material.SetColor(string propertyName, Color color)

で、第1引数はどのColorを変えるかを指定するもの。

具体的には、Standard Shaderでは、

  • _Color
  • _EmissionColor

の2種類のColorクラスがあるので、どちらを指定する。

第2引数は、設定するColor.これは、RGBAのプロパティを持っているので、このうちのA(アルファ)を変えることで、フェードイン・アウトを実装する。

ポイントは、Materialから取得したColorに対して変更を加えるだけではだめで、変更したColorを、SetColor()で対象Materialに戻してやらなければいけない、ということ。

サンプルスクリプト

同じマテリアルを適用した複数のオブジェクトのフェードイン・フェードアウトを行うスクリプト。アルファ値の変更は、iTweenのValueTo()を使った。

public class Fader : MonoBehaviour {

    public GameObject[] targets;   //Target GameObject
    private Material[] mats; //Target Material. Shader must be set to Fade or Transparent
    public float alphaMax = 1.0f;
    public float alphaMin = 0.0f;

    private Color color;

    // Use this for initialization
    void Start () {
        mats = new Material[targets.Length];
        int i = 0;
        foreach (GameObject target in targets)
        {
            mats[i] = target.GetComponent<Renderer>().material;
            i++;
        }
        color = mats[0].GetColor("_Color"); //GetColor()で、現在のColorを取得。このスクリプトでは、すべてのオブジェクトのColorは同じと仮定している。
    }

    public void FadeOut()
    {
        Debug.Log("FadeOut()");
        iTween.ValueTo(gameObject, iTween.Hash(
            "from", alphaMax,
            "to", alphaMin,
            "onupdatetarget", gameObject,
            "onupdate", "updateFromValue",
            "time", 0.8f,
            "delay", 0.0f,
            "easeType", "easeOutQuad",
            "oncomplete", "FadeOutComplete"
            ));
    }

    public void FadeIn()
    {
        Debug.Log("FadeIn()");
        iTween.ValueTo(gameObject, iTween.Hash(
            "from", alphaMin,
            "to", alphaMax,
            "onupdatetarget", gameObject,
            "onupdate", "updateFromValue",
            "time", 0.8f,
            "delay", 0.0f,
            "easeType", "easeOutQuad",
            "oncomplete", "FadeInComplete"
            ));
    }

    private void updateFromValue(float newValue)
    {
        color.a = newValue;
        foreach (Material mat in mats)
        {
            mat.SetColor("_Color", color);    // SetColor()で、変更したColorをMaterialに戻す
        }
    }

    private void FadeOutComplete()
    {
        Debug.Log("Fade Out Complete");
    }

  private void FadeInComplete()
    {
        Debug.Log("Fade In Complete");
    }

    void Update()
    {
        if(Input.GetKeyDown("a")){
            FadeOut();
        }
        if (Input.GetKeyDown("z"))
        {
            FadeIn();
        }
    }

}

モデルデータの書き出しサイズを小さくする Mesh Compression

アセット・コンポーネント

ビルドサイズを小さくする手段の一つに、モデルデータのメッシュサイズ圧縮がある。

これは、モデルデータのインスペクタ:Mesh Compressionで行う。

選択肢は、Off/Low/Medium/Highの4種類ある。

f:id:yasuda0404:20160915145848p:plain

オリジナルのサイズが約38MBのFBXデータをAssetに読み込み、Mesh Compressionの設定を変えてビルドしてみた。

なお、Windowsの場合、ビルドすると、任意名.exeと、任意名Data フォルダの2つのファイルができるが、Mesh Compressionでサイズが変わるのは、任意名Dataのみ。exeの方は影響を受けない。

シーンにモデルと、カメラ・ライトをおいただけのシンプルなシーンをビルドした結果の、それぞれのレンダリング画像と、***_Dataフォルダのサイズを以下に示す。

あくまでも、ひとつのケースの事例であるが、Off(Mesh Compressionなし)とLowの差は大きく、***_Dataフォルダのサイズは約半分にまで小さくなる。

一方、Highは、円筒部分の粗さが目立つ結果となった。

1. Off (***_Dataフォルダのサイズ 73.4MB) - Mesh Compression なし

f:id:yasuda0404:20160915150308p:plain

2. Low (33.8MB)

f:id:yasuda0404:20160915150349p:plain

3. Medium (31.8MB)

f:id:yasuda0404:20160915150407p:plain

4. High (27.6MB)

f:id:yasuda0404:20160915150426p:plain

なお、Mesh Compressionはビルド時に行われるものなので、アセット内のモデルデータは影響を受けない。

つまり、いったんHighに設定し他モデルを、シーンに配置したまま、Projectビューのモデルデータの設定をLowやOffにしても、書き出しサイズは問題なく変わる。この点は、3DCGソフトでポリゴンリダクションを行うのに比べて、結果を見ながら設定を変えられるので便利。

プロジェクトのUnityバージョンを確認する

アセット・コンポーネント

過去のプロジェクトが、どのUnityバージョンで作ったのかを確認する(裏)ワザ。

ProjectSettings/ProjectSetting.assetを、VisualStudioなどのエディターで開く。

バイナリファイルなので、基本、意味不明の文字列が表示されるが、最初のほうにUnityバージョンの文字列が見えるはず。

↓下は、VisualStudioで開いた場合。(「メモ帳」などのテキストエディタと比べると、ダンプ形式で整然と並べてくれるので精神衛生上ベター?)

f:id:yasuda0404:20160913094522p:plain

Colorの補間

script-基本

たとえば、点滅するインジケータなど、2つのカラー値を補間して表示したい場合に便利なのが、Color.Lerp()

public static Color Lerp(Color a, Color b, float t);

という形式で、t=0の時 Color a、t=1の時 Color b、t=0~1でaとbの間を線形補完する。

点滅(Blinking)するインジケータを実現するには、UpdateループとMathf.PingPong()を使って次のように実装できる。

void Update () {
    if (isBlinking)
    {
        if (myMat.HasProperty("_EmissionColor"))
        {
            Color color = Color.Lerp(colorOff, colorOn, Mathf.PingPong(Time.time * speed, 1.0F); 
            myMat.SetColor("_EmissionColor", color);
        }
    }
}

スクリプトからShaderパラメータを変更する(例:_EmissionColorについて)

Shaderのプロパティを変更するには、materialのメソッド、SetColor(), SetFloat(), SetInt(), SetTexture()を使う。( Unity - マニュアル: スクリプトを使用したマテリアルパラメーターへのアクセスと変更

それぞれのメソッドは、たとえば、SetFloat(シェーダプロパティ名, 値)という形をとる。

シェーダープロパティ名は、それぞれのシェーダに依存する。どんなシェーダプロパティがあるかは、Materialコンポーネントで、"Edit Shader..."を選択すればよい。

f:id:yasuda0404:20160730125025p:plain

UnityのStandard Shaderのプロパティは次の通り。

f:id:yasuda0404:20160730125345p:plain

ちなみに、"Compile and show code"をクリックすると、Shaderを編集できる。

たとえば、'_EmissionColor'は、'Color:Color'となっているので、

GetComponent<Renderer>().material = SetColor("_EmissionColor", Color.red)

のようにすればいい。

指定するShaderプロパティが存在しない場合は無視されるようだが、プロパティが存在するかどうかをチェックしてから値を設定したほうが、スクリプトとしてはよりロバスト。これは、material.HasProperty()を使う。

myMat = GetComponent<Renderer>().material;
if (myMat.HasProperty("_EmissionColor")) {
    myMat.SetColor("_EmissionColor", Color.black);
}

ここでひとつ疑問が。

今回は、「点滅するインジケータ」をEmissionの'Intensity'―Inspectorでは、下のEmissionのカラーボックスの右にある数字(現在1)―を上下して実現しようと考えた。しかし、Shaerプロパティに、Emission Intensityに相当するものが見当たらない。

f:id:yasuda0404:20160730130337p:plain

どうも、Emissionの強度の数字は、_EmissionColorのA(アルファ)値のようだ。

そこで、次のようなスクリプトを組むと、無事「1秒周期で点滅するインジケータ」ができた。('1.0f-val*val'の部分は、点滅パタンによって変更すればいい)

    void Update () {
        float val = Mathf.PingPong(Time.time, 1.0F);
        Color color = new Color(0f, 1.0f - val*val, 0f);    //Green + Alpha-Channel
        myMat.SetColor("_EmissionColor", color);
    }

スクリプトからシーンをロードする (Unity5.3~)

別なシーンのロードは従来、

Application.LoadLevel ("SceneNew");

としていたが、Unity5.3以降では使えなくなった(Obsolete).

Unity5.3以降でシーンをロードする際は

SceneManager.LoadScene ("SceneNew");

とする。あるいは、シーン番号を使って、

SceneManager.LoadScene (シーン番号);

としてもいい。

なお、読み込むシーンはBuild Settingsの'Scenes in Build'に登録しておく必要がある。

f:id:yasuda0404:20160516164335p:plain

【追記】

SceneMangerクラスを使うには、UnityEngine.SceneManagement をインクルードする必要がある。

XMLファイル読み込みのサンプルスクリプト

script-基本

以前、XmlDocumentクラスを使ってXMLを読み込み、パースする方法について書いた。

XmlDocument()でXMLをパースする その1 - Unityな日々(Unity Geek)

XmlDocument()でXMLをパースする その2 - Unityな日々(Unity Geek)

が、XMLファイルの読み込み&パースは、ファイルパスの指定方法を含めて結構忘れるので、再度、備忘録としてサンプルスクリプトをまとめておく。

サンプルスクリプト

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;   //XMLを扱う場合に必要
using Xooms;

namespace Xooms{
    public class FelicaDataSet : MonoBehaviour {
        
        //Public Variables
        public bool isFileInput = false;
        public string dirname   ="data";    //Output Directory
        public string fname ="felicadata";      //File-Header ** '.xml' not necessary !

        //Private Variables
        private XmlDocument xmlDoc;

        //Public Functions

        //--------------
        // Initialization
        void Start () {
            if(isFileInput) {
                xmlDoc = ReadXML();
                ParseXML(xmlDoc);
            } 
        }


        private XmlDocument ReadXML()   //XMLファイルを読み込む
        {
            //
            string dlm ="/";
            string path;
            if(dirname != "") {
                path = dlm + dirname + dlm;
            } else {
                path = dlm;
            }
            string fullpath;
            if(Application.isEditor){
                fullpath = Application.dataPath + path + fname; //エディタの場合, Application.dataPathは'Asset'フォルダ
            }else{
                fullpath = Application.dataPath + dlm + ".." + path + fname;    //PC/Macの場合,Application.dataPathは、'実行ファイル_data'フォルダ

            }
            XmlDocument xmlDoc = new XmlDocument(); //XmlDocumentクラス
            xmlDoc.Load(fullpath); // load the file.    //XMLデータをロード

            return xmlDoc;  //読み込んだXmlDocumentを返す
        }

        private void ParseXML(XmlDocument xmlDoc){  //XMLをパースする
            //Parse XML List
            XmlNode all = xmlDoc.FirstChild;    //最初のノード 'FelicaData'タグ
            Debug.Log("FirstChild " + all.InnerText);   //子ノードをふくむ、すべてのタグのテキストが表示される
            //
            XmlNodeList header = all.FirstChild.ChildNodes; //最初のノード='Header'タグの、子ノードのリスト
            foreach (XmlNode node in header){
                Debug.Log(node.Name + ", " + node.InnerText);   //タグ名と、テキストを表示
            }
            //
            XmlNodeList models = xmlDoc.GetElementsByTagName("Model");  //'Model'タグのリストを作る
            foreach (XmlNode model in models){
                Debug.Log(model.Attributes["id"].Value + ", " + model.InnerText);   //属性'id'と、テキストを表示
            }
            //
            XmlNodeList actions = xmlDoc.GetElementsByTagName("Action");    //'Action'タグのリストを作る
            foreach (XmlNode action in actions){
                Debug.Log(action.Attributes["id"].Value + ", " + action.InnerText); //属性'id'と、テキストを表示
            }
        }
    

    }
}

サンプルデータ sampledata.xml

<FelicaData>
  <Header>
    <Title>Test Data</Title>
    <Subtitle>Input Test</Subtitle>
    <Date>2015/9/13</Date>
  </Header>
  <Models>
    <Model id="00" >012E34E7C00D7D58</Model>
    <Model id="01" >012E34E7C00D7C42</Model>
    <Model id="02" >012E34E7C00D7D27</Model>
    <Model id="03" >012E34E7C00D7DB1</Model>
    <Model id="04" >012E34E7C00D7AAB</Model>
  </Models>
  <Actions>
    <Action id="00" >012E34E7C00D7BAD</Action>
    <Action id="01" >012E34E7C00D7E83</Action>
    <Action id="02" >012E34E7C00D7D1D</Action>
    <Action id="03" >012E34E7C00D88BA</Action>
    <Action id="04" >012E34E7C00D7E3A</Action>
  </Actions>
</FelicaData>

補足

  • XMLファイルの読み込みは、XmlDocument.Load(ファイルパス)で行う。一方、.LoadXml()メソッドは、XMLテキストを書き下すばあいに使用。

例:

doc.LoadXml("<book xmlns:bk='urn:samples' bk:ISBN='1-861001-57-5'>" +
                "<title>Pride And Prejudice</title>" +
                "</book>");
  • XmlNode all = xmlDoc.FirstChildは、XMLの最初のノード、すなわちになる。 all.InnerText)は、子ノードをふくむすべてのテキストをつなぎあわせたもの('Test DataInput Test2015/9/13012E34E7C00D7D58012E34E7C00D7C42012E34E7C00D7D27012E34E7C00D7DB1012E34E7C00D7AAB012E34E7C00D7BAD012E34E7C00D7E83012E34E7C00D7D1D012E34E7C00D88BA012E34E7C00D7E3A')になる。

  • XmlNodeList header = all.FirstChild.ChildNodesは、の最初の子ノード、すなわち

    になる。node.Nameはタグ名、node.InnerTextはテキスト名。

Debug.Log(node.Name + ", " + node.InnerText);の出力は次にようになる。

f:id:yasuda0404:20150913174826p:plain

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

script-基本

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

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

script-発展

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

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にテキストを設定する際の注意点

script-基本

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の参考資料

script-基本 アセット・コンポーネント

スクリプトでアニメーションを行う定番ライブラリが、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で見ると、冒頭の目標画面イメージのとおりになっている。