Unityな日々(Unity Geek)

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

入れ子のGeneric Listをインスペクタで使う

C#では、Generic Listを要素にもつGeneric List、すなわち、入れ子(多重)のGeneric Listを定義できる。

public List<List<string>> names = new List <List<string>>();

しかし、入れ子(二重)のGeneric Listは、インスペクタに表示されない。

インスペクタで入れ子Listを使うには、要素となるListをコンストラクタの引数とするクラスを定義し、そのクラスを親Listの要素にすることで実装できる。

次の事例は、'MyClass'クラスのGeneric Listを要素とするGeneric Listを作るもの。

子クラス(この事例では'ChildList')は、[System.Serializable]でシリアル化することを忘れないように。

//Generic ListをList要素とするためのクラス('PageController')
[System.Serializable]
public class ChildList
{
        public List<MyClass> list = new List<MyClass>();

        public ChildList(List<MyClass> _list)
        {
            list = _list;
        }
}

//メインクラス
public class MainClass : MonoBehaviour
{
    public List<ChildList> mainList = new List<ChildList>();
}

インスペクタでは、親List(MainList)の要素(Element0, Element1, ....)として、Listを指定できるようになる。

f:id:yasuda0404:20171219122102p:plain

子Listは、

List<MyClass> mylist = mainList[0].list

のように取り出す。

標準リテラルと逐語リテラル

リテラル」とは「文字通り」という意味。プログラミングにおける「リテラル」は、値や文字をそのまま書き下したものを指す。

