Unityな日々(Unity Geek)

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の実行は非常に遅い。可能であれば他の方法を使うのがよい。

同階層のComponentにアクセスする

Componentとは、GameObjectに付加する部品のようなもの。一般的には、GetComponent("Component名") で取得する。

javascript

GetComponent(Transform).Translate(0, 1, 0);

C#

using UnityEngine;
using System.Collections;
public class example : MonoBehaviour {
    void Example() {
        GetComponent<Transform>().Translate(0, 1, 0);
    }
}

ScriptもComponentの一種なので、同様にアクセスできる。

javascript

function Update () {
    var otherScript: OtherScript = GetComponent(OtherScript);
    otherScript.DoSomething();
}

C#

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Update() {
        OtherScript otherScript = GetComponent<OtherScript>();
        otherScript.DoSomething();
    }
}


よく使うComponentは変数が定義されている。

Transform transform
Rigidbody rigidbody
Renderer renderer
Camera camera (only on camera objects)
Light light (only on light objects)
Animation animation
Collider collider

名称は小文字で始まることに注意

transform.Translate(0, 1, 0);

Timeクラスを使う

Timeクラスのプロパティ

プロパティ 内容
time ゲームが始まってからの時間 (Read Only)
timeSinceLevelLoad 現在のレベルがロードされてからの時間 (Read Only)
deltaTime 最新のUpdateのインターバル (Read Only)
fixedTime FixedUpdateが開始されてからの時間 (Read Only)
fixedDeltaTime FixedUpdateのインターバル
maximumDeltaTime Updateインターバルの最大値。推奨値 1/10〜1/3秒
smoothDeltaTime deltaTimeをスムーズ化した値 (Read Only)
timeScale 時間経過のファクタ。1.0でリアルタイム、0.5は2倍のスローモーション
frameCount 経過した総フレーム数 (Read Only)
realtimeSinceStartup ゲーム開始からの実時間 (Read Only)
captureFramerate ゼロ以上に設定すると、実時間に関係なくこのFramerateでTimeの時間が進む。一定フレームレートで画面をキャプチャーする場合に便利

親オブジェクトを取得する

これは基本かもしれないが、親オブジェクトを取得するには、childGameObject.transform.parent とする。つまり、"transform"が必要。

たとえば、衝突してきたオブジェクトのコライダがあるオブジェクトの親オブジェクトにメッセージを渡す場合は次のようになる。

void OnTriggerEnter(Collider car){
	car.gameObject.transform.parent.SendMessage("lightOn");
}

GUISkinを変更する

UnityのデフォルトのGUSkinは、Assetに実体がないため、カスタマイズできない。
"Built-in GUI Skin"をダウンロードし、このGUISkinをデフォルトに設定することで、カスタマイズが可能になる。

なお上のパッケージのSkinにデフォルトで設定されているフォント"Arial Rounded Bold.trf"は、デフォルトのWindows7にはないようだ。適当なフォントをAssetsに読み込んで指定しなおす必要がある。

Button Skinの変更は次の各状態に対して行う

  • Normal 通常状態
  • Hover マウスオーバー時
  • Active マウスクリック時
  • Focus フォーカス移動時
  • On Normal ON状態&通常状態
  • On Hover ON状態&マウスオーバー時
  • On Active ON状態&マウスクリック時
  • On Focus ON状態&フォーカス移動時

後半の"ON..."は、ON状態があるToggleボタンやSelectionGridで有効。

f:id:yasuda0404:20121116101938j:plain

Unityから連番画像を書き出す

