2008年10月30日木曜日

スクリプトの話(14)〜「スコープ」のこと

前回の llOwnerSay を使って変数の値を確認しよう、という文章に対し、
hiromitsu Fall さんからご指摘がありましたので補足しておきます。
つまり、llOwnerSay は string 型のものしか使えませんよね、
ということです。
integer(整数)型の変数の値を知りたい場合には、
これを string 型に変換する必要があります。

例えば、ドアを開け閉めするスクリプトを組んでいて、
開ける時は openDoor()、閉じる時は closeDoor() という
自作関数を使うものとします。
この時、ドアが開いているか閉じているか
判断する手段として isOpen という整数型の変数を用意しました。

integer isOpen

if (isOpen = 0){
  openDoor();
  isOpen = 1;
} else {
  closeDoor();
  isOpen = 0;
}
llOwnerSay((string)isOpen);

結局、今ドアは開いていることになっているのか
閉まったことになっているのか、
最後に llOwnerSay() で確認しています。
ここで、isOpen は整数なので、その前に (string) をつけて
型を変換しているわけです。
これで「1」とか「0」とかしゃべるはずです。^^

     *   *   *

さて、前置きが長くなりましたが、
こうして変数の中身を確認したところで、
その結果が期待していたものと違う場合はどうしましょう?
どう見ても関数の使い方や
計算の仕方は合っていそうなのに。。。。
そんな時疑うべきはその変数の「スコープ」ということです。

「スコープ」とはもともとギリシャ語で、
「見る」という意味の言葉が変化したもので
「狙いをつける」というような意味になります。
実は変数はいつでもどこでも有効、というわけではなく、
「スコープ」=「狙い」=「射程範囲」が決まっています。
その範囲を超えると使えないのです。

いつものスクリプトをちょっとアレンジしてみましょう。

default {
  state_entry(){
    string strMessage = "Hello, Avatar!";
    llSay(0, strMessage);
  }

  touch_start(total_number){
    string strMessage = "Touched.";
    llSay(0, strMessage);
  }
}

llSay(0, strMessage) という全く同じ命令が2度出てきますが、
1回目は「Hello, Avatar!」としゃべり、
2回目は「Touched.」としゃべります。
strMessage の定義はそれぞれ state_entry() と
touch_start() の{}の中でされていますが、
実は、{}の中で定義された変数は
その{}の中だけしか有効ではないのです。
なので、もし、タッチした時も「Hello, Avatar!」と言わせたくて、

default {
  state_entry(){
    string strMessage = "Hello, Avatar!";
    llSay(0, strMessage);
  }

  touch_start(total_number){
    llSay(0, strMessage);
  }
}

としたとすると、すぐに
「Name not defined within scope」
(この名前はスコープの範囲内で定義されていません)
と怒られてしまいます。
このように{}の中だけで有効な変数を
「ローカル変数」と呼んでいます。

では、今のように{}の外でも同じ変数を使いたい場合は
どうすればいいでしょう?
それが、この連載にはじめの頃、一番最初に書くように、と言った
あの変数です。

string strMessage = "Hello, Avatar!";

default {
  state_entry(){
    llSay(0, strMessage);
  }

  touch_start(total_number){
    llSay(0, strMessage);
  }
}

これでどちらの場合も「Hello, Avatar!」としゃべるように
なったはずです。
どんな{}よりも外側にいるので、どんな場所でも有効です。
これを「グローバル変数」と言っています。
世界中どこでも、っていうニュアンスですね。^^

それでは、クイズです。
次の処理で、llSay() はそれぞれ何としゃべるでしょう?

string strMessage = "Hello, Avatar!";

default {
  state_entry(){
    string strMessage = "I love you!";
    llSay(0, strMessage);
  }

  touch_start(total_number){
    llSay(0, strMessage);
  }
}

おわかりになりますか?
state_entry() の方は、「I love you!」としゃべり、
touch_start() の方は、「Hello, Avatar!」としゃべります。
このように、同じ名前の変数が異なる階層にある場合は、
一番内側の{}の中にある定義が優先されます。
従って、state_entry() の{}の中の strMessage の定義は
この中だけで有効です。
一方、touch_start() の{}の中には何も定義がないので、
グローバル変数、一番頭にある strMessage の定義が有効となるのです。

因みに、グローバル変数とローカル変数の使い分けですが、
どうもプロのプログラマーはグローバル変数は使いたがりません。
長いプログラムを書いていて、
もし変数の定義を変更する必要が出た時、
その変数を使っているところを全部チェックしなければなりません。
それよりも、ローカル変数にしておいて
その部分だけ気にしていればいい、という方が楽だからです。
例えば、よくある繰り返しの処理で出てくる
i という変数。。。

integer i = 0;

while(i <= 10){
  llSay(0, "I love you!");
  i = i + 1;
}

これは10回「I love you!」としゃべるスクリプトですが、w
「10回」と数を数えるのに変数 i を使ってますね。
問題はこうした繰り返しの処理がいくつもある場合、
その変数にいちいち i, j, k, l... と違う名前をつけるのも面倒です。
i をそれぞれの処理の{}の中で、つまりローカルで使えば、
他のところでその中身がどうなってるか気にせずに
何度も使い回すことができます。

しかし、逆に、ある関数やステートの中で使った変数を
そのまま他の関数やステートで使いたい場合もあるでしょう。
そんな、変数を持ち回りしたい時はグローバル変数が活躍します。

長くなりましたが、変数の中身がどうもおかしいという時は、
是非この変数の「スコープ」を疑ってみて下さい。
慣れないうちはわけがわからないかもしれませんが、
やがて使い分けができるようになると実は便利なものなのです。

No response to “スクリプトの話(14)〜「スコープ」のこと”

Leave a Reply