ゾンビとUnity

ゾンビネタとUnityでのゲーム制作について綴るブログです。

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Unity スクリプトにアクセスする方法を詳しく

私は C# しか使わないので、C# でのサンプルです。
C# だと .Net Framework を使えるので、いろいろ捗ります。

TestScript の中身
using UnityEngine;
public class TestScript : MonoBehaviour {
  private int moge = 0;
  public int Moge { get { return this.moge; } set { this.moge = value; } }
  public void Hoge() { Debug.Log("Hoge!!"); }
}
このスクリプトは Test という名前の GameObject のコンポーネントとして登録されていると想定します。
このスクリプトは以下のサンプルで使います。


Test という名前の GameObject の TestScript という名前のスクリプトにアクセスする

ダイレクトにアクセス
GameObject.Find("Test").GetComponent<TestScript>()

GameObjectをローカル変数にキャッシュして TestScript にアクセス
GameObject g = GameObject.Find("Test");
g.GetComponent<TestScript>()

TestScript をローカル変数にキャッシュする
TestScript t = GameObject.Find("Test").GetComponent<TestScript>();

両方キャッシュ
GameObject g = GameObject.Find("Test");
TestScript t = g.GetComponent<TestScript>();

キャッシュする方法は、取得できたか?チェックが必要な場合に便利
GameObject g = GameObject.Find("Test");
if (g == null) //Testという名前のGameObjectが見つからなかった

TestScript t = g.GetComponent<TestScript>();
if (t == null) //TestはTestScriptを持ってない


Test という名前の GameObject の TestScript という名前のスクリプトの Hoge() メソッドを実行する

GameObject.Find("Test").GetComponent<TestScript>().Hoge();

いちいちチェックが必要なら
GameObject g = GameObject.Find("Test");
if (g == null) //Testという名前のGameObjectが見つからなかった

TestScript t = g.GetComponent<TestScript>();
if (t == null) //TestはTestScriptを持ってない

t.Hoge();



Test という名前の GameObject の TestScript という名前のスクリプトの Moge プロパティに値をセットする

GameObject.Find("Test").GetComponent<TestScript>().Moge = 値;

いちいちチェックが必要なら
GameObject g = GameObject.Find("Test");
if (g == null) //Testという名前のGameObjectが見つからなかった

TestScript t = g.GetComponent<TestScript>();
if (t == null) //TestはTestScriptを持ってない

t.Moge = 値;


Start() や Awake() でキャッシュしておく

毎回 GetComponent するのは効率が悪いので、プライベートなフィールドにキャッシュしておく
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  private TestScript test;
  void Awake() {
    this.test = GameObject.Find("Test").GetComponent<TestScript>();
  }
  void Update() {
    Debug.Log(this.test.Moge); //必要になったらキャッシュしておいたフィールドを使ってTestScriptにアクセス
  }
}

この効率の違いは簡単に検証できる
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  TestScript test_script;
  void Awake() {
    this.test_script = GameObject.Find("Test").GetComponent<TestScript>();
  }
  void Update() {
    float s = Time.realtimeSinceStartup;
    //100万回繰り返して、それにかかった時間を計測する
    for (int i = 0; i < 1000000; i++) {
      //キャッシュしない場合の時間を計測するなら、以下の1行の//を削除
      //GameObject.Find("Test").GetComponent().Moge = 1;

      //キャッシュする場合の時間を計測するなら、以下の1行の//を削除
      //this.test_script.Moge = 1;
    }
    float e = Time.realtimeSinceStartup - s;
    Debug.Log("elapsed = " + (float)e);
  }
}
私の環境では、キャッシュした場合は 0.005秒くらいで、キャッシュしない場合は 0.36秒くらい。
数回程度では差は分からないが、さすがに100万回となると明確な差が出る。
これは、ある程度の高い?性能を持っているPCだから、この結果になっただけかも知れないので、他の環境で同じ結果になるかは分からない。


GameObject.Find() を使わない方法

Test Test という名前の GameObject の NewBehaviourScript で GameObject.Find() を使わずに Test という名前の GameObject の TestScript にアクセスする

Test Test の NewBehaviourScript に public フィールドを追加
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  public GameObject test;
}

Test Test の Inspector に test が表示されるようになる
UnityScriptAccess1.png

Inspector に Test を指定する
UnityScriptAccess2.png

UnityScriptAccess3.png testフィールドにTestが登録された

これで GameObject.Find() を使わずに、TestScript にアクセスできるようになった
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  public GameObject test;
  void Update() {
    Debug.Log(this.test.GetComponent<TestScript>().Moge);
  }
}


毎回 GetComponent するのは効率が悪いのでプライベートなフィールドにキャッシュしておく
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  public GameObject test;
  private TestScript test_script;
  void Awake() {
    this.test_script = this.test.GetComponent<TestScript>();
  }
  void Update() {
    Debug.Log(this.test_script.Moge);
  }
}


Test の登録し忘れや、間違った GameObject が登録されるかも知れないのでチェックを入れる
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
  public GameObject test;
  private TestScript test_script;
  void Awake() {
    if (this.test == null) throw new System.NullReferenceException(); //Testを登録し忘れてたら例外を投げる
    this.test_script = this.test.GetComponent<TestScript>();
    if (this.test_script == null) throw new System.NullReferenceException();
  }
  void Update() {
    Debug.Log(this.test_script.Moge);
  }
}
これでエディタ上での操作ミスがあった場合に対応できる。

