[unity] C#:ライトの明滅

unityでエフェクト作成中に必要になったのがライトの明滅。
エフェクトの影響で周りの環境にライトを反映させたかった訳です。
ライトのintensityにアニメーションを手でつけるのはスマートな感じがしないのでスクリプトの勉強がてら作成しました。

using UnityEngine;
using System.Collections;

public class FlickerLight : MonoBehaviour {

	public Light Light;                  //ライトオブジェトを指定
	public float MinIntensity = 0.5f;    //最小のIntensityの値を設定
	public float MaxIntensity = 1.0f;    //最大のIntensityの値を設定
	public int Smooth = 3;               //ライトの明滅をスムースにする設定
	public float StartTime = 0.1f;       //ライトが点く時間
	public float DeadTime = 1.0f;        //ライトが点き始めてから消えるまでの時間

	private float time = 0.0f;
	private float[] data = null;
		
	void Start(){

	}
	
	void Update () {
		float total = 0.0f;
		float ave = 0.0f;

		time += Time.deltaTime;
		data = new float[Smooth];

		for(int i = 0; i < Smooth; i++){
			data&#91;i&#93; =  Random.Range(MinIntensity, MaxIntensity);
		}

		foreach(float n in data){
			total += n;
		}

		ave = total / Smooth;
		light.intensity = (time < StartTime) ? 0.0f : Mathf.Lerp(ave, 0.0f, time / (StartTime + DeadTime));
	}
}
&#91;/csharp&#93;

参考にさせていただいたのが↓です。

<a href="http://answers.unity3d.com/questions/34739/how-to-make-a-light-flicker.html">UnityAnswers:How to make a light flicker</a>

自分の復習がてらに理解不足ですが書き留めておきます。
[csharp]
using UnityEngine;
using System.Collections;

public class FlickerLight : MonoBehaviour {

	public Light Light;
	public float MinIntensity = 0.5f;
	public float MaxIntensity = 1.0f;
	public int Smooth = 3;
	public float StartTime = 0.1f;
	public float DeadTime = 1.0f;

	private float time = 0.0f;
	private float[] data = null;

まずは必要な変数を用意してやる訳ですが、「public」って変数の宣言前に付けると下記の画像のようにインスペクター(mayaでいうアトリビュートエディター)に追加して変更できるようになるみたいです。
flicker_light_1
逆に「private」と付けるか、何も付けてない場合はインスペクターに追加されないみたい。
最初の行の「Light」っていうのはGameObjectのlightを選択することができるようになる変数?みたいなもののようだが、選択してもしなくてもライトのintensityにこのスクリプトが作用して動くので、これの使用方法がよくわかりません。
参考のサイトのまま残して使用しております(笑)
あとは自分なり欲しいものを追加してます。
intensityの幅を指定したかったのでmin、maxを用意。ただunity内でintensityはデフォルトでは8が最大みたいなのでそれ以上無理っぽいです。

んで次に

void Start(){

}

C#では「void」っていうのが関数の宣言みたいで、melでいう「proc」pythonでいう「def」にあたるようです。
「void Start」はmelのエクスプレッションでいう「Creation」にあたります。
なので最初に行う処理や設定みたいな感じでしょうか、説明難しいですが。
今回は特に必要ないので空欄です。

次は結構悩んで時間掛かってしまったのですが