リテラル文字列には、「標準リテラル」と「逐語リテラル」の2種類があり、特殊文字の記述方法が異なる。(2.4.4.5 リテラル文字列 (C#)

標準リテラル

文字列をダブルクオーテーションで囲んで記述。

string h = "Hello World"。

特殊文字は、「\(円マーク)+文字」で表す。

たとえば、\“はダブルクオーテーション、\\は円マーク、\tはタブなど。

string e = "Joe said \"Hello\" to me";      // Joe said "Hello" to me
string g = "\\\\server\\share\\file.txt";   // \\server\share\file.txt

逐語リテラル

文字列を、@“(アットマーク)+”(ダブルクオーテーション)文字列"(ダブルクオーテーション)で囲む。すなわち、標準リテラルの前に、@をつけた形。

string h = @"Hello World"。

逐語リテラルでは特殊文字もそのまま記述できる。

string h = @"\\server\share\file.txt";      // \\server\share\file.txt

例外は"(ダブルクオーテーション)で、、"“とダブルクオーテーション2つで、ひとつのダブルクオーテーションをあらわす。

string f = @"Joe said ""Hello"" to me";   // Joe said "Hello" to me

サンプル

画像データパスが変数imageFilePathで与えられているとき、{“url”:“画像データパス”} というJSONデータを記述する:

string data1 = "{\"url\":\"" + imageFilePath + "\"}";    // 標準リテラル形式で記述した場合
string data2 = @"{""url"":""" + imageFilePath + @"""}";    //  逐語リテラル形式で記述した場合

ウェブカメラの映像を表示する

ウェブカメラの映像をテクスチャとして貼り付ける

ウェブカメラの動画を取得するには、WebCamTextureクラスを使う。

docs.unity3d.com

次は、ウェブカメラからの映像をメインテクスチャとして適用するスクリプト。このスクリプトを、たとえばPlaneオブジェクトにアタッチすれば、Plane上にウェブカメラの映像が表示される。

using UnityEngine;
using System.Collections;

public class WebcamCapture : MonoBehaviour {

    //このクラスをウェブカメラの映像をテクスチャとして貼り付けるオブジェクトに適用する

    private WebCamTexture webcamtex;

    // Use this for initialization
    void Start()
    {

        webcamtex = new WebCamTexture();   //コンストラクタ
        
        Renderer _renderer = GetComponent<Renderer>();  //Planeオブジェクトのレンダラ
        _renderer.material.mainTexture = webcamtex;    //mainTextureにWebCamTextureを指定
        webcamtex.Play();  //ウェブカムを作動させる
    }

}

width, height, FPSを明示的に指定する場合は、コンストラクタに引数として与えることもできる

        webcamtex = new WebCamTexture(1280, 800, 30);   //コンストラクタ (width, height, FPS指定)

f:id:yasuda0404:20170418094250p:plain

表示用Planeの設定

WebCamTextureを張り付けるPlaneのアスペクト比は、ウェブカメラのアスペクト比と同じにしておく。

また、表示以外の不要な機能を切っておく。具体的には、次がある。

  • MeshCollider
  • MeshRenderer
    • CastShadows
    • ReceiveShadows
    • MotionVectors
    • LightProbes
    • ReflectionProbes

f:id:yasuda0404:20170418095641p:plain

複数のウェブカメラを使う

複数のウェブカメラが接続されている場合は、WebCamTextureのコンストラクタで、デバイス名を明示的に指定する。

現在、接続されているウェブカメラ・デバイスは、WebCamTexture.devicesで取得でき、デバイス名は、WebCamDevice.nameプロパティで取得する。

    WebCamDevice[] devices = WebCamTexture.devices; //接続されているウェブカメラを取得
        foreach (WebCamDevice device in devices)
        {
            Debug.Log("WebCamDevice " + device.name);
        }
            
        //
        webcamtexA = new WebCamTexture(devices[0].name);   //コンストラクタ (デバイス指定)
        webcamtexB = new WebCamTexture(devices[1].name, 1280, 800, 30);   //コンストラクタ (デバイス, width, height, FPS指定)

ただし、Unityでは同じ種類のウェブカメラは1つしか使えない。たとえ片方のウェブカメラのドライバを強制的に変えて、見かけ上は異なるウェブカメラにしても、だめ。これは、ウェブカメラデバイスが、USBデバイスのレベルで管理されているためと思われる。

同じウェブカメラを複数使うためには、特別な対応が必要になる。これについては別途あらためて書きたい。

?と??ーNull許容型・Null条件演算子

C#のコードの中に、たまに'?‘が出てくる。たとえば次のように。

int? length = customers?.Length;  
Customer first = customers?[0];   
int? count = customers?[0]?.Orders?.Count();  

なんじゃこれは??・・・コードを見てまさに「?」と思っていたところ、これらは「Null演算子」「Null許容型」と呼ばれるものだと知った。恥ずかしながら、最近のことだ。

Null許容型

通常のint型はnullを代入できない。

int length = null;  //エラーになる

と書くと、エラーで通らないのだ。

そこで、「nullを代入できる拡張型int」として作られたのが「Null許容型int」。上の例だと、int? lengthが「Null許容型int」となる。

容易に察せられる通り、int以外の型にも「Null許容型」はある。

int? i = 10;
double? d1 = 3.14;
bool? flag = null;
char? letter = 'a';
int?[] arr = new int?[10];

Null演算子 ‘?.’ ‘?[’

次に、

int? length = customers?.Length; 

の右辺の、customersの後にある'?.‘だが、これは「Null条件演算子」と呼ばれるもの。「?の左側にあるクラスがnullでなければ該当するメンバーを、nullならばnullを返す」。

すなわち、三項演算子による記述、

int? length = customers != null?customers.Length:null;

を、より簡潔にかいたものである。さらに書き下すと

int? length;
if(customers != null){
  length = customers.Length;
}else{
  length = null;
}

と同じだ。

これくらいなら、わざわざ「Null条件演算子」と使わなくても、より可読性の高い三項演算子や、通常のif構文でいいのでは?と思うかもしれないが、「Null条件演算子」は次のような書き方もできる。

int? count = customers?[0]?.Orders?.Count();  // null if customers, the first customer, or Orders is null 

これは、「customers, customers[0], customers[0].Ordersのすべてがnullでない時のみ、customers[0].Orders.Count()を返し、どれかひとつでもnullがあればnullを返す」という評価を、1行で書けてしまう。慣れればコンパクトなコードが書けそうだ。

注)最初の、'customers?[0]‘という表記は、C#4.0、すなわち現行のUnityではエラーになってしまう。

Null合体演算子 '??'

??と?を2つ並べたのがNull合体演算子。A??Bは、「AがNullでないならAを、AがNullならBを返す」もの。

Console.WriteLine(s ?? "Unspecified");

上述したNull演算子と組み合わせて、

A?.B?.C?[0] ?? E 

と書くと、A.B.C[0]が評価でき、nullでなければA.B.C[0]を、nullならばEを返す、ということになる。

int? a = null;
string astr = (a??9999).ToString();
Debug.Log("astr= " + astr); //9999

null許容型の書き方とエラー例(2017/5/20追記)

null許容型変数

int? age = null;
int? myAge = age;
Debug.Log(string.Format("My Age {0}", myAge));   // 出力:"My Age "

※次はビルドエラーになる。null許容型変数を宣言しただけではnullは代入されない。

//ビルドエラー
int? age;   //変数定義のみ
int? myAge = age;   //エラー 'Use of unassigned local variable `age'

null許容型要素を持つ配列

int?[] age = new int?[]{ null};   //null要素をもつnull許容型int配列
int? myAge = age[0];   //OK
Debug.Log(string.Format("My Age {0}", myAge));    // 出力:"My Age "

※次はビルドエラーになる。配列変数宣言だけでは、要素にnullは代入されない //ビルドエラー int?[] age; //変数定義のみ int? myAge = age[0]; //エラー 'Use of unassigned local variable `age'

※次は、ビルドは通るが実行時エラーとなる。int?[]は要素にNullを許容するものであって、配列自体をNull許容型にするものではない。

//実行時エラー
int?[] age = null;   //配列にnullを代入
int? myAge = age[0];   //実行時エラー "NullReferenceException: Object reference not set to an instance of an object"

※次も、C#4.0、すなわち現行のUnityではエラーになってしまう。

int?[] age = null;
int? myAge = age?[0];   //C#4.0ではエラーになる

クラスの場合

クラスメンバーにnull許容型を定義した場合

public class NewBehaviourScript : MonoBehaviour {

    void Start () {
        NullTest nullTest = new NullTest();
        Debug.Log(string.Format("My Age {0}", nullTest.id));  //出力 "My Age "
    }
}

public class NullTest
{
    public int? id = null;
}

※次はエラー

void Start () {
    NullTest nullTest = null;
    Debug.Log(string.Format("My Age {0}", nullTest.id));  //実行時エラー "NullReferenceException: Object reference not set to an instance of an object"
}

※次の書き方もC#4.0では不可。

void Start () {
    NullTest nullTest = null;
    Debug.Log(string.Format("My Age {0}", nullTest?.id));  //ビルドエラー C#4.0ではこのような書き方はできない
    }

C# (.NET Framework)の名前の付け方

UnityでC#のコードを作成するとき、変数やメソッドなどの名前の付け方に迷うことが多々ある。「何が正しいか」は一意に決められないとは思うが、基本的には本家Microsoftの考える「名前の付け方ガイドライン」に従うのがいい。そうすれば、他のメンバーや、社外の人がコードを見た際にも混乱がすくなくてすむ。(作成者自身も、将来、どんな意図でコーディングしたかを忘れるものだから。)

ということで、MSDNガイドラインから、Unity/C#開発に関係が深そうなものを抜粋した。

参考:

.NET Framework-Development Guide-Framework Design Guidelines

Naming Guidelines(英語).aspx)

(日本語の.NET Frameworkのデザインガイドライン:名前付けのガイドライン.aspx)もあるが、翻訳がおかしい箇所が多い)

大文字・小文字の使い分け

  • 変数はCamel形式

  • その他はPascal形式

名前の付け方の一般論

  • わかりやすさを重視
    • 単語の順番は英語と同じに ×AlignmentHorizontal  〇HorizontalAlignment
    • 一般的な単語を使う ×ScrollableX  〇CanScrollHorizontally ('X'が何を示しているか不明瞭)
    • 省略形を使用しない ×OnBtnClk  〇OnButtonClick
    • できるだけ意味を特定する ×GetInt  〇GetLength
  • 英数字以外の文字は使用しない('_‘アンダースコア、’-‘ハイフンなども使用しない)
  • 言語のキーワードを使用しない 

名前空間

<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]
  • Pascal記法を用い、単語を'.‘でつなぐ -ただしブランド名固有の大文字・小文字の使い方がある場合はそれを優先してよい
  • Company:会社名をプレフィクスとして用いる
  • Product|Technology バージョンに依存しない安定した製品名・技術名
  • 名前空間と同じ名前をクラスの中で使用しない

メソッド

  • Pascal表記
  • 動詞または動詞句を使う

例:

public class String {  
    public int CompareTo(...);  
    public string[] Split(...);  
    public string Trim();  
}  

プロパティ

  • Pascal表記
  • 名詞または形容詞を使う
    • プロパティ名に'Get|Set'はつけないこと(メソッドと混乱するため)
  • コレクションの場合は複数形にする(—List, —Collectionなどと書かない)
  • Booleanの場合は、否定形ではなく肯定形のみ使用する ×CantSeek 〇CanSeek
    • 先頭に'Is', ‘Has’, ‘Can'をつけてもよい。ただし意味がある場合のみ。

イベント

  • Pascal表記
  • 動詞または動詞句を使う 例:Clilcked, DroppedDown, Painting
  • イベントの前後を示すのに、'Before|After'接頭句はつけない。動詞の過去形、現在形で表現する。
  • イベントハンドラは、「イベント名+EventHandler」とする。 
  • イベントハンドラの中では、イベント送信者は'sender'、イベントは'e'で表記する 例:public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);
  • イベント引数のクラスは、'—EventArgs'接尾句をつける

変数

  • Camel表記
  • 変数の型ではなく、変数の意味で名前をつける。

より上位の「C#コーディング規則」については次を参照のこと。 C# のコーディング規則 (C# プログラミング ガイド)

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

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();
        }
    }

}

Colorの補間

たとえば、点滅するインジケータなど、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);
        }
    }
}

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

以前、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#で数値を扱うクラス

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

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で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

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: スクリプトから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要素を設定しておくこと!