ソラマメブログ

2007年04月09日

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

まずは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(){}で処理

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



同じカテゴリー(初級スクリプト)の記事画像
衝突判定(スクリプト初級第二十三回)
カメラ制御(スクリプト初級第二十二回)
センサーを使おう(スクリプト初級第二十回)
HUDを作ろう(スクリプト初級第十六回)
prim間通信(スクリプト初級第十五回)
アニメさせよう(スクリプト初級第十三回)
同じカテゴリー(初級スクリプト)の記事
 衝突判定(スクリプト初級第二十三回) (2007-05-08 12:15)
 カメラ制御(スクリプト初級第二十二回) (2007-05-07 14:36)
 デモ商品を作ろう(スクリプト初級第二十一回) (2007-05-02 12:15)
 センサーを使おう(スクリプト初級第二十回) (2007-05-01 12:15)
 ステートのこと(スクリプト初級第十九回) (2007-04-27 12:15)
 rez!(スクリプト初級第十八回) (2007-04-26 12:15)
この記事へのトラックバック
某所に作った私の別荘。いろいろスクリプトを仕込みましたので、何回かに分けて紹介します。 今回は建物の作った「クリックすると開くドア」と庭に出るための「スライドする大きな窓...
ドアを作る【Konp KobaのSecond Life 雑記帳】at 2007年05月10日 00:39
やもさんより質問いただいたスクリプトについて解説です。やもさんからの質問最近自分で服を作るようになり試着の為の更衣室を用意したのですがドアに鍵をかけるか、自分にしか開けら...
鍵をかける【Makapu】at 2007年05月10日 16:24
ガラスの柱は、無事にショーケースとして出来上がりました。この写真のあと、形状はちょっと変更してドアをつけることにして、商品をゲットするときにはタッチで開けられるようにしま...
ドアを開けるスクリプトも組み込み成功!【初音メッセ】at 2007年05月21日 22:15
この記事へのコメント
こんばんは。^^

いつもおせわになっております。

最近自分で服を作るようになり試着の為の更衣室を用意したのですが
ドアに鍵をかけるか、自分にしか開けられないドアするにはどのようにしたら良いのでしょうか?

教えていただきたく書き込みさせていただきました。
Posted by やも at 2007年05月10日 15:05
>やもさん

