ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »Unityリファレンス
このページの記事一覧

【Unity】AnimationCurve をコードで初期化する  


 ただのメモ。簡単にイージングなどの効果の得られる「AnimationCurve」をインスペクタで設定できるようにしたとき、あらかじめ初期値を入れておきたいことってあるよね。カーブは一度エディタで登録しておけば使い回しできるけど、デフォルトに無い曲線などのときはコードで設定おくと便利。ググれば複雑なカーブのライブラリもあるけど、手軽に作ってしまいたいときの簡易リファレンス的な。


(※) Unity 2017.2.0b9 / Windows10(x64) で確認



■直線的なカーブ(Static Function)

using UnityEngine;

public class AnimationCurveTest : MonoBehaviour {

public AnimationCurve curve = AnimationCurve.Linear(0, 0, 1, 1); //timeStart, valueStart, timeEnd, valueEnd

}

●カーブエディタでは

●移動に使ってみると

 AnimationCurve.Linear() は static なメソッドである。引数は開始と終了の時間と値のペアを列挙したものになる。



■イージング的なカーブ(ゆっくりと始まり、ゆっくりと終わる)(Static Function)

using UnityEngine;

public class AnimationCurveTest : MonoBehaviour {

public AnimationCurve curve = AnimationCurve.EaseInOut(0, 0, 1, 1); //timeStart, valueStart, timeEnd, valueEnd

}

●カーブエディタでは

●移動に使ってみると

 AnimationCurve.EaseInOut() も static なメソッドである。引数は開始と終了の時間と値のペアを列挙したものになる。ゆっくりと始まり、ゆっくりと終わる移動などが簡単に作れる。



■トップスピード→だんだんゆっくりになっていくイージング[※プリセットと同じ](カスタム:Keyframe を2つ使う)

using UnityEngine;

public class AnimationCurveTest : MonoBehaviour {

public AnimationCurve curve = new AnimationCurve(
new Keyframe(0f, 0f, 0f, 2f), //time, value, inTangent, outTangent
new Keyframe(1f, 1f, 0f, 0f) //time, value, inTangent, outTangent
);
}

●カーブエディタでは

●移動に使ってみると

 このカーブはエディタのプリセットにも入っているが、上記のようにコードで設定することもできる。独自にカーブのポイントを打っていくには「Keyframe」を「AnimationCurve」のコンストラクタに列挙していく。

 「inTangent, outTangent」とはエディタのグラフから見れば、それぞれのポイントの左側・右側の傾きで、タンジェント(Tangent)というのは数学的に y/x で表せる傾きのことだから、0.5f(=1/2) なら「上に1, 右に2」の傾き、2f(=2/1) なら「上に2, 右に1」の傾きと考えれば良いのではないだろうか。時間軸に対する値なので、時間や値の逆数(分子と分母を入れ替える)を設定すれば簡単に反転したカーブも作れる。



■ジャンプのようなイージング(カスタム:Keyframe を3つ使う)

using UnityEngine;

public class AnimationCurveTest : MonoBehaviour {

public AnimationCurve curve = new AnimationCurve(
new Keyframe(0f, 0f, 0f, 4f), //time, value, inTangent, outTangent
new Keyframe(0.5f, 1f, 0f, 0f), //time, value, inTangent, outTangent
new Keyframe(1f, 0f, -4f, 0f) //time, value, inTangent, outTangent
);
}

●カーブエディタでは

●移動に使ってみると(Y軸方向)

 「トップスピード→だんだんゆっくり→頂点まで来たら反転→だんだん速く」といったジャンプのような動きになる。独自にカーブのポイントを打っていくには「Keyframe」を「AnimationCurve」のコンストラクタに列挙していく。

 「inTangent, outTangent」とはエディタのグラフから見れば、それぞれのポイントの左側・右側の傾きで、タンジェント(Tangent)というのは数学的に y/x で表せる傾きのことだから、0.5f(=1/2) なら「上に1, 右に2」の傾き、2f(=2/1) なら「上に2, 右に1」の傾きと考えれば良いのではないだろうか。時間軸に対する値なので、時間や値の逆数(分子と分母を入れ替える)を設定すれば簡単に反転したカーブも作れる。



■線形補間に利用する

 一応使い方を書いておくと、「AnimationCurve.Evaluate()」を使って、時間軸に対する値を取り出せばカーブのような変化になる。特に線形補間系のメソッド「~.Lerp()」と組み合わせると使い勝手が良い。以下のサンプルはオブジェクトの位置をスイっと動かす、かなりの手抜きのコードだが、応用すればアルファでフェードイン・アウトしたり、スケールを変化してアクションを表現したり、スワイプでスイっと動かすなんてこともできる(もちろんエディタ上ではアニメーションクリップを使う手もある)。

using UnityEngine;

