ドアを作ろう(初級スクリプト第五回)

Miz

2007年04月09日 12:11


まずはbuild

基本的なものにも関わらず、スクリプト初心者にとっては少々難しいのがドアのスクリプトです。
真横にスライドするドアならばまだ楽ですが、回転の要素が入ってくると途端に難しいと思う人が多いようですね。
前回の椅子を作る際に、回転角度の指定方法について便利な書き方を説明しましたので、今回はそれを使ってドアのスクリプトにチャレンジしてみようと思います。

例によってまずはbuildからです。
ドアの作り方にもいろいろありますが、注意していただきたいことは一点のみです。
スクリプトによる回転は、あくまでもオブジェクトの中心座標(ルート座標)を基準に行われますので、ルートの座標をドアの蝶番の位置にあうようにしなければなりません。

私は単純に、立方体を板状にした1primのドアを用意しました。



回転の軸をドアの端っこにするために、パスカットを使って板を半分にしています。
よくわからない方は、ひとまずパスカットの値を、0.375-0.875に設定してみて下さい。
立方体が半分になって、結果中心座標が端っこになります。

回転軸を端っこにする手法としては、棒状にした円柱をドアの端に配置し、それをルートプリムにする方法もあります。
それだとprim数が増えてしまうのであまりお勧めはしませんが、特殊な形状のドアを作る際にはそうすると良いでしょう(ホビットの家の丸ドア等)。
ドアスクリプトの構成

しつこいようですが、今回もスクリプトの構成をよく考えてから進めていきます。
「ステート(状態)」、「イベント(きっかけ)」、「反応」の三つはもうお馴染みですね。

まずステートですが、ドアの場合は椅子と違って「開いた状態」と「閉じた状態」があります。
これをそのままステートにしましょう。
ステート「open」とステート「close」です。

イベントは、素直に「タッチ」にしておきます。
アラジンと魔法のランプのように「開けゴマ」をイベントにしてもいいのですが、日常的に使うドアとしてはタッチのほうが便利でしょう。

ステート「open」のときに「タッチ」すると、「閉じた状態」になり、
ステート「close」のときに「タッチ」すると、「開いた状態」になる。
そのようなスクリプトにしましょう。

なお、defaultステートは必ず書かなければいけないステートですが、あくまでも「初期状態」ということで考えて、スクリプトの初期設定を行うステートにします。
スクリプトが動きだした後は、ドアを「閉じた状態」にしたいので、defaultステートの処理は、
ステート「default」が「開始されたとき」には「初期設定を行い、閉じた状態にする」ようにします。

ではさっそくここまでを書いてみましょう。
スクリプトを新規に作成し、不要な部分を削って以下のようにステートとイベントを追加します。


01:  default
02:  {
03:    state_entry()
04:    {
05:      state close;
06:    }
07:  }
08:  
09:  state open {
10:    state_entry()
11:    {
12:  
13:    }
14:    
15:    touch_start(integer total_number)
16:    {
17:      state close;
18:    }
19:  }
20:  
21:  state close {
22:    state_entry()
23:    {
24:  
25:    }
26:  
27:    touch_start(integer total_number)
28:    {
29:      state open;
30:    }
31:  }


defaultステート以外のステートを書くときには、先頭に"state"を書きます。
今回はopenステートとcloseステートを作りますので、それぞれ、
 state open
 state close
を書きました。

イベントはお馴染みのstate_entry()とtouch_start(integer total_number)です。
defaultステートは初期設定を行ったあとにステートをcloseに変えるだけですので、タッチのイベントはありません。

ステートを変更するときには、
  state hogehoeg;
のように書きます。
5行目、17行目、29行目に書いてあるのがこれです。

5行目は、defaultステートからcloseステートへの変更です。
17行目はopenステートのときにタッチされたらcloseステートに変更する処理です。
同様に、29行目はcloseステートのときにタッチされたらopenステートに変更する処理です。

