Unityな日々(Unity Geek)

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

Mouse入力 Event, OnMouse, Input

Mouse入力(および、タッチセンサー入力)を受け取るには、次の3つの方法がある。

1)Event
キー入力とMouse操作が発生した時に発信されるEventを受け取る。
ただし、Eventは、OnGUIの中のみで受け取れる(ようだ)

using UnityEngine;
using System.Collections;

public class RotateObject : MonoBehaviour {
	void OnGUI(){
		Event e = Event.current;
		if(e.button == 0 && e.isMouse){
			Debug.Log (e.delta);
		}
	}
}

e.buttonは、マウスボタンの種類。

e.button 0:左ボタン、1:右ボタン、2:中ボタン

e.isMouse == true は、マウスイベントであることを示す。


2)OnMouse
MonoBehaviourのメソッドとして用意されている、Mouse関連の関数を使う方法
Scriptのアタッチ先は、GUIElemmentかColliderを持つオブジェクトのみで、オブジェクトとマウスとの関係が重要

OnMouseEnter オブジェクト上に入った時に一度だけ呼ばれる。ボタンの状態は無関係
OnMouseExit オブジェクトから出た時に一度だけ呼ばれる。ボタンの状態は無関係
OnMouseDown オブジェクト上で左ボタンを押した。オブジェクト外で左ボタンを押してからオブジェクト内に入っても呼ばれない
OnMouseDrag マウス左ボタンを押したまま動かした。押したままオブジェクト外にでても有効
OnMouseOver オブジェクト上に入っている状態。OnMouseEnterと違って何度も呼ばれる
OnMouseUp オブジェクト上で左ボタンをアップ。オブジェクト外でアップしても呼ばれる。
OnMouseUpAsButton オブジェクト上で左ボタンをアップ。オブジェクト外でアップした場合は呼ばれない。


3)Input
アタッチされたオブジェクトと関係なくMouseの動作を取得する。通常は、Update()、FixedUpdate()の中で使用する。

GetMouseButton 左ボタンをおした状態。毎回呼ばれる。
GetMouseButtonDown 左ボタンをおした動作。押した際に一度だけ呼ばれる
GetMouseButtonUp 左ボタンを話した動作。一度だけ呼ばれる

*引数はすべて共通で、0:左ボタン、1:右ボタン、2:中ボタン

LineRendererを使う

例えば、空間上に引出線を描画する場合、線に相当するオブジェクトを置くよりも、LineRendererを使ったほうがスマート。

1)Scriptの例
  下の例は、Transformの配列(transforms)に格納した座標間にラインを描画するもの。

using UnityEngine;
using System.Collections;

public class LineTest : MonoBehaviour {
	
	public Transform[] transforms;
	LineRenderer lineRenderer;
	
	// Use this for initialization
	void Awake () {
		lineRenderer = GetComponent<LineRenderer>();
		lineRenderer.SetVertexCount(transforms.Length);
	}
	
	// Update is called once per frame
	void Update () {
		for(int i=0; i<transforms.Length;i++){
			lineRenderer.SetPosition(i, transforms[i].position);
		}
	}
}

2)上のScriptのアタッチ先オブジェクトに、LineRendererコンポーネントをアタッチする。ラインの色や太さなどはLineRendererコンポーネントで指定する。
f:id:yasuda0404:20130325174039p:plain
f:id:yasuda0404:20130325174048p:plain

3Dプロジェクタを設定する

Unityから3Dモニタ/プロジェクタに出力する際の設定覚書。
ちなみにサンプルの環境は、GeForce680のHDMIポートに、SANYO PDG-DWL2500(Panasonic CT230)とMitsubishi RDT231WMがつながっている状態である。DWL2500は解像度1280X800の3Dプロジェクタ、RDT231WMはFullHD(1920X1080)の液晶モニタだ。

nVIDIAコントロールパネルを開き、ディスプレイ-デスクトップのサイズと位置の調整で、3DVisionを動かすために、3Dプロジェクタのリフレッシュレートを120Hzにする。