public class AnimationCurveTest : MonoBehaviour {

public AnimationCurve curve = new AnimationCurve(
new Keyframe(0f, 0f, 0f, 2f), //time, value, inTangent, outTangent
new Keyframe(1f, 1f, 0f, 0f) //time, value, inTangent, outTangent
);

public Vector3 destination;
public float duration = 1f;

Vector3 origin;
float passed = 0;

// Use this for initialization
void Start () {
origin = transform.position;
}

// Update is called once per frame
void Update () {
passed += Time.deltaTime;
float t = curve.Evaluate(Mathf.Clamp01(passed / duration));
transform.position = Vector3.Lerp(origin, destination, t);
}
}


 もっと複雑なカーブが欲しい・イージングタイプを知りたい場合は、以下の参考URLを使うのも良いだろう。

(参考)
AnimationCurveのプリセットにペナーイージングを加える
AnimationCurveをInspectorで設定し、スクリプトから使う
Easing Function早見表

※トゥイーンとして使うなら DOTween みたいなものでも良いかも。
DOTween (Asset Store)
DOTween (公式:英語)
DOTweenをふわっとまとめてみた
【Unity】Tween アニメーション(DOTween)の話

■DOTween 参考書籍



●床や柱のイルミネーションにイージングを入れて、いい感じにフェードイン・アウトさせている


(関連記事)
【Unity】【C#】スワイプ(フリック)を判定、方向を取得してコールバックする


スポンサーサイト

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityリファレンス  C# 
tb: 0   cm: --

【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録  


 ここ最近、「スワイプ」「長押し」イベントの検出を書いたが、そのコールバックのやり方については投げっぱなしだったので、改めてコードパターンとして簡単にまとめておこう。


(※) Unity 5.6.1p3 / Windows10(x64) で確認



■UnityEvent でコールバックする

 UnityEvent は名前の通り Unity 特有のイベントコールバックの方法だ。その一番大きな特徴はインスペクタでコールバックを受信するメソッドを登録できることで、これを使えば毎回コードを書き換えないで済むというメリットがある。1度作ってしまえば、コンポーネントベースっぽい既存のものを組み合わせるだけでその機能が使えるので、再利用が非常にやりやすい。

 引数をとるか否かで書き方が違うので、その辺りだけ覚えれば利用は非常に簡単だ。なお、UnityEvent の引数は4つまでになってるが、引数が多い場合は専用のクラスや構造体を作って、それを1つの引数にしてしまえば良いだけなので、それほど多く使う機会はないと思う。

●呼び出し側(イベント発生側)
using System;
using UnityEngine;
using UnityEngine.Events;

public class UnityEventCallbackSample : MonoBehaviour {

//引数なしの場合
public UnityEvent OnLongClick; //引数なし

//引数を1つとる場合
[Serializable]
public class SwipeHandler : UnityEvent<Vector2> //引数に Vector2 を1つとる
{
}

public SwipeHandler OnSwipe; //引数に Vector2 を1つとる(インスペクタに表示される)

//引数を2つとる場合
[Serializable]
public class ChangeHandler : UnityEvent<int, int> //引数に int を2つとる
{
}

public ChangeHandler OnChange; //引数に int を2つとる(インスペクタに表示される)


// Update is called once per frame
private void Update () {
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick.Invoke(); //ここでコールバック(引数なし)

if (OnSwipe != null)
OnSwipe.Invoke(Vector2.up); //ここでコールバック(引数を1つとる)

if (OnChange != null)
OnChange.Invoke(1, 2); //ここでコールバック(引数を2つとる)
}
}
}

●コールバック受信側
using UnityEngine;

public class MainSample : MonoBehaviour {

public void OnLongClick() //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}

public void OnSwipe(Vector2 dir) //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}

public void OnChange(int before, int after) //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}

 コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。

 UnityEvent の定義の仕方だが、引数なしのときはそのまま

//定義
public UnityEvent CallbackMethod;

//コールバックを実行するときは
CallbackMethod.Invoke();

のように書くが、引数が1つ以上ある場合、UnityEvent を継承したクラスを作る必要がある。そしてそのジェネリックなパラメータの列挙が引数に対応してる感じになる。

//定義
[Serializable]
public class CallbackHandler : UnityEvent<T1, T2, T3, T4> //引数1~4つまで
{
}
public CallbackHandler CallbackMethod; //インスペクタで表示される

//コールバックを実行するときは
CallbackMethod.Invoke(t1, t2, t3, t4);

 また、コールバックを受信するメソッドを登録するとき(「+」ボタンを押し、コールバック受信するオブジェクトを登録>プルダウンからメソッドを選択)、引数なしの場合はリストに名前の順で出てくるが、引数が1つ以上で、かつ引数の型が合致しているものは、リストの上位の方に「Dynamic (引数の型)」のように出てくる。これを選択すれば、動的な引数の値を受信できる。それとは逆に、引数が1つで int や string などの場合(型による)、リストで下の方にある同じメソッド名を選択すると固定値にできる(値を入力できるようになる)。その他の詳しい内容は以下に参考URLを載せておこう。

(参考)
【Unity】UnityEventの用法と用量


上の方にある Dynamic (型) を選ぶと、
動的に引数の値を変えられる

引数が1つで int や string のときは(型による)、
下の方を選ぶと固定値を設定できる