これでステートとイベントについては出来ました。
ですが、肝心のドアの回転については何もまだ出来ていませんので、これだけでは見た目何も起こらないスクリプトです。

回転の処理

回転のデータの書き方については、前回の椅子のときにも出てきました。

  llEuler2Rot(;<0.0, 0.0, 90.0> * DEG_TO_RAD)

オマジナイのように、この書き方は覚えてしまいましょう。
この書き方を利用して、ドアの回転を行う処理を書きます。

オブジェクトを回転させるための命令は、
  llSetRot(rotation rot);
です。
"rotation rot"の部分に、上記のオマジナイを書くと、オブジェクトは指定した角度に回転します。

さて、ここでちょっと一工夫します。
ドアが「閉じているときの角度」と「開いているときの角度」をそれぞれ数字で指定してもいいのですが、それですとドアを作るたびに、いちいち角度を調べてスクリプトの数字を直さなければなりません。
例えば西向きのドアと、南向きのドアでは、「閉じているとき」「開いているとき」の角度が異なりますよね。
どちらのドアでも同じスクリプトが動くようにしたいところです。

そこで、
「初期状態のドアの角度」=「閉じているときの角度」
として、
「開いたときの角度」は「閉じているときの角度+90度」としたらどうでしょうか。
これならば、ドアが西向きであろうが、南向きであろうが、開いたときには+90度されるだけです。

これを実現するには以下のような処理を追加すれば良いでしょう。
・defaultステートのstate_entry()イベントの中で「初期状態の角度」を設定する
・閉じたとき(=closeステートのstate_entry()イベント)にはドアを「初期状態の角度」にする
・開いたとき(=openステートのstate_entry()イベント)にはドアを「初期状態の角度+90度」にする

コードはこのようになります。
太字が追加部分です。


01:  rotation rot;
02:  
03:  default
04:  {
05:    state_entry()
06:    {
07:      rot = llGetRot();
08:      state close;
09:    }
10:  }
11:  
12:  state open {
13:    state_entry()
14:    {
15:      llSetRot(rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD));
16:    }
17:    
18:    touch_start(integer total_number)
19:    {
20:      state close;
21:    }
22:  }
23:  
24:  state close {
25:    state_entry()
26:    {
27:      llSetRot(rot);
28:    }
29:  
30:    touch_start(integer total_number)
31:    {
32:      state open;
33:    }
34:  }


見慣れない表現が出てきました。
1行目に書いてある「rotation rot;」です。

これはドアの初期角度を保持しておくための「変数」です。
変数とは、数字や文字、位置や角度などのデータを扱うためのものです。

しばしば初心者さん向けのプログラミング解説書では「データを入れておく箱のようなもの」と説明されます。

変数を使うときには、
  変数の型 変数名;
のように書きます。
ここでは「回転角度」のデータを扱いますので、データの型はrotation型です。
変数名は自由につけられますが、何のための変数かわかりやすい名前にすべきでしょう。今回はrotという名前にしました。

7行目に、
  rot = llGetRot();
とあります。
llGetRot()という関数はオブジェクトの回転角度を教えてくれる関数です。
これが
・「初期状態の角度」を設定
の処理になります。
つまり「変数rotにオブジェクトの現在の角度を設定する」ということです。

15行目は「開いたとき」すなわち「ドアを初期状態の角度+90度にする」処理です。
  llSetRot(rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD));
ごちゃごちゃ書いてありますが、分割して見て行けば全て今までに出てきた内容です。

まずllSetRot()はオブジェクトの角度を設定する命令ですね。
この命令に「rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD)」のように角度を指定しています。
この部分は、「rot」と「llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD)」に分けれます。
「rot」は先ほど設定したドアの初期角度、「llEuler2Rot・・・」は例のオマジナイでZ軸回りに90度回転することを意味しています。

注意して見て頂きたいのは、「rot」と「llEuler2Rot・・・」が「*」でつながっているところです。
「*」は掛け算、つまり「×」のことです。