	void Update () {
		float total = 0.0f;
		float ave = 0.0f;

		time += Time.deltaTime;
		data = new float[Smooth];

		for(int i = 0; i < Smooth; i++){
			data&#91;i&#93; =  Random.Range(MinIntensity, MaxIntensity);
		}

		foreach(float n in data){
			total += n;
		}

		ave = total / Smooth;
		light.intensity = (time < StartTime) ? 0.0f : Mathf.Lerp(ave, 0.0f, time / (StartTime + DeadTime));
	}
&#91;/csharp&#93;
「void Update」はmelのエクスプレッションでいう「Runtime」にあたります。
毎フレームこのスクリプトを実行してくれるわけです。(フレームって概念なのかよくわかってませんが…)
まず一番考えたのが<span style="color: #ff0000;"><strong>ランダムな値をどうやって作るのか</strong></span>です。
単純にランダムにしても良かったのですが、それではものすごく明滅が激しく目に悪い気がしました。
あとは勉強にもなりませんし。
参考のサイト様のやり方を真似ることにしましたが、正直パッとみたときになにをしようとしているのかわかり辛く見た目もよくなかったので手法は真似ましたが記述は変更しました。
手法はこうです。
リスト(配列)をいくつか用意してその中にランダムな値を格納し、それらの平均値を取ることでランダムな数値をある程度均一化してやる。ってな訳です。
本当はmelのnoise関数みたいなものがあればよかったのですが、unityではMathf関数の「PerlinNoise」では2次元分布の戻り値になるようでしたのでやめておきました。

次に僕が頭を悩ませたのがC#のリストの扱い方というか宣言の方法についてです。
最初の変数の宣言ではさらっと流してましたがC#では下記のようなリストの宣言が出来ませんでした。
[csharp]
public int Smooth = 3;
private float[] data = new float[Smooth];

これはエラーをはいてどうしても通ってくれませんでした。
配列の数でスムースにする度合いに変化を付けたかったのでどうすればいいのか困りましたが、OKWave:C# 配列の変数宣言について。に解決策が。
はじめは実体化せずにnullをいれて、そのあとで使う段階で実体化するようにします。そのときに引数は変数でも問題ないわけだそうです。

public int Smooth = 3;
private float[] data = null;
data = new float[Smooth];

これで正常に処理することが出来ました。

あとは配列に「for」でランダムな値を代入します。
「Random.Range」をしようすることでランダムな値の範囲を指定しています。
次に「foreach」を使用することで配列内の全ての合計値を計算しています。
「foreach」はmelでいう「for-in」で配列内の数の分だけ繰り返し処理をします。
これらを使用し平均値をとるところまではできました。

しかしずっとランダムに明滅していたい訳ではないので指定の時間内だけ明滅して欲しいわけです。
そこで最後の一行なのです。

light.intensity = (time < StartTime) ? 0.0f : Mathf.Lerp(ave, 0.0f, time / (StartTime + DeadTime));
&#91;/csharp&#93;
melとかプログラミングをやってる人はみたことある手法で簡単ですが、知らない人には何をやっているのかわからないかと思います。
この一行で実は「if」と同じことができるわけです。melでは「?:演算子」とヘルプにのっています。
<span style="color: #ff0000;"><strong>(条件式) ? (条件式がtrueの場合に代入する値) : (falseの場合に代入する値)</strong></span>
↑のように記述することで条件をつけて、その結果を代入している訳です。

僕がこの手法に最初に出会ったのはmayaのmelではなく、今は亡きNaiadのNelでした。
NaiadはNelが出来ないと何も出来ませんってくらい必須でキーフレームもエクスプレッションで制御するくらいだったので、英語でしかリファレンスやらコミュニティもなっかたので悪戦苦闘して勉強しました。それが今じゃAutodesukの野郎にbifrostだかなんだか…
話はそれました!(笑)
僕はわりとこの書き方好きでmelのエクスプレッションなどではたくさん使ってます。
ぱっと見わかり辛いので敬遠する人もいるみたいです!

次に前回のエントリーでも紹介しました、「Mathf.Lerp」です。mayaのlinstepみたいなやつです、説明は割愛します!
これでintensityの強さをランダムな値から0にリニアに減衰するようになるわけです。
減衰は経過する時間で変化するように経過時間を取得する必要があるため
[csharp]
time += Time.deltaTime;

とあらかじめ変数に経過時間を取得しておきます。

以上です。長くなりました!
色々オプションを加えてリニアに減衰じゃなく任意のグラフで減衰など色々やってみたかったですが、今回についてはこれで良しとします!

  1. コメントはまだありません。

  1. トラックバックはまだありません。