 なお、コードからコールバックするメソッドを登録したい場合は、「UnityEvent.AddListener」を使う。その方法は以下のURLが参考になるだろう。資料は uGUI での例だが、考え方は同じだ。

(参考)
uGUIのイベントコールバック登録をコードから行う方法とUnityActionデリゲート群




■Action でコールバックする

 次に C# の標準機能(System)にある Action を使ってコールバックをやってみよう。実は Unity にも UnityAction というものが使えるが、基本的には機能は同じものなので、その辺りは自分で入れ替えて考えて欲しい。実際にそのままそっくり Action → UnityAction に書き換えても使える。ただし、現時点では Action は引数16個まで、UnityAction は引数4個までのようだ。しかし実際には上記の UnityEvent の使い方と同じように多くの引数を持ちたい場合、クラスや構造体にまとめて1つの引数にしてしまえば、少なくても問題はない。使い方も引数増えたら、ジェネリックなパラメタを増やす感じになるので、表記上も簡単だ。UnityEvent のときと全く同じ動作をする例を Action で書き直してみよう。

●呼び出し側(イベント発生側)
using System;
using UnityEngine;

public class ActionCallbackSample : MonoBehaviour {

//引数なしの場合
public static Action OnLongClick; //引数なし

//引数を1つとる場合
public static Action<Vector2> OnSwipe; //引数に Vector2 を1つとる

//引数を2つとる場合
public static Action<int, int> OnChange; //引数に int を2つとる

// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick(); //ここでコールバック(引数なし)

if (OnSwipe != null)
OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)

if (OnChange != null)
OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}

●コールバック受信側
using UnityEngine;

public class MainSample : MonoBehaviour {

void OnEnable()
{
//コールバックするメソッドを登録
ActionCallbackSample.OnLongClick += this.OnLongClick;
ActionCallbackSample.OnSwipe += this.OnSwipe;
ActionCallbackSample.OnChange += this.OnChange;
}

void OnDisable()
{
//コールバックするメソッドを解除
ActionCallbackSample.OnLongClick -= this.OnLongClick;
ActionCallbackSample.OnSwipe -= this.OnSwipe;
ActionCallbackSample.OnChange -= this.OnChange;
}

public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}

public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}

public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}

 コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。

 定義の仕方を除いて、一番大きな違いは、コールバック受信側(MainSample)の「OnEnable()」「OnDisable()」でコールバックメソッドの登録と解除を行ってる点だ。これはよく使われる定型パターンというだけで、必ずしもここでやらないといけないというわけではない。ちなみにメソッド登録に「+=」を使っているが、これを複数回やると、コールバックも複数回発生する。誤動作を防ぐために、登録したか否かのフラグで管理した方が安全なこともある。アプリで必ず1回のみなら Awake(), OnDestroy() でも良いと思う。

 あと、定義部分でフィールドを static で宣言しているが、これもコード簡略のためで、インスタンスでも構わない。というより、システムで全体でのイベントとしてユニークで良いのなら static を、オブジェクトごとにイベントの発生とコールバック受信したいのならインスタンスにすることになるだろう。その場合は Awake() でコンポーネント取得、またはインスペクタでインスタンス登録などの方法を用いれば良い。

●コールバックの定義部分をインスタンスにする
//呼び出し側(イベント発生側)
public class ActionCallbackSample : MonoBehaviour {

//引数なしの場合
public Action OnLongClick; //インスタンスを登録する

//(以下略)・・・
}

//コールバック受信側
public class MainSample : MonoBehaviour {

public ActionCallbackSample callback; //インスペクタで登録する

void Awake()
{
if (callback == null) //登録されてない場合はコンポーネント取得を試みる
callback = GetComponent<ActionCallbackSample>();
}

void OnEnable()
{
callback.OnLongClick += this.OnLongClick;
//(以下略)・・・
}

void OnDisable()
{
callback.OnLongClick -= this.OnLongClick;
//(以下略)・・・
}

//(以下略)・・・
}





●delegate でコールバックする

 この方法が本来言語に備わっている機能での書き方とも言える。上記の ActionUnityEventdelegate の機能をラップして簡単に扱う、または短く表記するようにしたものと考えてもいいだろう。そのため、他のプラットフォーム・言語に移植するときなどは互換性が高いかも知れない。ここでもこれまでの2例と全く同じ動作をするコードを書いてみよう。

●呼び出し側(イベント発生側)
using UnityEngine;

public class DelegateCallbackSample : MonoBehaviour {

//引数なしの場合
public delegate void LongClickHandler();
public static event LongClickHandler OnLongClick; //引数なし

//引数を1つとる場合
public delegate void SwipeHandler(Vector2 dir);
public static event SwipeHandler OnSwipe; //引数に Vector2 を1つとる

//引数を2つとる場合
public delegate void ChangeHandler(int befor, int after);
public static event ChangeHandler OnChange; //引数に int を2つとる

// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick(); //ここでコールバック(引数なし)

if (OnSwipe != null)
OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)

