基礎の理解度確認
これは
初心者向け UnityのGetComponentってなんやねん?
の補足説明記事になります。
わかりきっている方は読み飛ばしてください。
型と代入
超基本的なことのおさらいです。
例えば、
int a;
a = 3;
print("aの値は " + a + " です。");
// 出力結果:「aの値は 3 です。」
変数の宣言と同時に代入できます。
int a = 3;
print("aの値は " + a + " です。");
// 出力結果:「aの値は 3 です。」
intは整数型なので、
int a = 3.14f; // 整数型に小数は代入できない
float f = 3.14f; // 型が合っているのOK
int b = "あいうえお"; // 整数型に文字列型は代入できない
string s = "あいうえお"; // 型が合っているのOK
型の合わないものは代入できない
同様に
Rabbit rabbit = GetComponent<Dog>(); // うさぎ型に犬型は入らない
Rabbit rabbit = GetComponent<Rabbit>(); // 型が合っているのOK
余談 var って何?
var は自動型です。冗長な記述をスッキリできます。
var a = 3; // 整数型を代入しているので、自動的にvarはint型になる
int a = 3; // こう書いたのと同じ意味
var rabbit = GetComponent<MohuMohuRabbit>(); // 長い型名でもスッキリ書ける
MohuMohuRabbit rabbit = GetComponent<MohuMohuRabbit>(); // 長くて読み難い
var rb = GetComponent<Rigidbody>(); // スッキリ!
Rigidbody rb = GetComponent<Rigidbody>(); // 長い
変数のスコープ
変数にはスコープといって有効範囲があります。
int型変数の例
まずは簡単なint型で説明します。
変数の有効範囲は変数を宣言した { } ブロック内です。
Start()関数内で宣言した変数 a はStart()関数内のみで有効なローカル変数です。Start()関数から抜けると消えてしまいます。
メンバ変数名とローカル変数名が被った場合
public class IntScopeTest : MonoBehaviour
{
private int a; // メンバ変数
void Start()
{
// Start()関数内で宣言した a はStart()関数内が有効範囲のローカル変数
int a = 4; // ローカル変数 a に代入
// ここで↓ a と記述すると、Start()内で↑で宣言したローカル変数の a だと解釈される
// 変数名が被っている場合、よりローカルな方だと解釈される
// ↓「aの値は4です」と出力される
Debug.Log("aの値は" + a + "です");
} // Start()関数内を抜けるとローカル変数aは消失
void Update()
{
// Start()内のローカル変数aのことは知らないので、メンバ変数aだと解釈される
// ↓「aの値は0です」と出力される
Debug.Log("aの値は" + a + "です");
}
}
対して、
class 内で宣言されたメンバ変数 a の有効範囲はclass { } 内全体で有効です。ですので、このclass { }内であれば、どの関数内からでも呼び出すことができます(他のクラス内からは呼び出せません)。ローカル変数名がメンバ変数名と被っている場合、よりローカルな方の変数を指していると解釈されます。
メンバ変数だけ宣言した場合
public class IntScopeTest : MonoBehaviour
{
private int a; // メンバ変数
void Start()
{
// メンバ変数aだと解釈される
a = 4; // メンバ変数 a に代入
// ここで↓ a と記述すると、メンバ変数の a だと解釈される
// ↓「aの値は4です」と出力される
Debug.Log("aの値は" + a + "です");
}
void Update()
{
// メンバ変数 a のことだと解釈される
// ↓「aの値は4です」と出力される
Debug.Log("aの値は" + a + "です");
}
}
Rigidbodyの例
同様のことがRigibody型でも起こります。
メンバ変数名とローカル変数名が被った場合
以下のコードはJump()を呼んだ瞬間にNullReferenceExceptionエラーになります。
public class RigidScopeTest : MonoBehaviour
{
private Rigidbody rb; // ここのメンバ変数rbと
void Start()
{
// Start()関数内で宣言したrbはStart()関数内が有効範囲のローカル変数
Rigidbody rb = GetComponent<Rigidbody>(); // ローカル変数rbに代入
// ここで↓rbと記述すると、Start()内で↑で宣言したローカル変数のrbだと解釈される
// 変数名が被っている場合、よりローカルな方だと解釈される
// ↓このAddForceは問題なく実行される
rb.Addforce(Vector3.up * 5, ForceMode.Impulse);
} // Start()関数内を抜けると折角ゲットした内容が消滅
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
void Jump()
{
// Start()関数内のrbのことは知らないので、メンバ変数rbと解釈される
// メンバ変数のrbには何も入っていなくてエラー
rb.Addforce(Vector3.up * 5, ForceMode.Impulse);
}
}
Start()関数内で
Rigidbody rb = GetComponent(); と変数宣言して書くか
rb = GetComponent(); と宣言せずに書くかで大違いです。
初心者の方にこのミスが多いので注意してください。
メンバ変数だけ宣言した場合
public class RigidScopeTest : MonoBehaviour
{
private Rigidbody rb; // メンバ変数
void Start()
{
// このrbはメンバ変数rbを指す
rb = GetComponent<Rigidbody>(); // メンバ変数rbに代入
} // Start()関数内を抜けてもゲットした内容は消えない
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
void Jump()
{
rb.Addforce(Vector3.up * 5, ForceMode.Impulse); // 問題なく実行可能
}
}
なんでローカルな方が優先されるの?
例えば、あなたの教室内にイチローという名前のひとが居るとします。
「おい、イチロー」と呼んだ場合、普通その級友の方を指していると解釈されると思います。誰もマリナーズのイチロー選手の方を呼んだとは思わないでしょう。変数名も同様に、名前が被った場合、よりローカルな方を指していると解釈されます。
全部同じじゃないですか~?
全部違います。3つの変数名aが被っており、よりローカルなaだと解釈されます。
public class Aaa : MonoBehaviour
{
private int a = 1; // メンバ変数aを宣言
void Start()
{
int a = 2; // Start(){}のローカル変数aを宣言
if(true)
{
int a = 3; // if(){}のローカル変数aを宣言
Debug.Log("aの値は" + a + "です"); // 「aの値は3です」
}// if(){}を抜けたら、中で宣言した変数aは消失
Debug.Log("aの値は" + a + "です"); // 「aの値は2です」
} // Start(){}関数を抜けたら、中で宣言した変数aは消失
void Update()
{
Debug.Log("aの値は" + a + "です"); // 「aの値は1です」
}
}
そもそもなんで変数のスコープなんてあるの? 要らなくね?
もし、変数のスコープという概念がなくて全部どこからでもアクセスできるグローバル変数だったら? x や y、speedとかいちいち他の箇所で既に使っていないか確認して回る必要があります。他の.csファイル内でも使っていないか?っと見て回るのは大変です。
なので、変数には{ }ブロック内のみで有効…っという制限があります。
まだ、どこからでもアクセス可能な変数だと不用意に弄ってしまい、バグの温床になる可能性が高まります。