UnityScriptAccess4.png
↑Test を登録し忘れた場合は Console にこんなメッセージが表示される。

このメッセージから、NewBehaviourScript.cs の 7行目、NewBehaviourScript.Awake() の中で NullReferenceExeption が起きたことが読み取れるため、該当行 if (this.test == null) throw new System.NullReferenceException(); を見ることで、Test を登録し忘れたことが原因だと分かる。


いついかなるときも、1つしか存在しないスクリプトなら static を使うと楽

A という名前の GameObject で TestScript を使っていて、B という名前の GameObject でも TestScript を使っている。
A も B も Hierarchy に表示されている。
この場合、TestScript は2つ存在しているものとする。

A だけ Hierarchy に表示されている場合、TestScript は1つ。
B だけでも、1つ。
TestScript が1つしか存在しない場合もあり、A も B も表示されない(TestScriptが使われない)場合もある。

このように、1つしか存在しない場合もあるが、2つ以上存在する場合もある…という状況では使えない(使えないこともないけど、意図した結果にはならない)。
いついかなるときも、TestScript は1つしか使われない、もしくは、1つも使われない状況でだけ使える。
いわゆる、シングルトン(Singleton)ならOK

例えば、絶対にひとりしか存在しないユニークなNPCというのは、大抵のRPGならいるはず。

このNPCの名前は Bob とする。
Bob は固有の「コマネチ」アニメーションを持っている。
Bob だけが持っているコマネチアニメーションをスクリプトからお手軽に実行したい。
using UnityEngine;
public class NPC_Bob : MonoBehaviour {
  public static void Komanechi() { /* コマネチアニメーションを再生させる処理 */ }
}
パブリックなスタティックメソッド Komanechi() を定義した。
こうすることで、GameObject.Find() も要らないし、GetComponent も要らない。

NPC_Bob.Komanechi();

と書くだけで、コマネチアニメーションを再生する処理を実行させることができる。
Assets にある、全てのスクリプトで、NPC_Bob.Komanechi(); と同じ書き方で呼び出すことができる。

注意するのは、static なメソッドでは、static なフィールドやプロパティしか呼び出せない。
なので、これはダメ。
using UnityEngine;
public class NPC_Bob : MonoBehaviour {
  public static void Komanechi() {
    Animator a = this.gameObject.GetComponent<Animator>(); //これはNG
  }
}
this がスタティックじゃないのでコンパイルエラーになる。
this は通常省略できるので、例えば、こう書いても同じくエラーになる。
Animator a = gameObject.GetComponent<Animator>();
gameObject がスタティックじゃないから呼び出せないよ!と怒られる。

どうしてもスタティックじゃないフィールド・メソッド・プロパティにアクセスしたいなら、以下のような仕組みを作る。
using UnityEngine;
public class NPC_Bob : MonoBehaviour {
  private int life = 1;
  private static NPC_Bob instance = null;
  public static NPC_Bob Instance { get { return instance; } }
  void Awake() {
    if (instance == null) instance = this;
  }
  public static void Komanechi() {
    NPC_Bob.Instance.life = 5; //こうすることでスタティックじゃないlifeフィールドにアクセスできる
  }
  public void Say() {
    Debug.Log("Hello");
  }
}
重要なところに下線を引いた。
Instance というスタティックなプロパティを使うことで、スタティックじゃないフィールド・メソッド・プロパティにアクセスが可能になる。
ただし、life フィールドはプライベートなので、NPC_Bob クラス以外ではアクセスできない。
例えば、別のユニークなNPC Joe がいるとして、これを制御するC#スクリプトを NPC_Joe とする。
この NPC_Joe から NPC_Bob の life にアクセスすることはできない。
パブリックな Say() メソッドなら、NPC_Joe からでもアクセスできる。
using UnityEngine;
public class NPC_Joe : MonoBehaviour {
  void Update() {
    NPC_Bob.Instance.life = 0; //これはエラー
    NPC_Bob.Instance.Say(); //これはOK
}


あとは、子の GameObject を取得する方法が分かれば困ることはないでしょうか。
GameObject が取得できれば、そのコンポーネントに登録されているスクリプトにアクセスできます。

子の GameObject にアクセスする方法は、Unity Scripting API で transform.childcount と transform.getchild で検索をかけると見つけることができます。
//全ての子のGameObjectを取得する
for (int i = 0; i < this.transform.childCount; i++) {
  Transform t = this.transform.GetChild(i);
  GameObject child = t.gameObject;
}
子だけなら、この処理で十分ですが、GameObject は入れ子構造にできるので、孫やひ孫、ひひ孫…とかなり深いネストになっている場合もなきにしもあらずです。
完璧にやりたいなら、これだけでは不十分です。
子だけでなく、孫、子々孫々の全ての GameObject を取得したい場合は、再起呼び出しを使うなどして、取得した GameObject を List に突っ込むみたいな処理が必要です。
using UnityEngine;
using System.Collections.Generic;

public class SomeClass : MonoBehaviour {
  private List<GameObject> children;
  private void SeekChild(Transform t) {
    Transform child;
    for (int i = 0; i < t.childCount; i++) {
      child = t.GetChild(i);
      this.children.Add(child.gameObject);
      this.SeekChild(child);
    }
  }
  public List<GameObject> GetAllChildren() {
    this.children = new List<GameObject>();
    this.SeekChild(this.transform);
    return this.children;
  }
}

 
スポンサーサイト

- 0 Comments

Add your comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。