if (OnChange != null)
OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}


●コールバック受信側
using UnityEngine;

public class MainSample : MonoBehaviour {

void OnEnable()
{
//コールバックするメソッドを登録
DelegateCallbackSample.OnLongClick += this.OnLongClick;
DelegateCallbackSample.OnSwipe += this.OnSwipe;
DelegateCallbackSample.OnChange += this.OnChange;
}

void OnDisable()
{
//コールバックするメソッドを解除
DelegateCallbackSample.OnLongClick -= this.OnLongClick;
DelegateCallbackSample.OnSwipe -= this.OnSwipe;
DelegateCallbackSample.OnChange -= this.OnChange;
}

public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}

public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}

public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}

 コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。

 上記の Action での書き方とあまり変わらないので、それほど説明はいらないだろう。引数のパラメタがそのまま定義で書かれているだけだ。

 定義部分の static をインスタンスにする方法も Action での書き方を参考にして欲しい。考え方は全く同じだ。用途によって使い分けた方が良いだろう。




●interface でコールバックする

 この方法はオマケなので無視して良い(笑)。C# では delegate を使った方が楽なので、わざわざこういう書き方をする人はいないだろう。どちらかと言うと Java での書き方に近い。まぁ、delegate を持たない他の言語に移植するときとか、こういう書き方もあるという手法を学ぶのには良いだろう。

●呼び出し側(イベント発生側)
using UnityEngine;

public class InterfaceCallbackSample : MonoBehaviour
{
//引数なしの場合
public interface ILongClickHandler {
void OnLongClick();
}

public static ILongClickHandler LongClickHandler;

//引数を1つとる場合
public interface ISwipeHandler {
void OnSwipe(Vector2 dir);
}

public static ISwipeHandler SwipeHandler;

//引数を2つとる場合
public interface IChangeHandler {
void OnChange(int before, int after);
}

public static IChangeHandler ChangeHandler;

// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (LongClickHandler != null)
LongClickHandler.OnLongClick(); //ここでコールバック(引数なし)

if (SwipeHandler != null)
SwipeHandler.OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)

if (ChangeHandler != null)
ChangeHandler.OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}

●コールバック受信側
using UnityEngine;

public class MainSample : MonoBehaviour,
InterfaceCallbackSample.ILongClickHandler,
InterfaceCallbackSample.ISwipeHandler,
InterfaceCallbackSample.IChangeHandler
{

void OnEnable()
{
//コールバックするメソッドを登録
InterfaceCallbackSample.LongClickHandler = this;
InterfaceCallbackSample.SwipeHandler = this;
InterfaceCallbackSample.ChangeHandler = this;
}

void OnDisable()
{
//コールバックするメソッドを解除
InterfaceCallbackSample.LongClickHandler = null;
InterfaceCallbackSample.SwipeHandler = null;
InterfaceCallbackSample.ChangeHandler = null;
}

public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}

public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}

public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}

 コードはかなり簡略されたもので、あくまでも前述の3例に似せた書き方にしているので、実際に使うにはもう少し手を加えた方が良いだろう。内容的には前述の3例と違い、コールバックメソッドの登録が単ーとなる

 実際の所、interface にはメソッド名の縛りがあるので(delegate はメソッド名が異なっていてもシグネチャが合ってれば使える)、コールバックとしては自由度が低い。とは言え、例えば複数のシステム間で必ず決まったメソッド名でコールバックしたい場合などはこちらでも良い。ただし、delegate の例のように複数のメソッドを登録することはできないので(※上記の例の場合)注意。複数のメソッドを登録できるようにするなら、以下のようにそれぞれのコールバックの参照(LongClickHandler, SwipeHandler, ChangeHandler)を List などにして、AddListener(登録するメソッド)、RemoveListener(解除するメソッド) のような仕組みを作った方が良いだろう。もちろんイベント発生時にはそれら List を回して、登録してあるすべてのコールバックメソッドを実行する必要がある(null がないように注意)。

●interface でのコールバック登録を複数対応にする
using System.Collections.Generic;
using UnityEngine;

//呼び出し側(イベント発生側)
public class InterfaceCallbackSample : MonoBehaviour {

//(以上略)・・・

//コールバックの参照リスト
static List<ILongClickHandler> longClickListener = new List<ILongClickHandler>();

//コールバックを登録
public static void AddLongClickListener(ILongClickHandler callback)
{
longClickListener.Add(callback);
}

//コールバックを解除
public static void RemoveLongClickListener(ILongClickHandler callback)
{
longClickListener.Remove(callback);
}

//(以下略)・・・

// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
foreach (var callback in longClickListener) //※↓本来は null チェックした方が良い
callback.OnLongClick(); //ここでコールバック(引数なし)

//(以下略)・・・
}
}

//(以下略)・・・
}