次に、解像度を機器のスペックに合わせる。DWL2500の場合、1280X720。(注:DW2500のスペック上は1280X800まで表示できるはずだが、なぜか120Hzでは1280X720が上限。これはGPUの制約?)
f:id:yasuda0404:20130303113959p:plain

RDT231WMはスケーリングモードを「全画面表示」にしておく。こうしないと、後でDWL2500をプライマリにしてクローン化した際、RDT231WM側も全画面に表示されないため。解像度も1280X720にしているが、ここはFullHDのままでもよい。
f:id:yasuda0404:20130303113949p:plain


ステレオスコピック3D-ステレオスコピック3Dを設定します、で、
「ステレオスコピック3Dを有効にする」にチェックマークを入れる。
f:id:yasuda0404:20130303114008p:plain

次に、「ステレオスコピック3Dディスプレイのタイプ」に、3Dプロジェクタ、今回の場合、Sanyo PDG-DWL2500を選択する。(チェックマークだけ入れて、このディスプレイ選択をよく忘れるので注意!) 初めて動かす際は、「ステレオスコピック3Dのテスト」を実施して、3DVisionの機器設定を行う。
f:id:yasuda0404:20130303114015p:plain

ディスプレイ-複数ディスプレイの設定で、DWL2500の画面アイコン上で右クリック。
DWL2500を「プライマリする」を選択し、次に、
DWL2500上「クローンに使用」を選択。モニタ2(RDT231WM)をクローン化する。
f:id:yasuda0404:20130303114021p:plain

アイコンが下のような状態になる。
f:id:yasuda0404:20130303115623p:plain

以上で3Dの設定が完了。Unityから作成したEXEファイルを、解像度1280X720, フルスクリーン("Windowed"のチェックを外した状態)で実行すれば、立体視投影される。
f:id:yasuda0404:20130303120057p:plain


【注】もうひとつ、キーボードで'cntrl+T'を押すと、3D表示がオン・オフにトグルされる。この状態変化はドライバ側では認識されないので注意。実際、ドライバはちゃんと設定したのに3D表示されないことがあり、原因は'cntrl+T'だったことがある。

Prefabのインスタンスを生成する

Project内に次のPrefab (BallPrefab)があるとする。
f:id:yasuda0404:20130302160328p:plain

このPrefabをシーン内に生成するには、Instantiate()を使う。
たとえば次のScriptを作り、シーン内のGameObjectにアタッチ。

using UnityEngine;
using System.Collections;

public class MainScript : MonoBehaviour {
	public GameObject ball;
	
	// Use this for initialization
	void Start () {
		for (int i=0 ; i<3 ; ++i) {
      		      Instantiate(ball,new Vector3(Random.Range(-0.5f,0.5f)*10,i*10+10,Random.Range(-0.5f,0.5f)*10),Quaternion.identity);
    	        }
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

Instantiateの引数は、複製したいオブジェクト(Object)、位置(Vector3D)、回転(Quaternion)。
public変数として定義した'ball'に、Project内のPrefabをひもづける。
f:id:yasuda0404:20130302160647p:plain

Instantiate()の戻り値は'Object'のため、戻り値は使用するにはキャストが必要。
RigidBodyにキャスト:

	void Init () {
		Rigidbody[] obj = new Rigidbody[3];
		for (int i=0 ; i<3 ; ++i) {
      		obj[i] = Instantiate(ball,new Vector3(Random.Range(-0.5f,0.5f)*10,i*10+10,Random.Range(-0.5f,0.5f)*10),Quaternion.identity) as Rigidbody;
    	}

GameObjectにキャスト:

	void Init () {
		GameObject[] obj = new GameObject[3];
		for (int i=0 ; i<3 ; ++i) {
      		obj[i] = Instantiate(ball,new Vector3(Random.Range(-0.5f,0.5f)*10,i*10+10,Random.Range(-0.5f,0.5f)*10),Quaternion.identity) as GameObject;
    	}


このシーンを実行すると、Project内のBallPrefabがランダムな位置に生成される。
f:id:yasuda0404:20130302160835p:plain

                                                                      • -

[追記:2015/3/9]

上の手順では、インスタンス化したGameObjectはシーンの最上位階層におかれる。インスタンス化したGameObjectを特定のGameObjectの下(子供)におきたい場合は、

obj[i] = Instantiate(ball,new Vector3(Random.Range(-0.5f,0.5f)*10,i*10+10,Random.Range(-0.5f,0.5f)*10),Quaternion.identity) as GameObject;
obj[i].transform.parent = targetGameObject.transform;
    	}

と、transform.parentをつなぎなおせばいよい。

Textureの設定

オブジェクトへのTextureの貼付は
1)AssetにTexure画像を読み込み、
2) その画像をMaterialに貼り、
3) そのMaterialをObjectに適用する
と言う手順になる。