「あれ?初期角度+90度だから、×じゃなくて+じゃないの?」

素直に考えるとそのような疑問が出てきますが、四元数を使った3D座標系の回転は、掛け算をすることによって二つの回転を合計するのです。
あまり数学的なことを書くのもアレですので、「角度+角度」をしたいときには「角度×角度」と書くのだということを覚えてしまいましょう。

要するに15行目の
  llSetRot(rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD));
これで「ドアを初期状態の角度+90度にする」ことになります。

27行目は簡単ですね。
閉じたときにドアを「初期状態の角度」にする処理です。
rotには初期角度を設定してありますから、そのまま、
  llSetRot(rot);
これでドアは初期角度になるわけです。

ここまででドアの動きとしては必要なところを実装できました。

開けっ放しは嫌よ

ついでにもう少しスクリプトを改良しましょう。
今のままですと、一度開いたドアは、もう一度タッチしないと閉まりません。
閉め忘れたまま玄関全開、なんて状態にならないよう、開いてしばらく経つと自動的に閉じるような仕組みにしたいところです。

お馴染みの思考で考えてみましょう。
「開いているとき」「しばらく経つと」「閉じる」
つまり、
「ステートopen」のとき「一定時間が経過」すると「ステートcloseになる」
これが出来れば良いわけです。

「一定時間が経過」というイベントは初めて登場します。
timerイベントと言います。
このイベントはllSetTimerEvent()という命令とセットで使います。

どのように使うかと言うと、まず、
  llSetTimerEvent(10.0);
こう書くとタイマーの発動が10秒ごとに設定されます。
  llSetTimerEvent(5.0);
これなら5秒ごとです。
タイマーを切るときには、
  llSetTimerEvent(0.0);
このように0.0を指定します。

llSetTimerEvent()を実行したあと、指定された秒数が経過すると、timerイベントが起こります。
timerイベントは単純に、
  timer(){
  
  }
このように書きます。

ドアスクリプトに組み込んでみましょう。


01:  rotation rot;
02:  
03:  default
04:  {
05:    state_entry()
06:    {
07:      rot = llGetRot();
08:      state close;
09:    }
10:  }
11:  
12:  state open {
13:    state_entry()
14:    {
15:      llSetRot(rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD));
16:      llSetTimerEvent(30.0);
17:    }
18:    
19:    touch_start(integer total_number) {
20:      llSetTimerEvent(0.0);
21:      state close;
22:    }
23:  
24:    timer(){
25:      llSetTimerEvent(0);
26:      state close;
27:    }

28:  }
29:  
30:  state close {
31:    state_entry()
32:    {
33:      llSetRot(rot);
34:    }
35:  
36:    touch_start(integer total_number)
37:    {
38:      state open;
39:    }
40:  }


16行目で、ドアが開いたときに30秒のタイマーをセットしています。
24行目から27行目がタイマーが発動したときの処理です。
まず25行目でタイマーを切り、26行目でステートをcloseに変更しています。
これで、ドアが開いてから30秒経つと自動的に閉じるようになります。

なお、ドアによっては、内開き・外開きを変更したい場合もあるでしょう。
その場合は15行目の回転角度を、-90度にしてやれば開く向きが変わります。

今回の復習

ステートの書き方:
  state ステート名{
  
  }

ステートの変更:
  state ステート名;

回転角度の設定:
  llSetRot(rotation rot);

回転角度の取得:
  rot = llGetRot();

変数の定義:
  変数の型 変数名;
  例:rotation rot;

回転の合成:
  角度+角度ではなく角度×角度
  例:rot * llEuler2Rot(<0.0, 0.0, 90.0> * DEG_TO_RAD)

タイマーの使い方:
  llSetTimerEvent(秒数);で設定し、
  timer(){}で処理

だんだん覚えるべきポイントが増えてきました(^^;
がんばっていきましょう~。
初級スクリプト