//コールバック受信側
public class MainSample : MonoBehaviour,
InterfaceCallbackSample.ILongClickHandler,
(以下略)・・・
{

void OnEnable()
{
InterfaceCallbackSample.AddLongClickListener(this);
//(以下略)・・・
}

void OnDisable()
{
InterfaceCallbackSample.RemoveLongClickListener(this);
//(以下略)・・・
}

//(以下略)・・・
}

 定義部分の static をインスタンスにする方法は Action での書き方を参考にして欲しい。考え方は全く同じだ。用途によって使い分けた方が良いだろう。


(関連記事)
【Unity】【C#】長押し(ロングタップ)を取得してコールバックする
【Unity】【C#】スワイプ(フリック)を判定、方向を取得してコールバックする
【Unity】【C#】VideoPlayer で動画の終了判定をする
【Java】interface を使って簡単なコールバック機能を作る


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityリファレンス  C# 
tb: 0   cm: --

【Unity】【C#】uGUI ドロップダウンの要素をコードで設定と取得、外観のカスタマイズなど  


 どちらかというと備忘録。調べているとき色々な方法があったので、少しまとめておいた感じ。どのやり方でも違いはないので、用途によって使いやすい方を選べば良いと思う。

※ Unity 5.5.0f3 / Windows10(x64) で確認




■ドロップダウンの要素をコードで設定する

 まずはドロップダウンを画面に置いてしまおう。「GameObject」メニューから「UI>Dropdown」で「Canvas」以下に生成される。インスペクタで下の方へスクロールして見てみると、予め3つの要素が設定されている。これをコードで変更することになる。なお、GUI 関連をシーンビューで見るときは、「2D」のボタンをオンした方が見やすいかも知れない。







 要素を設定するコードは以下のようになる。インスペクタで操作するドロップダウンオブジェクトを設定して、スクリプトで変更する感じだ。「using UnityEngine.UI」を入れて置いた方が簡潔だろう。なお、クラス名は任意で良い。

●要素を文字列のリストで設定する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DropdownTest : MonoBehaviour {

public Dropdown dropdown; //操作するオブジェクトを設定する

// Use this for initialization
void Start () {
if (dropdown) {
dropdown.ClearOptions(); //現在の要素をクリアする
List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("5");
dropdown.AddOptions(list); //新しく要素のリストを設定する
dropdown.value = 1; //デフォルトを設定(0~n-1)
}
}
}

 このスクリプトを適当なオブジェクトにアタッチして、操作するオブジェクトをドラッグ&ドロップで設定しておく。今回は「Canvas」にアタッチしてあるが、どこでも良い。プレイして「Dropdown」をインスペクタで見てみると要素が変わっていることがわかる。なお、デフォルトの値(value)は要素の先頭を0とした連番となっている。ここでは2番目([1])の要素を設定している。



●プレイ中




 もう1つ、直接プロパティに設定する方法もある。内容的には上記の例と同じものだが、追加するリストには「Dropdown.OptionData」クラスの要素を使う。リストの内容はこの形式で保持されていると考えれば良いだろう。

●要素を「Dropdown.OptionData」のリストに直接設定する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DropdownTest : MonoBehaviour {

public Dropdown dropdown; //操作するオブジェクトを設定する

// Use this for initialization
void Start () {
if (dropdown) {
dropdown.options.Clear(); //現在の要素をクリアする
dropdown.options.Add(new Dropdown.OptionData("1"));//新しく要素を追加する
dropdown.options.Add(new Dropdown.OptionData("2"));
dropdown.options.Add(new Dropdown.OptionData("5"));
dropdown.value = 1; //デフォルトを設定(0~n-1)
//dropdown.RefreshShownValue();//更新を確認(画像を設定したとき)
}
}
}

 こちらを使った場合はアイコン画像(Sprite)なども一緒に設定できる。以下に資料を載せておくので必要があれば参照して欲しい。「Dropdown.RefreshShownValue()」は画像も更新したときは必要になるようだ。文字列だけなら無くても構わない。

(参考)
Unity5のドロップダウンのオプション値をスクリプトで設定する




■ドロップダウンの要素を取得する

 次に設定した要素を取得してみよう。今回は値(value)とテキストを取得することにする。ちなみに値の方は先頭からの連番(0~n-1)になる。

 この場合はコールバックイベントの「Dropdown.onValueChanged(int)」が使える。まずはスクリプトを用意して、「+」ボタンを押してインスペクタに設定しよう。なおここでは「DropdownTest」スクリプトは「Canvas」オブジェクトにアタッチしてあるものとする。