コメントでは長くなりそうでしたので記事としてお返事しました。
http://miz.slmame.com/e4842.html
こちらのエントリーをご参照ください(^^
Posted by Miz at 2007年05月10日 16:26
>やもさん

更衣室とはちょっと違うんですが、着替え用HUDという手もあり。
http://ichigoya.slmame.com/e4850.html
Posted by Ichigo Mayo at 2007年05月10日 17:16
>Ichigo Mayoさん

着替え用HUD拝見しました。
なかなか楽しそうな仕掛けですね。

要はprimやパーティクルで自分自身をすっぽり覆って見えなくする仕組みですが、あとから水や火、ハートなどを追加できるようになっているのが良いですね。
一つのシステムの作り方として、これは上手なやり方だと思います。
Posted by Miz at 2007年05月11日 09:38
おかげさまで開くドアができました。でもドアの枠をつけると開かないんです・・・どうしたらうまく行くでしょうか?ドアのスクリは,ドアと縁の間の金具に入れました。
Posted by すみ at 2007年05月13日 04:26
>すみさん

おそらくリンクの問題だとは思いますが、金具だけが回転したりしていませんか?

以下の2通りの方法を試してみて下さい。

1、ドアにスクリプトを入れる。リンクはしてもしなくても可。

2、金具にスクリプトを入れる。金具をルートにし、ドアとリンクし、枠はリンクしない。

どちらでも動作するはずですが、1の方法ではドアだけが回転し、2の方法ではドアと金具が回転します。
Posted by Miz at 2007年05月14日 10:47
レスありがとうございます。金具とドアが一緒に回ってます(^^)今の現状では枠とドアの二つを持っていないといけないんですが,すべてをリンクさせて中のドアのみ開閉させたいんです。可能でしょうか?
Posted by すみ at 2007年05月14日 15:01
>すみさん

基本的に「スクリプトが入ってるオブジェクト」が回転しますので、スクリプトをドアに入れればドアだけが回転します。

ただし、ドアがルートprimだった場合、リンクされた全てのprimが一緒に回転してしまいますので、ドアはルート以外にしてみてください~。
Posted by Miz at 2007年05月14日 15:23
一日考えて理解してきました(^O^)ルートプリムとはリンクされたオブジェを編集する時に色が変わってる所ですよね?そこにスクリが入っているとオブジェ全体に影響があるんですよね?私のドアは枠×4,ドア×1,ノブ×2,ノブの根本×2,金具×2の11プリムで構成されてます。このうち枠以外に回転のスクリを入れて最後に枠をリンクさせれば・・・と考えました。長文すいませんf^_^;
Posted by すみ at 2007年05月15日 19:14
>すみさん

そうです、ルートプリムは色が黄色になっているプリムです。
リンクするときに、一番最後に選択したプリムがルートプリムになります。

うーん、その構成だとドアだけを回転させるのはなかなか難しいかもしれませんね。
それぞれにスクリプトを入れると、パーツがそれぞれの中心を元に回転してしまいますので・・・。
枠以外を1オブジェクトにリンクし、回転スクリプトを入れ、枠を別オブジェクトにするのが一番現実的かもしれません。
Posted by Miz at 2007年05月15日 20:12
ドアのぶ付近私には難しくて・・・テクスチャー貼りますf^_^;
先生のおかげで知識が深まりました。ありがとうございましたm(^^)m
Posted by すみ at 2007年05月15日 23:57
こんにちは!
初めてスクリプトに挑戦するのに、Mizさんの「ドアを作ろう」のスクリプトを使わせてもらいました。
ありがとうございます!!
とても丁寧に説明を書いてくださっているので、とてもわかりやすかったです。
私のブログに、こちらのアドレスをトラックバックとして入れさせてもらいました。
トラックバックの使い方はそれでいいのか?初めて付けてみたのでわかりませんが、とりあえずご報告を・・・
Posted by 初音 at 2007年05月21日 18:03
>初音さん

無事ショーケースの扉が出来たようでなによりです(^^

トラックバックは記事単位に付けますので、記事ごとにURLがあります。
この記事の場合は
http://miz.slmame.com/t1714
がアドレスになります。
記事URL(http://miz.slmame.com/e1714.htm)とは異なりますので注意です。

それから、トラックバックを付けた際には、本文中にトラックバック先のリンクを含めたほうがマナーには適うと思います。
私のところにトラックバックする際には別にリンクがなくても構いませんが、人によってはうるさいこと言う場合もありますので(^^;

では、充実したセカンドライフをお過ごしください(^^
Posted by Miz at 2007年05月21日 18:14
Mizさん、トラックバックの使い方までご指導くださって、ありがとうございます。
なるほど~、トラックバックとは、記事ごとのアドレスを付けるのですか。
勉強になりました。
さっそく訂正して、本文にもアドレスを入れてみます。

では、これからもよろしくお願いします。
Posted by 初音 at 2007年05月21日 22:11
はじめまして。
色々と丁寧に解説してくれて大変助かっています。

回転ドアのスクリプトを流用してタッチすると開閉する木箱を作ったのですが、蝶番を軸にして蓋を開閉する場合箱と蓋をリンクしてしまうと箱ごと動いてしまいますし、なにか良い方法は無いかと見ていたらMizさんの自動ドアのスクリプトと合体させれば!とやってみたのです。

何とか上蓋だけを回転させる事に成功したのですが、回転軸が箱の中心になっている為思うような形にはなってくれません。

何か回転軸をずらす方法などはないでしょうか?
Posted by 美月 at 2007年05月25日 01:03
ごめんなさい><
ちゃんと読んで無かったです

最初の方に説明されてましたね^^;
アリガトウございます!
Posted by 美月 at 2007年05月25日 01:09
>美月さん

無事解決したようで何よりです(^^
Posted by Miz at 2007年05月25日 09:47
こんにちは。
スクリプト初心者なのですが教えてください。

ちょっと質問なのですが 最後のタイマーイベントスクリプトの
ところですが21行目の state close; は必要なのでしょうか?

下のタイマーが0になった時にstat close; となっているので
いらない気がするのですが、教えてください。
Posted by 月神 at 2007年06月18日 11:51
>月神さん

こんにちは。
21行目のstate close; はタッチイベントの中の記述ですね。
タイマーで閉じる処理だけでも確かに問題はないのですが、タッチしたときにも閉じるようにするために、state close;が二箇所あります。

SLのスクリプトは全てイベントがきっかけで動きますので、同じ処理でも、きっかけが複数ある場合は、複数書かなければいけない場合があります。

もちろん、「タッチで閉じる」動きが必要ない場合は、月神さんのおっしゃる通り、タイマーのほうだけで問題ありません(^^
Posted by Miz at 2007年06月18日 12:30
こんにちは!
最近このブログを発見して、勉強させていただいています。
それで、ちょっと質問させていただきたかったのですが、
コチラに回転の要素が府生まれるので、ココで質問させていただきます。
・・・というか、回転で実行させるのかどうかすら・・・汗;

えっと、柱時計の振り子のように、1つの軸に対して45~90度程度の左右の振りを恒久に繰り換えさせることは可能でしょうか。

図々しく聞いて申し訳ありません、色々考えたのですが、まだまだ
初心者でわからないことばかりで、つい頼ってしまいました。
いい方法があったら教えていただけると助かります。
Posted by Kei at 2007年07月01日 11:54
>Keiさん

振り子の動きそのものをシミュレートするのは凝り出すと結構厄介だったりしますが、妥協すれば簡単な実現方法もあります。

float x = 180.0;
float r = 45.0;

default{
 state_entry(){
  llSetTimerEvent(1.0);
 }

 timer(){
  r = -r;
  llSetLocalRot(llEuler2Rot(<x + r, 0.0, 0.0> * DEG_TO_RAD));
 }
}

1秒ごとにtimerイベントを発生させ、45度、-45度を交互に切り替えて回転させます。
これだけでもそれなりには見えます。
Posted by Miz at 2007年07月01日 22:00
ありがとうございます!
意外に短いスクリプトなんですね(汗;
早速試させていただきます!

それと、2重投稿になってしまったみたいで、申し訳ありません。

お返事ありがとうございました。
Posted by Kei at 2007年07月03日 00:50
以前からスクリプトに興味があったけど、ようやくスクリプトに手を出し始めたところです。
私は回転ドアではなくスライドドアを作ろうとしているのですが、スライドさせる方法がわかりません。
たとえば、X方向に1mスライドさせる場合、閉じている状態の時の現在のドアの位置を llGetPos() で取得し、 <1.0,0.0,0.0> を足した位置に移動すれば・・・あれ?オブジェクトの移動ってどうやれば?
llMoveToTarget かと思ったのですが、これは物理オブジェクト用みたいですし。
リファレンスを見た感じでは、オブジェクトの移動関数がないような気がするのですが・・ (見落としかもしれませんが;;)
Posted by つぶあん at 2007年08月21日 00:39
>つぶあんさん

位置の変更にはllSetPos関数を使います。
llGetPos関数の対になる関数です。

つぶあんさんが仰る通り、llGetPos関数で取得した現在位置に、移動する距離を足してやれば、スライドドアができます。
初級スクリプトの第二十回「センサーを使おう」の回で、センサーで自動的に開くドアを作るのですが、そのドアはスライド方式になっていますので、参考にしてみて下さい。
http://miz.slmame.com/e3954.html
Posted by MizMiz at 2007年08月21日 09:20
Miz さん、ありがとうございます。
回転関数があって移動関数が無いなんてことは無いですよね w
早速組み込んでみたら、ちゃんと動きました!
これからもお世話になると思いますが、よろしくお願いします。
Posted by つぶあん at 2007年08月22日 00:05
はじめまして。
SLを始めて3ヶ月、ここにたどり着いてスクリプトにも挑戦し始めましたw

ドアの回転とtimerイベントを参考にさせていただき「鹿威し」を作れないものかと思い下記のようなスクリプトを竹筒の軸に仕込んでみたのですが、オブジェクトの向きを変えても回転する方向がその向きについてきてくれません。
変えた向きに合わせるにはどのようにしたらよいでしょうか?なにかヒントをいただければ幸いです。
rotation rot;
default
{
state_entry()
{
rot = llGetRot();
state tame; //最初水を溜めている。
}
}

state ake {
state_entry()
{
llSetRot(rot * llEuler2Rot(<0.0, -60.0, 0.0> * DEG_TO_RAD));
llSetTimerEvent(1.5);
}

timer(){
state tame;
}
}

state tame {
state_entry()
{
llSetRot(rot);
llSetTimerEvent(10.0);
}

timer(){
state ake;
}
}
長くなって申し訳ありません。
この記事へのコメントとして場違いであれば削除していただいて結構です。
Posted by Simhide at 2007年09月02日 11:28
>Simhideさん

とても良い質問だと思います。
回転については意外と理解している人が少ないです(難しい部分です)。
ちょっと長くなりますが、回答しておきます。

ポイントは2点あります。
(1)ご提示のコードは、「スクリプト開始時点の回転角度」を基準にして回転しているため、スクリプト開始後にオブジェクトの角度を変えてしまうと正しく回転しない。
(2)ご提示のコードは「ワールド座標のY軸」に対して回転を行うので、オブジェクトを回転させると意図しない向きに回転してしまう。「ローカル座標のY軸」に対して回転する必要がある。

簡単に修正するなら、13行目の
  llSetRot(rot * llEuler2Rot(<0.0, -60.0, 0.0> * DEG_TO_RAD));
この部分を以下の2行に置き換えます。
  rot = llGetRot();
  llSetRot(llEuler2Rot(<0.0, -60.0, 0.0> * DEG_TO_RAD * rot));

これが何をやっているかと言うと、
  rot = llGetRot();
この行はポイント(1)です。
鹿威しがカコーンと落ちる前に「元の回転角度」を取得し、これを基準に回転させるようにします。

修正した2行目は「ローカル軸」による回転です。
今回のコードでは<0.0, -60.0, 0.0>Y軸周囲を-60度回転させようとしていますが、このまま回転させるとワールド座標の軸で回転してしまいます。
ですが、* rotをすると(現在の回転値を掛け算すると)面白いことにローカル座標の回転に変わってくれます。
その辺りの理屈を説明すると長いので省きますが(^^;

vecotr型を使って一定の角度を「ローカル」に回転させたいときは「現在の角度を掛け算」する、と覚えてしまうといいかもしれません。
まずは試してみて下さい。
Posted by Miz at 2007年09月04日 00:33
丁寧な回答ありがとうございます。さっそく試してみたのですが・・
やはり、オブジェクトの向いている方向を変えてしまう(例:北向きの鹿威しを東向きに設置しなおす)と傾きが変わってしまいました。
もう少し頑張ってみます。
Posted by Simhide at 2007年09月04日 13:09
漣書きですみません。
解決しました。
llSetRot(llEuler2Rot(<0.0, -60.0, 0.0> * DEG_TO_RAD * rot));

llSetRot(llEuler2Rot(<0.0, 70.0, 0.0> * DEG_TO_RAD) *rot);
にしてみたらうまくいきました。
ありがとうございました。
これからも勉強させていただきます。
Posted by Simhide at 2007年09月04日 13:42
>Simhideさん

無事動いたようで動いて何よりです(^^
Posted by MizMiz at 2007年09月08日 13:15
大変参考になりました。
Posted by kotorin little at 2007年09月13日 10:32
こんにちわ
いつも、素敵な解説ありがとうございます。

こちらのスクリプトを見よう見まねで利用させていただいたのですが、
文中の下記の抜粋箇所(ステートがオープンの際のタッチイベント処理)

19: touch_start(integer total_number)
20: {
21: state close;
22: }

こちらの20行目と21行目の間にタイマーのリセット(無効化)を記述しないと
クローズしたのにまたクローズ処理が働いてしまうという動作をしました。
(原文にて自称確認済み)
これにより次のような現象を確認しました。
1) ドアを開けてから、タッチで閉じる。
2) 上記1でドアを開けてから(30秒*n)秒 + xx秒
  
19: touch_start(integer total_number)
20: {
// ***Timer reset**** 1++
llSetTimerEvent(0);
21: state close;
22: }
Posted by Katsu Choche at 2007年10月19日 20:55
すいません、上記編集途中で送信してしまいました(;;
削除が無いようですので上記に追記する形で

追記1  確認した現象
1) ドアを開けてから、タッチで閉じる。
2) 上記1でドアを開けてから(30秒*n)秒 + xx秒後にドアを開ける
  (XXは、29以上30未満)

結果:2でドアを開けた瞬間にドアが閉じる

レアケースではありますが、自称確認しましたので報告差し上げます。

上記、投稿の最後の記述(スクリプト)は解決案です。
Posted by Katsu Choche at 2007年10月19日 20:59
> Katsu Chocheさん

ご指摘ありがとうございます。
そうですねぇ、タイマーはステートを切り替えてもリセットされるわけではないので、closeステートに移る前にきちんと切らねばいけなかったですね。

本文のほうも修正しておきました~。
Posted by MizMiz at 2007年10月23日 10:58
こんにちはいつも参考にしています。ありがとうございます。
わからないことがあるのですが、llSetTimerEvent ()を
二つのスクリプトで使用しています。
一つは0.5秒ごとにリンクしているプリムを動かすというようなスクリプト
llSetTimerEvent(0.8);
llMessageLinked(LINK_SET, 0, "close", NULL_KEY);

もう一つは選択方式で1.5.10分になったらに完全に消すというスクリプトです。
link_message(integer sender, integer num, string str, key id)
{
llSetTimerEvent(num);
timer()
{
llDie();

プリムにはそれぞれ別々にスクリプトが入っています。
どうしても動かすスクリプトを入れると消すほうのスクリプトがうごかなくなってしまいます。
抜くと動くのですが・・・初心者ですがわかるようでしたらお願いいたします。
Posted by pom at 2007年10月23日 18:12
>pomさん

こんばんは。ふむふむ・・・。

動かすほうのスクリプトから、
llMessageLinked(LINK_SET, 0, "close", NULL_KEY);
このようにリンクメッセージを送り、

消すほうのスクリプトでは、
link_message(integer sender, integer num, string str, key id){
llSetTimerEvent(num);
受信したリンクメッセージの整数値numを使ってタイマーをセットしているのですよね?

となると、動かすほうのスクリプトから送っているリンクメッセージの整数値は
0ですので、消すほうのスクリプトがリンクメッセージを受信した時点でタイマーは0にセットされ、停止するはずです。

動かすほうのスクリプトから0以外の値を送るか、消すほうのスクリプトでタイマーを再設定しないようにすれば解決しませんか?
Posted by MizMiz at 2007年10月23日 19:05
本当にありがとうございます。
動くほうを1に変更したところ動きました!初歩的なことに丁寧に答えていただいてありがとうございました(__;)
Posted by pom at 2007年10月23日 20:51
>pomさん

いえいえ、リンクメッセージは決して初歩的ではないと思いますよ。
複数のスクリプトを連携して動かすのは、ややこしくてわかりにくい部分です。
無事に動いて何よりです(^^
Posted by MizMiz at 2007年10月24日 11:47
いつも勉強させていただいています。前にも伺ったことのある者ですが、
上記にあります、振り子のような動きを
その下にあります、鹿威しのようにドコへ向けてもOKな様にしたかったのですが、
どうしても回転して戻ってしまいます。
整数でないとダメなのでしょうか。
ちなみに、タッチでスタートさせようと思っております

とりあえず今何とか書いた部分がコチラなのですが・・・。
無駄なところも多々あるかと思いますが、
よろしくご指導いただけるとありがたいです。

float x = 0.0;
float r = 45.0;
rotation rot;

default{
state_entry(){
rot = llGetRot();
state stop ;
}
}
state swing {
state_entry()
{
llSetTimerEvent(1.0);
}

touch_start(integer total_number) {
llSetTimerEvent(1.0);
state stop;
}


timer(){
r = -r;
rot = llGetRot();
llSetLocalRot(llEuler2Rot(<x + r, 0.0, 0.0> * DEG_TO_RAD * rot));
}
}
state stop {
state_entry()
{
llSetRot(rot);
}

touch_start(integer total_number)
{
state swing;
}
}


基本的なことも今一何もわからず書いているので
その他おかしな部分も、ご指導願えたら嬉しいです。
よろしくお願いいたします。
Posted by Kei at 2007年10月29日 19:39
>keiさん

えーと、一度に動かす角度を90度にして、
float r = 90.0;

回転させるときに、
llSetLocalRot(rot * llEuler2Rot(<x + r, 0.0, 0.0> * DEG_TO_RAD * rot));

こんな感じでしょうか。

ちなみに上記の改造ですと、振り子は必ず45度傾いた状態でスタートさせないと駄目です。

とりあえず回転は文字で説明するのが難しいので、試してみて下さい(^^;
Posted by Miz at 2007年10月30日 23:50
ありがとうございます><;、
やってみます!
Posted by Kei at 2007年10月31日 12:51
はじめまして^^
いつも大変参考になり感謝しております。。
この度、当ドアスクリプトを応用して BOXに付ける蓋(上開き)を製作中です。
なのですが、オブジェクトの向きを変えると蓋だけが元の位置に戻ってしまいます(>_<)
上記等読んでTRYしてるのですが、
開けた時に元の状態(方向?)になってしまいます。
オブジェクトの向きを変えて
開けても閉めても同じ状態を維持するにはどうしたら良いでしょう・・・

どこか改善点がありましたら是非!ご教授ください

rotation rot;

default
{
state_entry()
{
rot = llGetRot();
state close;
}
}

state open {
state_entry()
{
rot = llGetRot();
llSetRot(llEuler2Rot(<90.0, 0.0, 0.0> * DEG_TO_RAD));
}

touch_start(integer total_number)
{
state close;
}
}

state close {
state_entry()
{
llSetRot(rot);
}

touch_start(integer total_number)
{
state open;
}
}

長々となってしまいましたが
初心者の私に手を差し伸べてください
よろしくお願いいたします
Posted by jarman at 2008年02月14日 13:48
>jarmanさん

この記事のコメントに回答があります。
鹿威しと振り子の話がヒントになるんじゃないでしょうか。

詳しいことは上記コメントを見てもらうとして、答えだけ書きますと、

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

この行にちょっと追加して、

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

としてやればうまく開きそうな気がする今日この頃いかがお過ごしでしょうか。

この問題を解決するにはオブジェクトのワールド座標とローカル座標との違いを理解する必要がありますが、最初につまづく人が多いトラブルです。
いや、結構スクリプトいじってる人でも「回転はわがんね」って投げてること多いですし(^^;
FAQにでも載せるべき質問かもしれませんね~。
Posted by Miz at 2008年02月15日 16:04
>この問題を解決するにはオブジェクトのワールド座標とローカル座標との違いを理解する必要があり


頭の中で問題が飛躍し過ぎてましたね(^^;
ワールドとローカルは今回は関係ないです。
無かったことにして下さい(><

llSetRotの動きは、
「primの回転を90度にする」
のように解釈すると良いかも知れません。

jarmanさんのやりたいことは、
「primを90度回転させる」(現在の角度からさらに90度回転)
ということだと思いますが、上記のllSetRotの動きとの違いがわかるでしょうか。

llSetRotは、primの現在の角度が何度であろうとも、
「primの回転をxx度にする」
そういう関数です。

ですから、primを現在の角度からさらに90度回転させるには、
「primの回転を"現在の角度 + 90度"にする」
このようにすれば良いことになります。

そういうわけで一つ前のコメントのような答えになります。

・・・自己フォローでした(^^;
Posted by Miz at 2008年02月15日 16:59
早速のお返事ありがとうございます
さっそくやってみます^^
Posted by jarman at 2008年02月16日 17:58
この度はお世話になります^^

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

*rotが抜けていたようで(>_<) スミマセン^^;良く見るべきでした・・・

記入したら思い通りの動きになりました!

質問ついでになるのですが・・よろしいでしょうか?

蓋だけだとオブジェクトを移動させても問題なく希望通りの動きになったのですが、

これを通常のBOXオブジェ(箱)とリンクさせ向きを変えると、またまた蓋が挙動不審に^^;

蓋を親プリムにしても子プリムにしてもダメなようです(>_<)

BOXオブジェ(箱)にも何かスクリプトをいれなければ無理でしょうか?

最終的には蓋をパコパコ出来る箱を作ろうと思っています(2プリムで)・・・

2つ(蓋と箱)をリンクして尚且つ移動&向き等を変えても同じ状態を保つには

何か良い方法はありますでしょうか・・・

ここでの質問では無いかも知れないですが^^;

ご教授頂けたら幸いです^^

宜しくお願いいたします<m(__)m>

お暇な時でかまいませんので是非是非お願いいたします
Posted by jarman at 2008年02月16日 18:52
jarmanさん、こんにちは。

割り込み、失礼します。

提示しているスクリプトにMizさんがご指摘しているとおり、現在のオブジェクトの回転を掛けあわさないと、常に同じ回転になってしまいます。
分かりやすいようにベクターで書きますが、例えば<90.0, 0.0, 0.0>の回転なら、常にX軸を基準に時計回りとなります。

もうひとつ、closeステートで回転の「元に戻す」ということで、openステートで取得したrotにしていると思いますが、これだとopenしてからオブジェクトを動かしてcloseするとopenする前の回転値に戻ってしまい、ずれてしまいます。

オブジェクトが常に動く可能性がある場合、Mizさんの書き方をお借りすれば、openは「primの回転を"現在の角度 + 90度"にする」。closeは「primの回転を"現在の角度 - 90度"にする」としなければなりません。

オブジェクトを箱と蓋にして、箱をルートプリムとすれば、蓋はllSetLocalRotで回転させればもっと楽になりますよ。
Posted by ささぴ at 2008年02月18日 09:38
>jarmanさん

そこで私が早とちりして書いた、
「この問題を解決するにはオブジェクトのワールド座標とローカル座標との違いを理解する必要があります」
が出てきます。

ざっくり言ってしまうとllSetRotで扱われる回転はワールド座標基準です。
ささびさんが最後に言及してくださってるllSetLocalRotはローカル座標基準です。

どう違うかと言うと、
ワールド座標は世界のXYZ軸に対する回転になります。
オブジェクト全体を回転させるような場合にはこちらを使います。

ローカル座標はオブジェクト(ルートプリム)のXYZ軸に対する回転です。
オブジェクトの一部分を回転させるような場合はこちらです。

例えばルートプリムの回転がゼロで、フタの部品が90度回転してリンクされているようなオブジェクトがあった場合、オブジェクト全体を回転させたとしても、ルートプリムに対するフタの回転角度は一定です。

というわけで、ささびさんの最後の一言が全てです(^^

なお、このblogのコメント欄での質問がしにくいようでしたら以下のBBSをどんどん活用して下さい。
http://bb2.atbb.jp/lslbbs/index.php
ユーザー登録できるBBSですが、特に登録は必要ありませんので。
Posted by Miz at 2008年02月18日 15:03
ささびさん・Mizさん

こんばんわ^^

詳しい説明をどうもありがとう御座います!

出来ました!!!!とても感激です。

そして意味もなんとなくですが理解する事が出来ました^^

スクリプト初心者なので最初は意味もさっぱりでしたが

一つずつ読み返しながら試して箱が完成致しました(^_-)-☆

お二人にとても感謝しています^^

2週間悪戦苦闘でここに書き込んでみてこのような結果は

感謝感謝に尽きます(#^.^#)

これからも初歩的な質問等ぶつけるかもしれませんが、何卒、宜しく

お願いいします。
Posted by jarman at 2008年02月18日 22:13
はじめまして^^自分には未知の世界のスクリプトなんですが^^;
ドアを四角い箱の蓋に応用したいのですが、リンクすると箱ごと90度ひらくきます

どーしてもリンクした状態で蓋部だけ開閉させることは不可能なのでしょうか><w
調べようがなく先生に聞くしかありません、よいアドバイスがあれがよろしく
お願いいたします。
Posted by apaman at 2008年02月27日 23:36
>apamanさん

箱のフタはブームなんでしょうか(^^;

まずは少し前の jarmanさんの質問とコメントを見てみてください。
おそらく作ろうとしているものは jarmanさんのものと同じような動きだと思いますので、参考になるかと思います。

とりあえず動かしてみたいぞ~ということでしたら、ドアのスクリプトの中で使われているllGetRot/llSetRotをllGetLocalRot/llSetLocalRotに変えて試してみて下さい。
なおスクリプトはフタのほうに入れて、ルートプリムは箱本体のほうにするとご利益間違いなしだと、近所の神主さんが言ってました。
Posted by Miz at 2008年02月28日 17:29
連続になりますが、回転についての質問が多いので、詳しい記事を作成中です。

回転について:http://www21.atwiki.jp/mizcremorne/pages/318.html

まだ途中ですが、ご参考にどうぞ。

また、込み入った質問などはこのblogのコメントだとやり取りがしにくい(コードとか書きにくい)ので、LSL-BBSを利用していただければと思います。

LSL-BBS:http://bb2.atbb.jp/lslbbs/index.php
Posted by Miz at 2008年02月28日 17:35
出来ました^^
ありがとうございます^^

jarmanさんの記事がまさしくそーでした^^;よく読んでたつもりが・・・><
すいません2度手間おかけしてしい・・・こんな初歩の初歩の質問ですいませんでした^^;

おかげで周りで悩んでいる仲間もよろこびます^^
ありがとうございました^^
Posted by apaman at 2008年02月28日 21:50
はじめまして
llSetRot(llEuler2Rot(<90.0, 0.0, 0.0> * DEG_TO_RAD));
このようなスクリプトは物理の物でも作動するのですか?
Posted by for you at 2008年08月12日 12:19
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。