画面のスクリーンショットはApplication.CaptureScreenshot(ファイル名)で取得する。
下の例は、スクリーンショットをループ(Update等)に入れて、動画を連番ファイルで書き出すサンプル。(出典:ScreenShotMovie - Unify Community Wiki

using UnityEngine;
 
public class ScreenShot : MonoBehaviour
{
	// The folder we place all screenshots inside.
	// If the folder exists we will append numbers to create an empty folder.
	public string folder = "ScreenshotFolder";
	public int frameRate = 25;
 
	private string realFolder = "";
 
	void Start()
	{
		// Set the playback framerate!
		// (real time doesn't influence time anymore)
		Time.captureFramerate = frameRate;
		// Find a folder that doesn't exist yet by appending numbers!
		realFolder = folder;
		int count = 1;
		while (System.IO.Directory.Exists(realFolder))
		{
			realFolder = folder + count;
			count++;
		}
		// Create the folder
		System.IO.Directory.CreateDirectory(realFolder);
	}
 
	void Update()
	{
		// name is "realFolder/0005 shot.png"
		var name = string.Format("{0}/{1:D04} shot.png", realFolder, Time.frameCount);
		// Capture the screenshot
		Application.CaptureScreenshot(name);
	}
}

画像書き出しの負荷はかなり高く、Updateのインターバルはかなり長くなる。
後、細かいTipsだが、テキストフォーマットで{:D04}とすると、4桁の数字としてString化できる。

C#  値型・参照型、値渡し・参照渡し

次のサブクラスに対し、

using UnityEngine;
using System.Collections;
//Sub Class
public class Sub : MonoBehaviour {
	public int a;	
}

次のメインクラスがあるとする。

using UnityEngine;
using System.Collections;
//Main Class
public class Main : MonoBehaviour {
	// Use this for initialization
	void Start () {
		Sub sub1 = GetComponent<Sub>();
		sub1.a = 1;
		Sub sub2 = sub1;
		sub2.a = 2;
		Debug.Log("sub1.a="+sub1.a);
		Debug.Log("sub2.a="+sub2.a);
	}
}

これを実行すると次のようになる。

sub1.a=2
UnityEngine.Debug:Log(Object)
sub2.a=2
UnityEngine.Debug:Log(Object)

すなわち、クラスは参照型のため、 sub2はsub1と同じ実体を指す。

では次はどうなるだろうか。

using UnityEngine;
using System.Collections;
//Main Class
public class Main : MonoBehaviour {

	// Use this for initialization
	void Start () {
		Sub sub1 = GetComponent<Sub>();
		sub1.a = 3;
		Sub sub2 = Proc (sub1);
		Debug.Log("sub1.a="+sub1.a);
		Debug.Log("sub2.a="+sub2.a);
		sub2.a = 5;
        Debug.Log("sub1.a="+sub1.a);
	}
	
	Sub Proc(Sub sub){
		sub.a = 4;
		return sub;
	}
}

この出力は次の通り。

sub1.a=4
UnityEngine.Debug:Log(Object)
sub2.a=4
UnityEngine.Debug:Log(Object)
sub2.a=5
UnityEngine.Debug:Log(Object)

つまり、関数の引数にクラスを渡す場合は参照渡し。関数の中でクラスに加えた変更は呼び出し側にも反映される。

C#でcoroutineを使う

javascriptでcoroutineを使う際は、

処理1
yield WaitForSeconds(2);
処理2

と、yiledをはさむだけでよい。

C#の場合は、StartCoroutine/IENumearatorの組になるようだ。

public void endRecording(){
  if(isFile){
    titleScreen.endRecording();
    //
    StartCoroutine("resstartGame");
  }
}
	
IEnumerator resstartGame(){
  print ("before waiting");
  yield return new WaitForSeconds(3);	//Wait for 3 seconds
  print ("after waiting");
  Application.LoadLevel(0);	//If not writing, restart scene.
}

次の事例は、呼び出す関数に引数がある場合。

using UnityEngine;
using System.Collections;

public class DoCoroutine : MonoBehaviour {

	// Use this for initialization
	void Start () {
	    print("Starting " + Time.time);
            StartCoroutine("WaitAndPrint", 2.0F);
            print("Before WaitAndPrint Finishes " + Time.time);
	}
	
	// WaitAndPrint
	IEnumerator WaitAndPrint(float waitTime) {
            yield return new WaitForSeconds(waitTime);
            print("WaitAndPrint " + Time.time);
        }
}

この実行結果は次のようになる。

Starting 0
Before WaitAndPrint Finishes 0
WaitAndPrint 2.007803

つまり、呼び出す側ではStartCoroutineに続く行は即実行される。

StartCoroutineの引数は、関数オブジェクトでもよい。上例は次のように書いても良い。

       void Start () {
	    print("Starting " + Time.time);
            StartCoroutine(WaitAndPrint(2.0F));
            print("Before WaitAndPrint Finishes " + Time.time);
	}

※こういう参考事例も ねぎもちもち: Unityで音のフェードアウト

開始したCoroutineを停止する場合は、StopCoroutineを使う。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    IEnumerator Start() {
        StartCoroutine("DoSomething", 2.0F);
        yield return new WaitForSeconds(1);
        StopCoroutine("DoSomething");
    }
    IEnumerator DoSomething(float someParameter) {
        while (true) {
            print("DoSomething Loop");
            yield return null;
        }
    }
}

StopCoroutineは、関数名の文字列でStartCoroutineを呼び出した場合のみ使える。なので、StartCoroutineは関数を文字列で呼び出すのを標準にしたほうが良さそう。

Materialの色を変える

Materialの色は、(renderer.)material.SetColor("_Color", Color)で変更する。

下の例は、materialをPublic変数として受け取り、指定したOn/Off時間で色を変えるもの

using UnityEngine;
using System.Collections;

public class FlashingLight : MonoBehaviour {
	public Material mat;
	public float durationOn = 1.0F; //sec for On
	public float durationOff= 1.0F; //sec for Off
	public bool isOff;	//Start from Off (or from On)
	
	private Color color;
	private Color colorOff;
	private Color colorOn;
	private float currentTime;
	private float nextTime;
		
	// Use this for initialization
	void Start () {
		currentTime = 0.0F;
		colorOff 	= new Color(0.5F,0.5F,0.5F,1.0F);
		colorOn 	= new Color(1.0F,1.0F,0.0F,1.0F);
		if(isOff){
			color = colorOff;
			nextTime = durationOff;
		}else{
			color = colorOn;
			nextTime = durationOn;
		}
		mat.SetColor("_Color", color);
	}
	
	// Update is called once per frame
	void FixedUpdate () {
		if(Time.time > nextTime){
			if(isOff){
				color = colorOn;
				isOff = false;
				nextTime += durationOn;
			}else{
				color = colorOff;
				isOff = true;
				nextTime += durationOff;
			}
			mat.SetColor("_Color", color);
		}
		
	}
}