●「OnValueChanged(int)」コールバックを使って要素を取得する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DropdownTest : MonoBehaviour {

public Dropdown dropdown; //操作するオブジェクトを設定する

// Use this for initialization
void Start () {
if (dropdown) {
dropdown.ClearOptions(); //現在の要素をクリアする
List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("5");
dropdown.AddOptions(list); //新しく要素のリストを設定する
dropdown.value = 1; //デフォルトを設定(0~n-1)
}
}

public void OnValueChanged(int value) {
Debug.Log("value = " + value); //値を取得(先頭から連番(0~n-1))
Debug.Log("text(options) = " + dropdown.options[value].text); //リストからテキストを取得
Debug.Log("text(captionText) = " + dropdown.captionText.text); //Labelからテキストを取得
}
}


 プレイしてドロップダウンを変更してみると値がコンソールにデバッグ表示される(はじめの1回はデフォルト設定の変更のため。0からならデフォルト設定は必要ない)。


 値(連番)ではなくて、直接選択されたテキストを使いたいときもあるだろう。この場合は大まかに2つの方法がある。1つは上記のドロップダウンに設定したリストを取得する方法と、選択したときに表示されるラベルを取得する方法だ。


 内容的にはリストの方は設定した値を取得しているだけなので説明はいらないと思うが、「Label」からというのはヒエラルキーで「Dropdown>Label」と開くと、このオブジェクトに選択したテキストが設定されているのがわかるからだ。「Dropdown.captionText」はそのテキストを保持しているオブジェクトと考えれば良いだろう。




■ボタンを押したとき、ドロップダウンの要素を取得する

 まずはドロップダウンがある状態から、「GameObject」メニューの「UI>Button」でボタンを追加しよう。このボタンを押したとき、選択されているドロップダウンの要素を取得するようにする。基本的には今までの方法と変わらない。取得タイミングをボタン押下にしただけだ。

 ボタンには「Button.onClick()」というコールバックが標準で備わっている。ドロップダウンのときと同じようにスクリプトを用意して、「+」ボタンを押してインスペクタで設定しよう。先のドロップダウンの例をそのまま使うなら、以下のようになる。


●「OnClick()」コールバックを使って要素を取得する
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DropdownTest : MonoBehaviour {

public Dropdown dropdown; //操作するオブジェクトを設定する

// Use this for initialization
void Start () {
if (dropdown) {
dropdown.ClearOptions(); //現在の要素をクリアする
List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("5");
dropdown.AddOptions(list); //新しく要素のリストを設定する
dropdown.value = 1; //デフォルトを設定(0~n-1)
}
}

public void OnClick() {
Debug.Log("dropdown.value = " + dropdown.value); //値を取得(先頭から連番(0~n-1))
Debug.Log("text(options) = " + dropdown.options[dropdown.value].text); //リストからテキストを取得
Debug.Log("text(captionText) = " + dropdown.captionText.text); //Labelからテキストを取得
}
}

 この例のようにドロップダウンの初期化とボタンの操作を1つのクラスで管理する場合は上記のやり方で十分だが、別々のクラスで管理したいときはどうすれば良いのだろうか。もちろん同じように操作するオブジェクトを設定させても良いが、コールバックのオーバーロードをすればもう少し簡潔に書くこともできる。

●コールバックのオーバーロードを使って要素を取得する
(初期化のみのクラス「DropdownTest」)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DropdownTest : MonoBehaviour {

public Dropdown dropdown; //操作するオブジェクトを設定する

// Use this for initialization
void Start () {
if (dropdown) {
dropdown.ClearOptions(); //現在の要素をクリアする
List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("5");
dropdown.AddOptions(list); //新しく要素のリストを設定する
dropdown.value = 1; //デフォルトを設定(0~n-1)
}
}
}

(要素の取得のみのクラス「ButtonTest」)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ButtonTest : MonoBehaviour {
//OnClick のオーバーロード
public void OnClick(Dropdown dropdown) {
Debug.Log("dropdown.value = " + dropdown.value); //値を取得(先頭から連番(0~n-1))
Debug.Log("text(options) = " + dropdown.options[dropdown.value].text); //リストからテキストを取得
Debug.Log("text(captionText) = " + dropdown.captionText.text); //Labelからテキストを取得
}
}

 この「ButtonTest」を例えばボタンにアタッチし、要素を取得するドロップダウンをインスペクタで設定すれば、スクリプトはかなり簡潔になる。ドロップダウンの状態は「Dropdown」オブジェクトが保持しているわけだから、これを引数にして直接渡してしまえば良いわけだ。もちろん「Label」オブジェクトを引数にするのもアリだろう。





●プレイ中にボタンを押すと…




■uGUI の見た目を変更する(簡易的な外観のカスタマイズ)

 一番簡単な方法として、各オブジェクトの「Image>Source Image」を入れ替えれば良い。試しに Asset Store (Ctrl-9) から「Cartoon UI Pack」をインポートして使ってみよう。キーワードに「Cartoon UI」を入れて「無料のみ」のボタンを押せば簡単に見つかる。


 インポートし終わったら、あとは適当なものを選ぶだけだ。好きなものを設定してみよう。見た目が変わったハズだ。




 また、ボタンをハイライトしたときの色などの設定は「Highlighted Color(ハイライト)」「Pressed Color(押したとき)」「Disabled Color(無効化のとき)」を変更すれば良い。好きな色を設定してみよう。



 他にも細かい設定ができるようなので参考URLを載せておこう。色々試してみると良いだろう。