この時、Texture画像の設定によってレンダリング後の見え方が違う。Assetで画像データを選択した状態でInspectorで設定する。(以下、そろそろ4にアップグレードしなければと思いつつ、まだ3.5で動かしてます…)
f:id:yasuda0404:20130228161813p:plain

1.Filter Mode
1-1) Pointは画像そのまま表示
f:id:yasuda0404:20130228161916p:plain

1-2) Bilnear/Trilinearはカメラがある程度近づくとブラーをかける。
Bilnearの場合、
f:id:yasuda0404:20130228161940p:plain

1-3) Trilinearの場合
f:id:yasuda0404:20130228161957p:plain

Bilinear/Trilinearはカメラが近づいた際のブラーのかけ方が違う。


2.Max Size
画像の最大サイズのデフォルトは1024px.である。 1.の事例はすべてMax Size1024pxのためカメラが近づくと粗さが目立つ。

2-1) Max Sizeを4096pxにすると、カメラが近づいても精細に表示できる。次は4096px/'point'の場合。1-1)と比べると差は一目瞭然。
f:id:yasuda0404:20130228162145p:plain

2-2) 4096px/'bilnear'だとこんな感じ。画像解像度を上げると、同じカメラ距離でもブラーのかかりは弱くなる。
f:id:yasuda0404:20130228162219p:plain

KINET SDKを使う その2・スクリプト解析編

KINECT SDKを使う その1・環境設定編 - Unityな日々(Unity Geek)の続き

KinectInterop.cs
Kinectとのインターフェースを司どる最下層のスクリプト

public interfrace KinectInterface :Kinectとのインターフェース定義。実装はKinectSensor.csで定義される

KinectSensor.cs
Marshalクラスを用いてデータを取得している。たとえば、

ColorBuffer cb = (ColorBuffer)Marshal.PtrToStructure(buf.m_pBuffer,typeof(ColorBuffer));

は、MarshalクラスのPtrToStructureメソッドを用いて、ポインタ(buf.m_pBuffer)の示すバッファデータを、ColorBufferにマーシャリング(型変換)する。ColorBuffer構造体は、KinectInterop.csに定義されている。

C#はマネージ環境、一方Kinect DLL APIはアンマネージ環境で実装されているため、マーシャリングが必要となるらしい。マーシャリングについてはこのあたりを参考に。

Unity/KINECT SDKスクリプトの中で中心的役割を果たすSkeletonWrapperは、Kinect Prefabにアタッチされている。

Meshの三角ポリゴンを反転する

http://wiki.unity3d.com/index.php?title=ReverseNormals
http://forum.unity3d.com/threads/101018-How-do-I-get-the-normal-of-each-triangle-in-mesh

インポートした3DモデルのMeshを次のスクリプトで調べてみた。

print ("normals="+normals.Length+"  triangles="+triangles.Length+"  vertices="+verts.Length+" vertexCount="+mesh.vertexCount);

次のような出力が得られた

normals=1620 triangles=5691 vertices=1620 vertexCount=1620

これから推察するに、
vertices.Length と、vertexCountは同じようだ。

書式付で数値を文字に変換する

数値を文字に変換するのは、toString()関数だが、同時に書式も指定できる。
たとえば、小数点以下第一位まで表示した文字は、

数値変数.toString("#.0")

とする。

他にも多くの書式設定がある。
カスタム数値書式指定文字列


なお、toString()は、日付時刻の書式付変換でも使う。
bear.mini : [.NET] DateTime.ToString() 書式指定文字列 まとめ - カスタム DateTime 書式指定文字列編

GUIがエラーになる時(クラス名のコンフリクト)

GUIメソッドがない、というエラーで悩まされた。
f:id:yasuda0404:20130112151800p:plain

スクリプトは間違いがないので、システムがおかしくなったかとも疑ったが、理由は簡単だった。
別途、'GUI'という名前のC#クラスを作ったため、コンフリクトを起こしていたのだ。
f:id:yasuda0404:20130112151937p:plain

GUIファイルを削除(リネーム)して、問題解決。

他にもUnityの予約クラス名とのコンフリクトは注意しなければ。

Static Classを使う

Static Classを使う場合は次のように記述

javascript

#pragma strict

static class Ball {
        function getColor():Color{
		return new Color(0.0F,1.0F,1.0F,0.3F);
	}

}

c#

using UnityEngine;
using System.Collections;

public static class Ball  {
        //関数名
	public static Color getColor(){
		return new Color(0.0F,1.0F,1.0F,0.3F);
	}
}

C#の場合、MonoBehaviourを継承しないことに注意。

Static Classは、'Plugins'フォルダか'Standard Asset'フォルダに入れる。これらのフォルダは優先的にコンパイルされるため、通常のユーザークラスから参照が可能になる。

呼び出し側は、Using...は不要。上の例の場合、いきなり、

Color baseColor = Ball.getColor();

というふうに、定義したStatic Classを呼び出せる。



定数を定義する場合は、たとえば、

public static class PhaseConstant
{
    public const int Prep         = 0;
    public const int Approaching  = 2;
    public const int Pause        = 4;
    public const int Departing    = 8;
}

のように書けば、

int phase = PhaseConstant.Prep;

のように使える。

キー入力のメモ 'GetKey'と'GetKeyDown/Up'の違い

キー入力は基本だが、「あるキーが押された」「離された」というイベントと、「押されているか」「押されていないか」という状態は区別されている。

1)イベント(一回限り)

キーが押された

if (Input.GetKeyDown("a")) {
    // aが押された!(押され続けは検知されない)
}

if (Input.GetButtonDown("Fire1")) {
    // Fire1が押された!(押され続けは検知されない)
}

・キーが離された

if (Input.GetKeyUp("a")) {
    // aが離された!
}

if (Input.GetButtonUp("Fire1")) {
    // Fire1が離された!
}


2)状態
・キーが押されている

if (Input.GetKey("a")) {
    // aが押され続けてる!(シューティングのAuto Fireなどに使う)
}

if (Input.GetButton("Fire1")) {
    // Fire1が押され続けてる!
}

C#を使う際の注意事項

Unity Script Reference – Overview: Writing Scripts in C#にまとめられている。

  • すべてのクラス(静的クラスを除く)はMonoBehaviourを継承し、クラス名とファイル名は一致していなければならない。
  • コルーチンはIEnumerator型の戻り値を持たねばならない。
  • C#ではコンストラクタは使えない。代わりにAwake()を使う。

は知っておかねば。

覚えておくと便利なMathf関数

UnityのMathf関数には、通常のMath関数以外に3Dインタラクティブ用に特化された関数がある。次は知っておくと効率化できそう。

関数名(引数) 機能
Clamp(,min,max) 最大・最小値でカット
Pow(x,y) xのy乗
Ceil(x) 小数点切り上げ(floatのまま)
CeilToInt(x) 小数点切り上げ&整数化
Floor(x) 小数点切捨て(floatのまま)
FloorToInt(x) 小数点切捨て&整数化
RoundToInt(x) 小数点四捨五入&整数化
Sign(x) 符号(1または-1(float))
Lerp(from,to,t) 補間
LerpAngle(from,to,t) 角度補間(360度を考慮)
InverseLerp(from,to,value) Lerpの逆関数
MoveTowards(current,target,maxDelta) maxDeltaでターゲット値へ
MoveTowardsAngle(current,target,maxDelta) moveTowardsの角度版
Approximately(a,b) float変数が等しいか?
SmoothDamp(cur,tar,vel,t,s,dt) t秒でcurからtarにスムージング
SmoothDampAngle(cur,tar,vel,t,s,dt) SmoothDampの角度版
Repeat(t,length) length周期で繰り返し
ClosestPowerOfTwo(x) もっとも近い2のn乗の数値を返す
DeltaAngle(a,b) a,bの差(360度を考慮)
PerlinNoise(a,b) 2次元Perlinノイズ
PingPong(float t, float length) 周期lengthで0-tの範囲内の数値を返す

※Mathfの角度はすべてRadian単位


定数名
Deg2Rad degreeからradianへの変換乗数
Epsilon 浮動小数点型の最小値(大小比較で使う)
PI π
Rad2Deg radianからdegreeへの変換乗数

他のGameObject、Componentにアクセスする

現在のScriptがアタッチされているGameObject以外のGameObjectにアクセするには次の方法がある。


1)Inspectorでアサインする
public変数としてGameObjectを定義し、Inspector上で該当GameObjectをアサインする方法。
簡単だが、この方法だとScriptだけで完結しないため、デバッグや再利用がし辛い

他のGameObjectにアタッチされているComponentに直接アクセスする場合は、public変数で該当Componentを定義し、InspectorでそのComponentを持つGameObjectをアサインすることに注意。(これはUnityEditor上でGameObjectを操作することしかできないための便法と思われる。)

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    public OtherScript target; //Inspector上でOtherScriptを持つGameObjectをアサインする
    void Update() {
        target.foo = 2;
        target.DoSomething("Hello");
    }
}


2)GameObjectの階層構造をたどる
現在のGameObjectから親または子のGameObjectをたどる方法。出発点は"transform"であることに注意。下は子GameObjectにアクセスする事例。GetComponentを組み合わている。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Example() {
        transform.Find("Hand").GetComponent<OtherScript>().foo = 2;
        transform.Find("Hand").GetComponent<OtherScript>().DoSomething("Hello");
        transform.Find("Hand").rigidbody.AddForce(0, 10, 0);
    }
}

すべての子GameObjectに対して同じ操作を行う場合、foreach ... in transform ループで子GameObject(のTransformコンポーネント)をひとつずつ見ていく。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Example() {
        foreach (Transform child in transform) {
            child.Translate(0, 10, 0);
        }
    }
}

親GameObjectを取得するには、(childGameObject.)transform.parent とする。


3)名前またはタグを使う

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Start() {
        GameObject go = GameObject.Find("SomeGuy");
        go.transform.Translate(0, 1, 0);
        go.GetComponent<OtherScript>().DoSomething();
        GameObject player = GameObject.FindWithTag("Player");
        player.transform.Translate(0, 1, 0);
        player.GetComponent<OtherScript>().DoSomething();
    }
}


4)メッセージの送受による

イベントハンドラを通じてColliderコンポーネントから、アタッチされているScriptを取得する。
そのScriptが存在するかどうかは、if(該当Script名)とする。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void OnTriggerStay(Collider other) {
         other.rigidbody.AddForce(0, 2, 0);
        if (other.GetComponent<OtherScript>())
            other.GetComponent<OtherScript>().DoSomething();
        }
    }
}


5)同タイプのすべてのオブジェクトを取得する
FindObjectOfType(タイプ名)を使う。クラス(スクリプト)の場合のタイプ名は、typeof(スクリプト名)とする。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Start() {
        OtherScript other = FindObjectOfType(typeof(OtherScript));
        other.DoSomething();
    }
}

複数ある場合は、FindObjectsOfType()を使って配列として取得する。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void OnMouseDown() {
        HingeJoint[] hinges = FindObjectsOfType(typeof(HingeJoint)) as HingeJoint[];
        foreach (HingeJoint hinge in hinges) {
            hinge.useSpring = false;
        }
    }
}

なお、FindObject(s)OfTypeの実行は非常に遅い。可能であれば他の方法を使うのがよい。