(参考)
【Unity】uGUIドロップダウンメニュー(コンボボックス)の使い方
【Unity開発】uGUIのDropDownの使い方【ひよこエッセンス】


■uGUI 関連参考書籍






(関連記事)
【Unity】【C#】【JavaScript】4.6.x の Canvas~UI Text のテキストをコードで変更する
【Unity】【C#】【JavaScript】GUIText をコードで生成する


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: C#  サンプル  Unityリファレンス 
tb: 0   cm: --

【Unity】【C#】インスペクタの表示項目を動的に変更する  


 インスペクタである値を設定すると、それに対応したパラメタ設定だけを表示したいときってあるよね。幸いにも「Editor」クラスを継承して「OnInspectorGUI()」 を override すれば、元のスクリプトを変更せずに表示を切り替えられるので、試しに設定項目の表示切り替えをやってみよう。

(※) Unity 5.4.3f1 / Windows10 で確認



 せっかくなので、用例は前回作った「独自のギズモを表示する」のスクリプトをそのまま使って、形状(Shape)を切り替えたときに「球」「箱」「線」に対応した設定パラメタだけを表示するようにしてみよう。各形状パラメタは「MySphere」「MyCube」「MyLine」クラスで保持されているが、実際に必要なのは「Shape」(形状)に対応するパラメタだけなので、1つだけ表示した方がインスペクタがスッキリする。以下は元の「独自ギズモを表示する」スクリプトとなる。

●球/箱/線状を切り替えられる独自ギズモを表示するスクリプト(→前回のと同じもの
using UnityEngine;
using System.Collections;

/** 独自 Gizmo (球,箱,線 状) */
public class MyGizmo : MonoBehaviour {

/** 形状を表す定数 */
[System.Serializable]
public enum Shape {
Sphere = 0,
Cube,
Line,
}

public bool visible = true; //可視状態
public Color color = Color.yellow; //色
public Shape shape = Shape.Sphere; //形状

public MySphere sphereParam = new MySphere(0, 0, 0, 1); //球状のパラメタ
public MyCube cubeParam = new MyCube(0, 0, 0, 1, 1, 1); //箱状のパラメタ
public MyLine lineParam = new MyLine(0, 0, 0, 1, 1, 1); //線状のパラメタ

void OnDrawGizmos() {
if (!visible) {
return;
}

Gizmos.color = color;

//Gizmo はワールド座標指定なので、相対座標指定の場合はマトリクス変換で移動する
Gizmos.matrix = Matrix4x4.TRS(this.transform.position, this.transform.rotation,
this.transform.localScale);
switch (shape) {
case Shape.Sphere:
Gizmos.DrawSphere(sphereParam.center, sphereParam.radius);
break;

case Shape.Cube:
Gizmos.DrawCube(cubeParam.center, cubeParam.size);
break;

case Shape.Line:
Gizmos.DrawLine(lineParam.from, lineParam.to);
break;
}
}
}

/** 球状パラメタ */
[System.Serializable]
public class MySphere
{
public Vector3 center = Vector3.zero;
public float radius = 1f;

public MySphere(float x, float y, float z, float radius) {
this.center = new Vector3(x, y, z);
this.radius = radius;
}
}

/** 箱状パラメタ */
[System.Serializable]
public class MyCube
{
public Vector3 center = Vector3.zero;
public Vector3 size = Vector3.one;

public MyCube(float x, float y, float z, float scaleX, float scaleY, float scaleZ) {
this.center = new Vector3(x, y, z);
this.size = new Vector3(scaleX, scaleY, scaleZ);
}
}

/** 線状パラメタ */
[System.Serializable]
public class MyLine
{
public Vector3 from = Vector3.zero;
public Vector3 to = Vector3.one;

public MyLine(float x1, float y1, float z1, float x2, float y2, float z2) {
this.from = new Vector3(x1, y1, z1);
this.to = new Vector3(x2, y2, z2);
}
}

 このスクリプトでは画像の一番上のように各形状のパラメタがインスペクタに全て表示される。このパラメタ部分を「Shape」(形状定数)を変更したら、それに対応するパラメタだけを表示するように、もう1つ「MyGizomoEditor」(名前は任意で良い)を作成しよう。完成したスクリプトは以下のようになる。なお、カスタムエディタ(インスペクタ)機能のスクリプトは「Editor」というフォルダを作って、そこに置く必要がある(※ルートでなくても良い)。


●MyGizmo の Shape によってインスペクタを動的に変更するスクリプト
using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor (typeof(MyGizmo))]
public class MyGizmoEditor : Editor {

public override void OnInspectorGUI() {
MyGizmo obj = target as MyGizmo;

obj.visible = EditorGUILayout.Toggle("Visible", obj.visible);
obj.color = EditorGUILayout.ColorField("Color", obj.color);

obj.shape = (MyGizmo.Shape)EditorGUILayout.EnumPopup("Shape", obj.shape);

EditorGUI.indentLevel++; //インデントを入れる

switch (obj.shape) {
case MyGizmo.Shape.Sphere:
obj.sphereParam.center = EditorGUILayout.Vector3Field("Center", obj.sphereParam.center);
obj.sphereParam.radius = EditorGUILayout.FloatField("Radius", obj.sphereParam.radius);
break;

case MyGizmo.Shape.Cube:
obj.cubeParam.center = EditorGUILayout.Vector3Field("Center", obj.cubeParam.center);
obj.cubeParam.size = EditorGUILayout.Vector3Field("Size", obj.cubeParam.size);
break;

case MyGizmo.Shape.Line:
obj.lineParam.from = EditorGUILayout.Vector3Field("From", obj.lineParam.from);
obj.lineParam.to = EditorGUILayout.Vector3Field("To", obj.lineParam.to);
break;
}

EditorGUI.indentLevel--; //インデントを戻す

//obj.iconImage = EditorGUILayout.TextField("Icon Image", obj.iconImage); //アイコン画像

EditorUtility.SetDirty(target);
}
}

 注意点としては、継承するのはいつもの「MonoBehaviour」ではなく「Editor」(要:using UnityEditor;)であることと、「[CustomEditor (typeof(MyGizmo))]」「MyGizmo obj = target as MyGizmo;」のようにカスタム対象となるスクリプト(ここでは「MyGizmo」)を指定することだ。

 あとは「OnInspectorGUI()」内で「EditorGUILayout」(または「EditorGUI」)を使って、すべての表示項目の入力フィールド(チェックボックスやスライダーなども同様)を表示し、戻値をセットしていくだけだ。クラスや構造体のメンバ(プロパティ)なども同様にすれば良い。アイコン画像についてはコメントアウトしてあるが、これは前回の「ギズモのアイコンを表示する」に対応する場合の例なので記事を参照して欲しい。なお、ここで表示しなかったものは全て非表示となる。つまりある値によって表示する項目を分岐すれば、表示切り替えが可能となるわけだ。

 他にも入力値制限や細かいカスタムインスペクタの使い方などは以下に資料を載せておこう。

(参考資料)
カスタムエディタ編 Inspectorが変わる!
インスペクタで設定できる値を動的に変更する【Unity】【エディタ拡張】


(関連記事)
【Unity】【C#】独自のギズモを表示する
【Unity】【C#】FPS(フレームレート)をリアルタイムに測定して表示するv2(4隅選択可能で、画面サイズの変更にも対応版)


■参考になる書籍


category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityリファレンス  C# 
tb: 0   cm: --

【Unity】【C#】【JavaScript】GUIText をコードで生成する  


 GUIText って、Ver 4.6.x より前は「GameObject」のメニューから直接「GUIText」が作れたのだが、4.6.x 以降は「空の GameObject にGUIText のコンポーネントを追加」して、自分で作るしかないのね。とりあえず以下の参考資料の方法で作ってプレハブ化しておけば良いのだが、色々な資料をみると意外と GUIText を使ったサンプルが多いので、スクリプトで動的に生成するのもアリと思った。というわけで、またしても適当なコード。

↓メニューから作るには、
(参考) Unity4.6以降のGUITEXTとGUITEXUREについて

↓スクリプトで作るには、
●C#
GameObject guiObj = new GameObject("GUI Text");  //Hierarchy に表示される名前
guiObj.AddComponent("GUIText");
guiObj.guiText.transform.Translate(0.5f, 0.5f, 0);
guiObj.guiText.anchor = TextAnchor.MiddleCenter;
guiObj.guiText.alignment = TextAlignment.Center;
guiObj.guiText.fontSize = 24;
guiObj.guiText.fontStyle = FontStyle.Normal;
guiObj.guiText.text = "Text C#";

●JavaScript
var guiObj : GameObject = new GameObject("GUI Text");  //Hierarchy に表示される名前
guiObj.AddComponent("GUIText");
guiObj.guiText.transform.Translate(0.5f, 0.5f, 0);
guiObj.guiText.anchor = TextAnchor.MiddleCenter;
guiObj.guiText.alignment = TextAlignment.Center;
guiObj.guiText.fontSize = 24;
guiObj.guiText.fontStyle = FontStyle.Normal;
guiObj.guiText.text = "Text J";

 書く場所はテキストが固定なら Start() だけで良いが、Update() で内容を変更したいなら、guiObj をグローバルスコープに置けば良い。new GameObject(name) で付ける名前はユニークな方が良いね。

 Canvas の UI Text とは座標系が少し違うから、サンプルごとに調整するのは面倒だしね。スクリプトをコードテンプレート(スニペット)に登録しておくと、いつでも呼び出せる利点もある。

(参考) [MonoDevelop]Snippetsを使って記述を楽にする方法


(関連記事)
【Unity】【C#】【JavaScript】4.6.x の Canvas~UI Text のテキストをコードで変更する
【Unity】【C#】uGUI ドロップダウンの要素をコードで設定と取得、外観のカスタマイズなど

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: C#  JavaScript  Unityリファレンス 
tb: 0   cm: --


プロフィール

Twitter

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop