BlackSheep-LSL@Wiki
http://w.atwiki.jp/mizcremorne/
BlackSheep-LSL@Wiki
ja
2019-02-16T11:41:30+09:00
1550284890
-
回転について
https://w.atwiki.jp/mizcremorne/pages/318.html
*はじめに
回転を扱うプログラミングは、初めての人には少々敷居の高いものです。
何故なら回転は3Dグラフィックの知識がないと理解しにくく、例えばπ(パイ)とか三角関数のように、学校の数学で習うようなものではないからです。
まったく予備知識がない状態で回転の処理に取り組むのは、非常に難解なパズルに挑むのに似ています。
そう、例えばルービックキューブを独力で解ける人なら、回転プログラムも簡単に理解できるかもしれません(^^;
ある程度LSLを使いこなしている人であっても、
「回転だけは・・・」
苦手意識を持っている人も少なくないくらいです。
私が受ける質問の中でも、回転関連のものはかなり多いです。
そんなわけで、ハッキリ言って回転は難しいです(^^;
にもかかわらず回転を使わないといけない場面というのは非常に多いのです。
例えば単純なドアを作るのにも、回転は出てきます。
乗り物を作ろうと思ったら、回転は避けて通れません。
逆説的に言うなら、回転を使えないと、作れるものの幅がずいぶんと狭くなってしまうとも言えます。
ここでは回転の基本的なところから少しずつ仕組みと理屈を解説しつつ、なるべく回転の苦手意識を克服できるような内容を書いていきたいと思います。
*rotation型
SLにおいて回転を扱う際に避けて通れないのがrotation型です。
俗に回転値とか回転データとか言います。
正式には四元数(クォータニオン)という行列値のことです。
その名が示す通り、4つの値から成り立っています。
LSLにおいては、x、y、z、sの4つの小数値です。
最初の3つx、y、zはオブジェクトが向いている方向を示しています。
最後のsはオブジェクトが向いている方向の軸の周囲をどのくらい回転しているかを意味します。
ハッキリ言って、このx、y、z、sの4つの小数値を見て、オブジェクトがどんな風に回転しているのかを即座に理解できるツワモノはあんまりいません(^^;
この四元数、ハミルトンというイギリスのおっさんが今から150年以上も前に発見したものなんですが、今でこそ3D計算には欠かせない理論になっているものの、当時はまったくもって意味不明、こんなの何の役に立つんだ状態で持て余されていたそうです。
そりゃあ150年前と言えば日本では江戸時代、ネズミ小僧がウロウロしてた頃ですから(^^;
3Dの回転計算なんて誰一人として妄想すらしてなかった時代でしょう。
ハミルトン自身も理論は確立したものの実用性が見出せず、散々に考え悩んだ挙句に最後はアル中になって野たれ死んでます。
四元数が有効に利用されるようになるのは現代に至ってから。
長い間報われなかった理論であります。
いやぁ、報われない壮絶な野たれ死には天才には付き物ですね。
それが今や3Dの回転計算には欠かせないのが四元数です。
世の中の3Dコンピュータグラフィックは四元数が無かったら実現できないと言っても過言ではありません。
もちろんセカンドライフも例外ではなく、ログインした瞬間から四元数の恩恵にあずかっていると言えます。
アバターの向きを変えるにも四元数。
オブジェクトを回転させるにも四元数。
カメラを動かすのだって四元数を使っています。
野たれ死んでしまったハミルトンに感謝しましょう。
プリムを回転させたときには、
「ありがとうハミルトン」
ぜひ心の中でそう祈りを捧げてください。
*vector型
しかし四元数は難解過ぎます。
なんたって天才ハミルトンが編み出した必殺技ですので、我々軟弱な一般人が習得するのは困難です。
通常、セカンドライフ内でプリムを回転させるとき、どんな風にするかと言うと、X、Y、Zの3つの軸の周囲を回しますよね。
X軸回りを90度。次にZ軸回りを45度・・・というように。
ビルドウインドウの回転の設定も、入力欄はX、Y、Zだけになっているはずです。
この方法のほうが、四元数よりも直観的にわかりやすいからです。
LSLにおいてはこの方式の回転はvector型で扱われます。
実際、スクリプトコードの中で回転を扱う際には大抵vector型で回転量を指定します。
例えばオブジェクトが左に90度回るような処理を書いている場合は、
vector rot = <0.0, 0.0, 90.0>;
こんなコードを書いたりします。
これなら「Z軸回り(垂直軸に対して)90度回転」と読み取るのは容易です。
このようにvector型で表現する回転値のことを、オイラー表現、と呼ぶことがあります。
ハミルトンよりさらに遡ること半世紀、今から200年以上前の数学者オイラーにちなんだ呼び方です。
彼もまた天才の例にもれず、最後は失明しながら研究に取り組むという壮絶な生き様を見せ付けてくれています。
LSLの関数の名前に出てくるEulerの読み方が「オイラー」です。
間違っても「エウレアー」とか読んではいけません。オイラーさんに失礼です。
さて。
X、Y、Zの3つの角度を使うオイラー表現。
オイラー表現はハミルトンの四元数よりも我々素人にはわかりやすいのは言うまでもないですが、何故に難解なrotation型が必要なのでしょうか。
実はオイラー表現には致命的な弱点があるためです。
その弱点はジンバルロックと言われますが、少々イメージがしにくいのでよりわかりやすい例を示しておきます。
vector rot = <90.0, 0.0, 90.0>;
上記のオイラー表現はどのような回転を意味するでしょうか。
X軸回りを90度、Z軸回りを90度ということですが・・・。
次の(A)と(B)のうち、正しいのはどちらだかわかりますか。
#ref(rotorder1.jpg)
#ref(rotorder2.jpg)
(A)はまずX軸の周囲を90度回転させ、それからZ軸の周囲を回転させています。
(B)はその逆に、Z軸、X軸の順で回転させています。
見てわかる通り、二つの方法はそれぞれ回転の結果が異なります。
正解を言ってしまうと、セカンドライフにおける回転は(B)のほうになります。
オイラー表現による回転では、X、Y、Zのそれぞれの軸の回転順序が決まっていないと、どのように回転させるのが正しいのかが決まりません。
セカンドライフではZ、Y、Xの順に回転させる、と決まっているため(B)が正解ですが、他の3Dグラフィックも同じようになっているという保証はどこにもありません。
従ってvector型では厳密には回転を表現しきることが出来ないと言えます。
厳密さを求めるのがコンピュータの世界ですので、オイラー表現よりも四元数のほうが都合が良いのです。
曖昧さを許す人間と機械との違いを浮き彫りにしているようで面白いところです。
*vectorとrotation間の変換
以上のように、回転を表現するデータにはrotationとvectorの二種類があり、そのどちらにもメリット・デメリットがあります。
rotationはコンピュータ向き、vectorは人間向き、とでも言うのが端的な説明かもしれません、
この二種類のデータは、表現方法が異なるだけで回転の実態は一緒です。
例えば赤い果実のことを「りんご」と言うのと「アップル」と言うのとで、表現は違っても意味しているところのものは同一、というのと似ています。
LSLではvectorとrotationの回転を双方に翻訳するための関数が用意されています。
[[llEuler2Rot]]関数と[[llRot2Euler]]関数です。
この二つの関数を使って、vector表現の回転値とrotation型とは自由に変換が可能です。
一般的な使い方としては、人間にわかりやすいvector型で回転値を定義し、実際に回転を行う前に[[llEuler2Rot]]関数でrotation型に変換する、というのが良くある手法です。
なお、[[llEuler2Rot]]関数で扱うことのできるvector型は、単位が度ではなくラジアンです。
ラジアンとは、円において弧の長さが半径と等しくなるような場合の中心角のことですが、言葉では何のことやらだと思うので図示してみました。
#ref(radian.jpg)
赤い線(半径)と緑の線(弧の長さ)が等しくなっています。
このときの2本の半径の間の角度が1ラジアンです。
図から直観的にわかると思いますが、1ラジアンは180度の1/3に近いです。
正確には180度=πラジアンになります。
πは3.1415・・・ですから、ほぼ1/3というのは妥当な線でしょう。
ゆとり教育を実感できます。
逆に1度は何ラジアンかと言うと、π/180ですので、約0.0174533ラジアンということなります。
まぁ、LSLにおいてはそんなことは覚えておかなくても大丈夫です。
何故なら度数とラジアンを変換するのに便利な定数が用意されているためです。
頻繁に使うのは度数からラジアンの変換だと思いますが、これにはDEG_TO_RADという定数を使います。
DEG_TO_RADの実態は先ほどの0.0174533です。
ですので例えば90度をラジアンに変換したい場合は、
float rad = 90 * DEG_TO_RAD;
としてやれば良いことになります。
これを先ほどのオイラー:四元数変換と組み合わせて、
vector e_deg = <90, 0, 0>; // 度数のオイラー表現
vector e_rad = e_deg * DEG_TO_RAD; // ラジアンなオイラー表現
rotation rot = llEuler2Rot(e_rad); // 四元数
このように使います。
*回転の向きについて
ログインして作業しているときは実際にオブジェクトの動きを目で見ながら確認できるのであまり意識しませんが、LSLのコードだけを無心に書いている時、
「はて、X軸の方向はどっちだったっけ?」
なんて迷いが生じることがあります。
ましてや回転の向きとなると、自分の望んでいる方向が+方向なのか-方向なのかわからなくなることもしばしば。
基礎知識として、セカンドライフの3D座標系について、向きの覚え方を書いておきます。
まず座標軸の方向ですが、いわゆる右手の法則で覚えます。
高校あたりで習う例のアレ、フレミングの法則と一緒ですので覚えやすいんじゃないでしょうか。
まず右手をピストルの形にします。
それから中指を人差し指と直角になる方向に伸ばす、と。
人差し指の向きがX方向、中指がY、親指がZになります。
#ref(righthand1.jpg)
さて、座標軸の方向がわかったところで、次に回転の向きですが、これまた右手を使います。
今度は親指を立て、残りの指をグーにした形です。
親指は回転の基準となる軸の+方向を示します。
残りの指の方向が、回転の+の向きです。
#ref(righthand2.jpg)
これはつまり、右ネジを締める方向が+ということです。
ネジを締めることの多い人なら、感覚的にわかるかもしれません。
なお、人前で回転の向きを考える時は、決して親指を下向きにしてはいけません。
戦いになります。
*rotationの合成
実際に回転を処理し始めるとすぐにぶち当たる問題があります。
例えば金庫のダイヤル錠のように、
「右へ40度・・・次に左に65度・・・」
と回転させる動きを考えてみてください。
この動きを実現するには「ダイヤルの現在の角度」と「回す角度」を分けて考える必要があります。
何故ならLSLに用意されている回転関数は、基本的に全て「回転角度をXXにする」機能しかなく、「現在の角度からさらに動かす」機能はないからです。
つまり、ダイヤルを+40度動かすためには、オブジェクトの角度を「現在の角度+40度」に設定してやれば良いことになります。
しかしながら、ここで一つ注意しなければいけないことがあります。
rotation型のデータは、通常の方法(つまり+)では足し算できません。
同様に引き算(-)もできません。
これはもう決め事として覚えてしまったほうが早いことですが、rotation型の足し算では*(かける)を使い、引き算では/(わる)を使います。
何故そうなるのかは四元数理論の領域に踏み込んでしまうため割愛します。
いつかハミルトンと会うことがあったら尋ねてみて下さい。
従って、金庫のダイヤルを右へ40度回すには以下のようなコードになります。
rotation r1 = llGetRot(); // 現在の角度
rotation r2 = llEuler2Rot(<40, 0, 0> * DEG_TO_RAD); // 40度(軸はX)
llSetRot(r1 * r2); // 掛け算すると回転の合成になる
同様に、左へ65度回すコードです。
rotation r1 = llGetRot(); // 現在の角度
rotation r2 = llEuler2Rot(<65, 0, 0> * DEG_TO_RAD); // 65度(軸はX)
llSetRot(r1 / r2); // 割り算すると逆回転の合成になる
*回転の合成順序
合成には*と/を使いますが、さらに注意しなければいけないことがあります。
それは回転を合成するときの順序です。
算数の世界では、掛け算の順序を入れ替えても答えは一緒になります。
2 * 5 = 10
5 * 2 = 10
しかし、回転の世界ではそうではありません。
具体的に考えてみましょう。
あるオブジェクトの回転が、X軸周囲90度の状態だったとします。
llSetRot(llEuler2Rot(<90, 0, 0> * DEG_TO_RAD)); // X軸周囲を90度回転
このオブジェクトに対し、Z軸周囲90度の回転を加える場合どうなるでしょうか。
rotation r1 = llGetRot(); // 現在の角度(=X軸周囲90度回転)
rotation r2 = llEuler2Rot(< 0, 0,90> * DEG_TO_RAD); // Z軸周囲を90度回転
llSetRot(r1 * r2);
この場合、まずr1回転が行われた後、r2回転をすることになります。
前にも出した図ですが、以下のようになります。
#ref(rotorder1.jpg)
一方、r1とr2をかける順序を変えるとどうなるかと言うと、
rotation r1 = llGetRot(); // 現在の角度(=X軸周囲90度回転)
rotation r2 = llEuler2Rot(< 0, 0,90> * DEG_TO_RAD); // Z軸周囲を90度回転
llSetRot(r2 * r1);
今度はまずr2回転が行われた後に、r1回転が行われます。
#ref(rotorder2.jpg)
掛け算の順序を変えることで、回転の結果が変わってくるわけですが、これにはどんな意味があるのでしょうか。
*グローバル座標系とローカル座標系
実は掛け算の順序を変えるということは、回転の基準となる軸を変えることと同じです。
3Dの座標系ではX,Y,Zとお馴染みの3つの軸があるわけですが、座標軸には基準によって種類があります。
最もイメージしやすいのはグローバル座標系です。
これはセカンドライフの全世界共通の座標軸で、X軸は西向き、Y軸は北向き、そしてZ軸は空を向いています。
ワールド座標系、とも言われますが、回転においては同一の意味です。
通常、セカンドライフでビルドを行う時、回転は全てグローバル座標を基準に指定します。
この座標系はオブジェクトがどのように回転しようとも、軸そものもが変化することはありません。
RLで喩えるなら、私が右を向こうが左を向こうが、はたまた逆立ちしようが、東は東ですし北は北です。
これがグローバル座標系です。
これに対してローカル座標系というものが存在します。
ローカル座標系はオブジェクト基準の座標系で、オブジェクト自体が回転すると向きが変わる座標系です。
人間で喩えるなら、ローカル座標系は前後・左右・頭足で表現される軸のことです。
私が向きを変えると、前方向は変化します。
寝転がったら頭の方向も変わりますよね。
セカンドライフのローカル座標系では、Xは前方向、Yは左方向、Zは頭の方向になっています。
これがローカル座標系です。
話を戻して、先ほどの回転に関して、グローバル座標軸とローカル座標軸がどのようになっているか見てみます。
まず、r2を適用する前のオブジェクトはX軸周囲を90度回転しています(下図の一番左の状態)。
#ref(rotorder3.jpg)
グローバル座標系はオブジェクトが回転しても向きが変わりません。
これに対してローカル座標系はオブジェクトの回転にあわせて向きが変わります。
ローカルのY軸とZ軸の向きが変わっていますね(ローカル座標軸は薄い色で示してあります)。
上図の真ん中は、r1 * r2の場合の回転です。
グローバルのZ軸に対して回転していることがわかります。
上図の一番右が、r2 * r1の場合です。
こちらはローカルのZ軸に対して回転していることになります。
つまり話をまとめると、
あるオブジェクトの回転がr1のとき、
llSetRot(r1 * r2)はグローバル座標を基準としてオブジェクトを回転させる。
llSetRot(r2 * r1)はローカル座標を基準としてオブジェクトを回転させる。
ということになります。
これは回転を扱う際には重要な考え方です。
オブジェクトを回転させるとき、東西南北天地を基準としたいのか、それとも前後左右頭足を基準としたいのかで、計算式が変わるということになります。
*リンクプリムの回転
段々とややこしい世界に突入していきたいと思います(^^;
SLのオブジェクトは複数のprimで構成することが出来ますが、オブジェクトの中核となるprimのことをルートprimと呼び、その他のprimのことを子primとかリンクprimなどと表現するのは、今更説明するまでもないですよね。
ここまでの説明は基本的にオブジェクト全体、またはルートprimについてのお話でした。
今度は一歩踏み込んで、リンクprimの回転について考えてみます。
まずリンクprimの回転についての基本的な考え方ですが、リンクprimはルートprimからの相対的な回転で管理されます。
例えば、ルートprimに対してX軸周囲に90度回転した状態でリンクされているリンクprimがあるとします。
#ref(linkrot01.jpg)
上図左側はルートの回転が0の場合です。
リンクprimはルートの回転に対してX軸周囲90度になっています。
このオブジェクト全体をZ軸周囲90度回転させたのが右側の図です。
ルートの回転はZ軸周囲90度になっています。
リンクprimのグローバル軸に対する回転は<0,90,90>ですが、ルートprimに対する回転は<90,0,0>で変化なしです。
リンクprimのルートprimに対する回転のことを「ローカル回転」と呼びます。
前節で説明した通り、オブジェクトの軸のことを「ローカル軸」と言いますので、「ローカル軸」を基準とした回転のことを「ローカル回転」と表現するんですね。
LSLではローカル回転を扱う関数が用意されています。
rotation llGetLocalRot() // ローカル回転の取得
llSetLocalRot(rotation rot) // ローカル回転の設定
この二つの関数は、スクリプトが格納されているprimのローカル回転を取得/設定します。
つまり先ほどの図の例で言うと、小さなリンクprimにスクリプトを格納し、その中で[[llGetLocalRot]]関数を実行すると、X軸周囲90度回転のrotation値が取得できるということになります。
[[llSetLocalRot]]関数のほうはprimのローカル回転角度を設定する関数ですが、[[llSetRot]]関数と同様、「primのローカル回転角度をXXXにする」働きがあります。
「現在のローカル回転角度からさらに動かす」わけではありませんので注意して下さい。
「現在のローカル回転角度からさらに動かす」ようにしたい場合は、「現在の角度」と「新たに動かしたい角度」を組み合わせて指定しなければいけません。
ということは、[[llSetRot]]関数の時とまったく同じ問題が起きるということです。
掛け算の順序によって、回転の基準軸が変わる問題です。
[[llSetRot]]関数では、
llSetRot(現在角度 * 追加角度)はグローバル座標を基準としてオブジェクトを回転させる。
llSetRot(追加角度 * 現在角度)はローカル座標を基準としてオブジェクトを回転させる。
このような動きでした。
[[llSetLocalRot]]関数ではどうなるのか、具体的に見てみます。
あるリンクprimがローカル回転<0,0,90>でルートprimにリンクされているとします。
#ref(localrot01.jpg)
リンクprimの赤・緑の面がルートprimとはズレています。
ルートprimのZ軸を基準として90度回転していますので、リンクprimのX面は画像の右奥方向になっています。
このリンクprimに対して、さらにX軸基準に90度回転する処理を行います。
まずは以下のように「現在角度x追加角度」で処理を行った場合です。
rotation r1 = llGetLocalRot(); // 現在のローカル角度(=Z軸周囲90度回転)
rotation r2 = llEuler2Rot(<45, 0, 0> * DEG_TO_RAD); // X軸周囲を45度回転
llSetLocalRot(r1 * r2); // 現在角度x追加角度
#ref(localrot03.jpg)
ルートprimのX軸(赤い面)に対して回転していることがわかります。
これはつまりローカル軸基準の回転ということです。
次に掛け算の順序を変えて、「追加角度x現在角度」で処理を行います。
rotation r1 = llGetLocalRot(); // 現在のローカル角度(=Z軸周囲90度回転)
rotation r2 = llEuler2Rot(<45, 0, 0> * DEG_TO_RAD); // X軸周囲を45度回転
llSetLocalRot(r2 * r1); // 追加角度x現在角度
#ref(localrot04.jpg)
今度はリンクprimのX軸(赤い面)に対して回転しています。
[[llSetRot]]関数のときと同じような動きですが、基準となる軸が違うことに注意して下さい。
[[llSetLocalRot]]関数を使った「追加角度x現在角度」は、リンクprim自身の軸を基準とした回転です。
まとめると、リンクprimに対する[[llSetLocalRot]]関数は、
llSetLocalRot(現在角度 * 追加角度)はルートprimのローカル軸を基準としてオブジェクトを回転させる。
llSetLocalRot(追加角度 * 現在角度)はリンクprimのローカル軸を基準としてオブジェクトを回転させる。
使い方を間違うと思いもよらない回転になりますので覚えておきましょう。
ところで、[[llGetLocalRot]]関数や[[llSetLocalRot]]関数をルートprimに対して使った場合はどうなるでしょうか。
[[llGetLocalRot]]関数が「ローカル軸に対する回転を取得」する関数だということを考えると、ルートprimに対する[[llGetLocalRot]]関数は常に<0,0,0>を返してくれるのが筋のように思えます。
しかしながら、ルートprimに対して[[llGetLocalRot]]関数を実行した時は、例外的にグローバル回転角度が返ります。
同様に、[[llSetLocalRot]]関数をルートprimに対して使った場合はグローバル回転角度の設定になります。
ということは言い換えるなら、
llGetLocalRot関数は、primの親軸(ルートならグローバル軸、リンクならルートの軸)基準の回転を返す
llSetLocalRot関数は、primの親軸(ルートならグローバル軸、リンクならルートの軸)基準の回転を設定する
と言えます。
さて。
この時点ですでにだいぶややこしくなってきていますが、まだまだややこしい世界には先があります(^^;
お楽しみにw
----
(つづく)
----
- 移動速度を検出して回転速度を変化させるスクリプトを作ったのですが、前進時の回転までしかできませんでした>w<;;; -- Backard Wylie (2008-04-23 14:30:33)
- エンターキー押しちゃったw ので、、、書き直し^^; default&173;state_entry()&173;llSetTimerEvent(0.20);&175;timer()&173;vector vel = llGetVel();float speed = llVecMag(vel);llTargetOmega(<1.0,0.0,0.0>,speed,1);&175; これを元に前進時の回転と行進時の回転を検出して自動で回転方向を切り替えてくれるスクリプトって作れるのかな~? -- Backard Wylie (2008-04-23 14:34:32)
- 文字化けしてますね^^; 消しといて下さい>w<;;; LsL-BBSに投稿したので、そちらで>< -- Backard Wylie (2008-04-23 14:37:02)
- こんなに決まり事があったのですね・・・わかりやすく説明されているので助かります。 -- くるじん (2008-11-07 18:48:39)
- ついに次回はベクトルの回転と四元数の使い方になるわけですねw -- 回転の国の王子様 (2009-03-27 10:58:03)
- いつも、わかり易い解説有難うございます。非常に参考になっています。 ところで、「BlackSheep-LSL@Wiki 回転について」のページについてですが、「グローバル座標系とローカル座標系」のところの、 > これはセカンドライフの全世界共通の座標軸で、X軸は西向き、Y軸は北向き、そしてZ軸は空を向いています。 のところの、「X軸は西向き」 ---> 「X軸は東向き」ではないでしょうか。 -- Xpyoda Janus (2019-02-16 11:41:30)
#comment
2019-02-16T11:41:30+09:00
1550284890
-
トップ
https://w.atwiki.jp/mizcremorne/pages/56.html
*このWikiについて
BlackSheep-LSL@wikiは[[Makapu改めBlackSheep-LSL@SLMame>http://miz.slmame.com/]]の移転サイトです。
セカンドライフのスクリプト(LSL)について解説するwikiです。
特に「何かやってみたいのだけれども、何をどうしていいのか」わからない方に向けて、なるべく初歩的なところからLSLについて説明を試みています。
LSLの考え方や作り方を理解し、「面白いな~」と思った方がさらに高度なものを作り出すための足がかりにでもなれば幸いです。
なお、掲載しているスクリプトや情報については、素人仕事ですので、記述ミスや勘違いなどが無いとは言えません。
間違いに気づいた方はご指摘下さると助かります。
また、特に断りがない限りは掲載のコードはフリーのサンプルです。
自由に利用して実用化・商品化していただいて構いませんが、動作を保証するものではありませんので十分にテスト等行って下さい。
リンク等はご自由になさって結構です。
トラックバックについてもスクリプト利用例や実際に試してみた結果レポート等、基本的に歓迎しますが、あまりにも記事の内容と関連のないものはご遠慮下さい。
特に単なる集客目的で言及リンクもないようなトラックバックについては削除する場合があります。
LSLについてご質問がある場合は[[BBS>http://bb2.atbb.jp/lslbbs/index.php]]をご利用下さい。
このwiki専用のBBSというわけではありませんので、記事に無関係な質問でも構いません。
しかしながら私が「そのまま動くサンプルコードを教える」ことは稀だと思ってください(^^;
最初の段階で私が回答するのは「考え方」「仕組み」である場合がほとんどです。
コードを提示するのは可能ですが、重要なのは「考え方」「仕組み」を理解することであり、そこが理解できなければ、いつまで経っても自分でスクリプトを組むことは出来ません。
サンプルコードをそのまま貼り付けて動かしても、自分で作った充実感は無いはずです。
やはり自力で作り上げる喜びを味わっていただきたいので(^^
*主なコンテンツ
各コンテンツへは左メニューよりお進み下さい。
・[[基礎知識]]・・・LSLの基礎的な概念・用語等の説明
・[[初級スクリプト>スクリプト初級]]・・・実践的な例を挙げながらLSLの基本を解説した記事
・[[リファレンス>リファレンス(名前順)]]・・・LSLの関数及びイベントの辞典
・[[スクリプト小技]]・・・中級者向けのちょっとしたノウハウ記事
----
- makapuに頼りっきりですので大変助かります。 -- jackfield (2007-09-12 16:09:19)
- 素敵です!またしてもみんなの役に立ちそう。メニューもわかりやすいです。 -- neron (2007-09-21 23:22:59)
- 助かりました。ホントにありがたいです。 -- Rios (2007-12-05 01:24:24)
- 初めまして。解説サイトは助かります。これからもヨロシクお願いします -- 名無しさん (2008-10-13 18:13:55)
- 初めまして。ありきたりな言葉ですが、必要なことが必要な分纏まっていてとても助かります。 -- 名無しさん (2009-03-31 16:24:56)
- 初めまして。とても分かりやすいです。これからも、ヨロシクお願いします -- aripot (2012-08-01 14:12:12)
- いつもいつもお世話になってます^^ -- 名無しさん (2013-06-17 19:41:04)
#comment
2013-06-17T19:41:04+09:00
1371465664
-
初!スクリプト
https://w.atwiki.jp/mizcremorne/pages/287.html
#contents
*はじめに
前回はスクリプトの構成について説明しました。
「状態(ステート)」
「きっかけ(イベント)」
「反応」
の3つで構成されているという点は基礎中の基礎ですので踏まえておいて下さい。
今回は実際にSLの中でスクリプトを動かしてみたいと思います。
だらだらと説明ばかりでは飽きるでしょうし(^^;
*スクリプトの作成
SLにログインし、実際に作ってみましょう。
スクリプトの作り方は、厳密には2つの方法があります。
primのコンテンツ内に作る方法と、アバターのインベントリ内に作る方法の二種類ですが、たいていの場合はprimのコンテンツ内に作成します。
何故ならスクリプトはprimのコンテンツ内に置かないと作動しないためです。
汎用的なスクリプトを作る場合などは、アバターのインベントリにスクリプトを作ったりもしますが、最初のうちはそういうこともあまりないでしょう。
適当にprimを作って下さい。
primの作り方がわからないという方は、「SL prim 初めて 作成」でGoogle検索して下さい(^^;
SecondLife Wiki JPのアイボリータワー翻訳プロジェクトなどがとても参考になると思います。
primを作ったら、editウインドウの下側に並んでいるタブの一番右、「コンテンツ」のタブをクリックします(editウインドウの下半分が表示されてない人は「more」ボタンで表示されます)。
「コンテンツ」タブには、primの中に入っているものの一覧が表示されています。
最初は「contents」というフォルダがあるのみで、空っぽになっているはずです。
この画面で「New Script」のボタンを押すと、新しいスクリプトが作成されます。
「New Script」という名称でコンテンツの中に表示されますので、今度は作られたスクリプトをダブルクリックします。
するとスクリプト編集用のウインドウが開き、なにやらコードが書かれているのが見れます。
以下のようなコードが表示されるところまで実際に試してみて下さい。
default
{
state_entry()
{
llSay(0, "Hello, Avatar!");
}
touch_start(integer total_number)
{
llSay(0, "Touched.");
}
}
*ハロー・アバター・スクリプト
作成されたスクリプトは、ハロー・アバター・スクリプトと呼ばれる初期スクリプトです。
前回のスクリプト構造についての話を思い出して、このスクリプトの構造を読み取って見ましょう。
default ・・・状態(ステート)
{
state_entry() ・・・きっかけ(イベント)
{
llSay(0, "Hello, Avatar!"); ・・・反応
}
touch_start(integer total_number) ・・・きっかけ(イベント)
{
llSay(0, "Touched."); ・・・反応
}
}
読み取れたでしょうか。
「状態(ステート)」、「きっかけ(イベント)」、「反応」。
どこの部分が何に当たるのかをまずつかんで下さい。
それがわかると次には、
「defaultってのが状態なのはわかったけど、一体何のことやら?」
という疑問がわいてくるかと思います。
それぞれの英語の構文の意味について、簡単に説明しましょう。
・状態(ステート)
このスクリプトには状態(ステート)が一つしかありません。
defaultステートです。
このステートは特別なステートで、いわゆる「初期状態」です。
スクリプトは複数のステートを持つことができますが、このdefaultステートを無くすことはできません。
またdefaultステートは、スクリプトが開始されるときの最初のステートです。
・きっかけ(イベント)
きっかけ(イベント)は二つあります。
[[state_entry]]()というのと、[[touch_start]](integer total_number)です。
この二つは最も基本的なイベントです。
[[state_entry]]()は、ステートにエントリーしたとき、つまり「この状態になったとき」に実行されます。
[[touch_start]]()は、タッチをスタートしたとき、つまり「タッチしたとき」に実行されます。
・反応
それぞれのイベントに対して反応が書かれています。
このスクリプトではどちらも、[[llSay]](0, "ほげほげ")という反応になっています。
[[llSay]]()は、「言う」すなわち"ほげほげ"の部分に書かれた内容をオブジェクトが発言する命令です。
さて、ここまでわかると、このスクリプトの動きがわかるはずです。
ステート、イベント、そして反応を踏まえながら考えてみましょう。
まずステートについては一つしかないので悩むことは何もありません。
このスクリプトは初期状態から変化することがなく、初期状態のまま完結しています。
イベントは2つ。
「この状態になったとき」と「タッチしたとき」です。
「この状態になったとき」の「この状態」とは何かと言うと、当然defaultステートのことです。
つまりこのイベントは、「defaultステートになったとき」のことです。
このスクリプトは初期状態のdefaultステートしかありませんから、このイベントが発生するのは「一番最初にスクリプトが動いた時」だけです。
そのときにどんな反応をするのか。
[[llSay]](0, "Hello, Avatar!");と書いてあります。
この命令を正確に説明すると、
「チャットチャンネル0番で"Hello, Avatar!"と発言する」
ということになります。
チャットチャンネルについてはとりあえず省略しますが、0番チャンネルでの発言というのは通常チャットと同じ発言になるということだけ知っておいて下さい。
イベントと反応をあわせて考えると、
「一番最初にスクリプトが動いた時」>「"Hello, Avatar!"と発言する」
ということになります。
次のイベントも見てみましょう。
「タッチしたとき」のほうは、文字通りアバターがオブジェクトをタッチしたときのことです。
[[touch_start]]()の()の中に書いてある「integer total_number」については今は気にしなくていいです。
こういう風に書くんだと覚えておきましょう。
反応は先ほどと同じく[[llSay]]です。
今度は[[llSay]](0, "Touched.");と書いてありますので、
「チャットチャンネル0番で"Touched."と発言する」
ということになります。
つまり2番目のイベントと反応は、
「アバターにタッチされたとき」>「"Touched."と発言する」
ということです。
*本当にそうなってるのか?
スクリプトがどういう動作をするのかわかったところで、実際に試してみましょう。
まずは、スクリプトの編集ウインドウの下のほうにある、「リセット」のボタンを押してください。
このボタンを押すとスクリプトがリセットされます。
リセットされるということは、初期状態に戻りますので、一番目に書いてあったイベント、
「一番最初にスクリプトが動いた時」
が発動するはずです。
このイベントが発動すると、
「"Hello, Avatar!"と発言する」
はずですので、チャット欄をよく見ておきましょう。
リセットしたときに"Hello, Avatar!"とオブジェクトが言っていれば予想通りの動きです。
次に、オブジェクトにタッチしてみます。
editウインドウが開いたままだとうまくタッチできませんので、一度editモードを終了して下さい。
それからオブジェクトにタッチしてみます。
2番目に書いてあったイベントの「アバターにタッチされたとき」が発動して、「"Touched."と発言する」はずです。
タッチしたときに"Touched."とオブジェクトが言っていることを確認しましょう。
今回は実際にスクリプトのコードを見て、その構造を理解し、どんな動きをするのか考えてみました。
フリーのスクリプトなどを入手する機会もあるかと思いますが、その動きを理解するときも同様の思考手順を踏みます。
さて、具体的にスクリプトを作る方法がわかりましたので、次回はオリジナルのスクリプトを作成するところまで進んでみたいと思います。
何かご意見・ご感想などありましたら遠慮なくコメントして下さいませ(^^
……的外れじゃないかと不安でたまらなくなってくるのでw
----
- とても分かりやすいですね。 -- チョコ (2011-12-12 22:46:05)
#comment
2011-12-12T22:46:05+09:00
1323697565
-
鍵をかける
https://w.atwiki.jp/mizcremorne/pages/311.html
#contents
*はじめに
やもさんより質問いただいたスクリプトについて解説です。
[やもさんからの質問]
最近自分で服を作るようになり試着の為の更衣室を用意したのですが
ドアに鍵をかけるか、自分にしか開けられないドアするにはどのようにしたら良いのでしょうか?
これって結構ニーズのある話だと思います。
着替えの最中は裸になることも多く、やはり人に見られたくない場合がありますので・・・。
また、Kirさんからも関連のある質問をいただきました。
[Kirさんからの質問]
なんとか、オーナー限定にて動作するように改良したいのですが、
if (llDetectedKey(0) == llGetOwner()){
handle=llListen(channel, "", llGetOwner(), "");
これでしょうか?
あわせて見ていきましょう。
*鍵をかけることの意味
実際には「人に見られることのない場所」を作るのは結構大変だったりします。
というのも、SLではかなり自由なカメラのコントロールが利きますので、建物をALT押しながら左クリックしてドラッグすると、簡単に内部を覗けてしまうんですね(^^;
たとえドアに鍵をかけたとしても、これを防ぐことはできません。
覗き見だけでなく、内部に侵入することも可能です。
カメラを動かして建物内部を見て、中の家具などにsitすると、強引に室内に押し入ることができたりします。
これを防ぐにはsitしたときの強制排除の仕組みを建物や家具に組み込んでおくしかありません。
[[llSitTaeget]]でsit位置を屋外に設定してしまうとかですね(^^;
完璧なセキュリティを考え始めると、かなり頭を使うことになります。
どこかのSIMに、セキュリティを売りにしているラブホテルがあるとか聞きましたが、そこも相当工夫をしているはずです。
現物を見てないのでどこまでセキュリティを実現しているのかはわかりませんが・・・。
個人的には「絶対に誰にも覗かれない場所」というのは不可能に近いと思っています。
コンピュータの世界に「完璧なセキュリティ」が存在しないのと一緒です。
あくまでも「悪意を持って故意に覗こうとしない限りは、覗かれずに済む」程度のプライバシーであれば確保できます。
以上のようなことを前提とした上で、他の人には開けられないドア及び鍵のかかるドアについて見てみましょう。
**操作できる人を限定する方法
自分にしか開けられないドアを作るのは簡単です。
前にタッチして開くドアを作りましたが、そのスクリプトのタッチイベントにコードを追加し、オーナーがタッチしたときだけ反応するようにすればOKです。
rotation rot;
vector open_rot = <0.0, 0.0, 90.0>;
default {
state_entry()
{
rot = llGetRot();
state close;
}
}
state open {
state_entry()
{
llSetRot(rot * llEuler2Rot(open_rot * DEG_TO_RAD));
llSetTimerEvent(30.0);
}
touch_start(integer total_number){
if (llDetectedKey(0) == llGetOwner()) { // タッチしたのがオーナーの場合のみ
state close;
}
}
timer(){
llSetTimerEvent(0);
state close;
}
}
state close {
state_entry(){
llSetRot(rot);
}
touch_start(integer total_number){
if (llDetectedKey(0) == llGetOwner()) { // タッチしたのがオーナーの場合のみ
state open;
}
}
}
[[llDetectedKey]](0)は「タッチした人のUUID」を得る関数です。
[[llGetOwner]]()のほうは「オーナーのUUID」を得る関数ですので、これが等しい場合だけ反応するようにしておけば、オーナー以外には操作ができません。
ドアに限らず、「オーナーがタッチしたときにだけ動作するスクリプト」は、
touch_start(integer total_number){
if (llDetectedKey(0) == llGetOwner()) {
// オーナーがタッチしたときの処理
} else {
// オーナー以外がタッチしたときの処理
}
}
このような書き方になります。
しばしば使う表現ですので、これはもう丸暗記でもいいかと思います。
この手法をもうちょい応用してみましょう。
オーナーだけでなく、同じグループの人が操作できるようにするには?
touch_start(integer total_number){
if (llDetectedGroup(0) ) {
// オブジェクトと同じグループの人がタッチした場合の処理
} else {
// オブジェクトと同じグループ以外の人がタッチした場合の処理
}
}
[[llDetectedGroup]]関数は、オブジェクトのグループと同じアクティブグループかどうかを調べる関数です。
さらに応用して、スクリプトに操作を許可する人の名前を定義しておいて、その人以外には使えないようにするには、
list agents=["Miz Cremorne", "Hoge Hogera", "Honya Honyara"];
rotation rot;
vector open_rot = <0.0, 0.0, 90.0>;
(中略)
state open {
(中略)
touch_start(integer total_number){
if (llListFindList(agents, [llDetectedName(0)]) != -1) { // リストagentsに名前のある人のみ
state close;
}
}
(以下略)
list型変数agentsに、タッチを許可する人の名前を列挙しておきます。
[[llListFindList]]関数はリストの中から指定した要素を探してそのインデックスを返す関数ですが、リストに無かった場合は-1を返します。
つまりリストに名前があるなら、-1以外の数字が返ってきます。
ですので-1以外が返ってきた場合だけ、動作させればOKですね。
ここのところを逆に-1のときだけ動作するようにすると、
if (llListFindList(agents, [llDetectedName(0)]) == -1) { // リストagentsに名前のない人のみ
// 処理
}
BANのように、リストに名前のある人には動かすことのできないスクリプトになります。
**鍵をかける
鍵をかける仕組みは「操作できる人を限定する方法」の応用に過ぎません。
上で紹介したものはどれも「操作できる人」をあらかじめ限定していましたが、ロックしていないときは誰でも操作できて、ロックしたときだけ「操作できる人」が限定されれば良いですね。
rotation rot;
vector open_rot = <0.0, 0.0, 90.0>;
key lock=NULL_KEY;
integer counter=0;
default {
state_entry()
{
rot = llGetRot();
state close;
}
}
state open {
state_entry()
{
llSetRot(rot * llEuler2Rot(open_rot * DEG_TO_RAD));
llSetTimerEvent(30.0);
lock = NULL_KEY; // ロック解除
}
touch_start(integer total_number){
state close;
}
timer(){
llSetTimerEvent(0);
state close;
}
}
state close {
state_entry(){
llSetRot(rot);
}
touch_start(integer detected){
counter=0;
}
touch(integer detected){
if (llDetectedKey(0) == llGetOwner()){ // ロックできるのはオーナーだけ
if (counter < 50){
counter ++;
}else if (counter == 50){
counter ++;
if (lock == NULL_KEY){
lock = llDetectedKey(0); // ロック
} else {
lock = NULL_KEY; // アンロック
}
}
}
}
touch_end(integer total_number){
if (lock == NULL_KEY){ // ロックがかかっていない場合
state open;
} else if (llDetectedKey(0) == lock) { // ロックをかけた人がタッチした場合
if (counter < 50){
state open;
}
}
}
}
ステートopenのときには誰もが操作可能です。開いてるのですから、閉じるのはいくらでもできるわけです。
ステートcloseのときにロックの仕組みを追加しました。
しばらくドアをクリックしっ放しにするとロックがかかります。
[[touch]]イベントを使って「しばらくマウスボタンを押し続けたとき」の処理の仕方は色の変わるカツラを作ったときに使いました。
今回は「しばらくマウスボタンを押し続けたとき」に、ロックがかかっていなければロックし、ロックがかかっていればアンロックしています。
ロックがかかっているかどうかはkey型変数lockの中身で判断します。
lockがNULL_KEYの場合はロックがかかっていない状態です。
ロックがかかったときには、ロックした人のUUIDが変数lockに入ります。
ドアを開けることができるのは、
(1)ロックがかかっていない場合=lockがNULL_KEY
(2)ロックした人がタッチした場合=lockが[[llDetectedKey]](0)と同じ
の2通りですので、touch_endイベントの中でそのように判断しています。
このスクリプトではロックできるのはオーナーだけにしていますが、[[touch]]イベントの中の最初のif文に手を加えてやれば、グループメンバーが自由にロック/アンロックできるドアなども出来ます。
*強制排除スクリプト
ついでに、sitによる侵入を防ぐコードも考えてみます。
せっかくドアには鍵かかかっているのに、sitで侵入されては無念ですので(^^;
vector exit_pos = <128.0, 128.0, 128.0>;
default {
state_entry(){
vector p = exit_pos - llGetPos();
p /= llGetRot();
llSitTarget(p, ZERO_ROTATION);
}
on_rez(integer parm){
llResetScript();
}
moving_end(){
llResetScript();
}
changed(integer change){
if (change & CHANGED_LINK){
key avatar = llAvatarOnSitTarget();
if (avatar != NULL_KEY){
llUnSit(avatar);
}
}
}
}
変数exit_posには強制退去される先の座標を設定します。
上記のコードでしたら、sitした人は全員<128.0, 128.0, 128.0>へ飛ばされます。
ただし飛ばせる距離は300m程度ですので、あまり離れたところに退去させるのは無理です。
オブジェクトがrezされたとき、あるいは動かされたときにはスクリプトをリセットし、退去位置を再設定します。
誰かがこのスクリプトの入ったオブジェクトに座ると、退去位置に移動しますので、即座に[[llUnSit]]で排除します。
このコードは全primに入れておく必要はありません。
例えば建物がリンクされているのであれば、ルートにさえ入れておくだけで済みます。
----
- 親切丁寧な解説で、初心者の私にも解りやすく、ありがたく思っています。しかし、強制排除スクリプトi -- Zephyr Whitfield (2011-05-15 21:33:04)
- 投稿ミスをしました。済みません。もう一度書き直します。親切丁寧な解説で、初心者の私にも解りやすく、ありがたく思っています。しかし、強制排除スクリプトにllDetectedKey()を使用して10m以内に近付いたAvatarを排除するスクリプトを作ろうとしましたが、その組み合わせ方が分かりません。サンプルを掲載して頂ければ嬉しいのですが、お願いできませんでしょうか? m(_ _)m -- Zephyr Whitfield (2011-05-15 21:39:05)
#comment
2011-05-15T21:39:05+09:00
1305463145
-
llParseString2List
https://w.atwiki.jp/mizcremorne/pages/299.html
*llParseString2List
list llParseString2List(string src, list separators, list spacers)
----
文字列srcをseparators/spacersを区切りとしてリスト型データに展開する。
separatorsとspacersの違いは、区切り文字自体を結果のリストに含めるかどうかである。
separatorsは結果リストに含まれず、spacersは含まれる。
具体的に見るのが手っ取り早いと思うのでいくつか例を示す。
llParseString2List("A,B,C,D,E", [","], []);
結果:["A","B","C","D","E"];
llParseString2List("A/B/C/D/E", ["/"], []);
結果:["A","B","C","D","E"];
llParseString2List("A,B/C,D/E", ["/",","], []);
結果:["A","B","C","D","E"];
llParseString2List("A:B::C:D::", [":"], []);
結果:["A","B","C","D"];
llParseString2List("I am a pen.", [" "], []);
結果:["I","am","a","pen."];
llParseString2List("A1B2C3", ["1","2","3"], []); // セパレータ
結果:["A","B","C"]; // セパレータは結果に含まれない
llParseString2List("A1B2C3", [], ["1","2","3"]); // スペーサー
結果:["A","1","B","2","C","3"]; // スペーサーは結果に含まれる
[[llCSV2List]]関数はllParseString2List(src, [","], [])と同等である。
また、リストから逆に文字列への変換には[[llDumpList2String]]関数を使用する。
----
- 実験したところ、llCSV2List(src)とllParseString2List(src, [","], [])は若干動作が違うようです。CSV2Listの場合だけ、元の文字列にカンマの連続があった場合でも間に要素があるとみなされます。 -- RoyalTurkey Markstein (2009-07-06 04:47:37)
- 1年前のコメントに補足ですw つまりllCSV2List(src)の場合は、llParseStringKeepNulls(src,[","],[])を使えばまったく同じ結果になりますね。 http://www21.atwiki.jp/mizcremorne/pages/300.html -- RoyalTurkey Markstein (2010-07-30 22:52:34)
#comment
2010-07-30T22:52:34+09:00
1280497954
-
llGetGMTclock
https://w.atwiki.jp/mizcremorne/pages/165.html
*llGetGMTclock
float llGetGMTclock()
----
グリニッジ標準時(GMT)0時からの秒数を返す。
日本の標準時刻はGMT+9時間なので、
llGetGMTclock() + 9 * 3600
とすると日本時間を計算することができる(3600 = 1時間の秒数)。
integer jp_time = llGetGMTclock() + 9 * 3600;
時:(integer)((jp_time / 3600) % 24)
分:(integer)((jp_time % 3600) / 60)
秒:(integer)(jp_time % 60)
----
- あれ?\ninteger jp_time = llGetGMTclock() + 9 * 3600;じゃなくてn\nfloat jp_time = llGetGMTclock() + 9 * 3600;じゃ…?? -- 名無しさん (2009-12-20 16:55:52)
#comment
2009-12-20T16:55:52+09:00
1261295752
-
llRegionSay
https://w.atwiki.jp/mizcremorne/pages/320.html
*llRegionSay
llRegionSay(integer channel, string text)
----
土地全域に聞こえるチャットメッセージtextをチャンネルchannelで送信する。
LSLではチャットメッセージを[[listen]]して処理を行うことが多く、不用意にllRegionSayを多用すると負荷が高くなる可能性がある。
チャットメッセージの送信範囲は必要に応じて使い分けるべき。
他のチャット関連関数も参照のこと。
[[llWhisper]]・・・範囲10m
[[llSay]]・・・範囲20m
[[llShout]]・・・範囲100m
[[llOwnerSay]]・・・オーナーにのみ送信
[[llInstantMessage]]・・・特定アバターのみ(IM)
[[llMessageLinked]]・・・リンクprim間の通信。listenに影響なし。
----
- チャンネル0(パブリックチャンネル)は使えないことを記述してはどうでしょうか -- 名無しさん (2009-10-06 09:46:38)
#comment
2009-10-06T09:46:38+09:00
1254789998
-
llGetInventoryNumber
https://w.atwiki.jp/mizcremorne/pages/169.html
*llGetInventoryNumber
integer llGetInventoryNumber(integer type)
----
指定したtypeのアイテムがコンテンツ内にいくつあるかを返す。
アイテムのタイプには以下の値を使用する。
|定数|値|説明|
|INVENTORY_ALL |-1 |全種類|
|INVENTORY_ANIMATION |20 |アニメーション|
|INVENTORY_BODYPART |13 |ボディパーツ|
|INVENTORY_CLOTHING |5 |服|
|INVENTORY_GESTURE |21 |ジェスチャー|
|INVENTORY_LANDMARK |3 |ランドマーク|
|INVENTORY_NOTECARD |7 |ノートカード|
|INVENTORY_OBJECT |6 |オブジェクト|
|INVENTORY_SCRIPT |10 |スクリプト|
|INVENTORY_SOUND |1 |サウンド|
|INVENTORY_TEXTURE |0 |テクスチャ|
例えば、以下の例はコンテンツに含まれるアイテムの数を返す。
default {
state_entry(){
integer item_number = llGetInventoryNumber(INVENTORY_ALL);
if (item_number == 0){
llSay(0, "There is no item.");
}else{
llSay(0, "There is " + (string)item_number + " items.");
}
}
}
----
- この例では、コンテンツを数えるスクリプト自身を数えるから、item_number==0は機能しないですよね・・・・。 -- 通りすがっちゃう人 (2009-07-15 18:59:22)
#comment
2009-07-15T18:59:22+09:00
1247651962
-
llCeil
https://w.atwiki.jp/mizcremorne/pages/97.html
*llCeil
integer llCeil(float val)
----
引数val以上の最も小さい整数を返す。
つまり小数以下を切り上げる。
llCeil(1.5) → 2
llCeil(0.1) → 1
llCeil(4.02) → 5
llCeil(2.82) → 3
小数以下の切り捨てには[[llFloor]]、四捨五入には[[llRound]]を使う。
----
#comment
2009-06-07T14:12:25+09:00
1244351545
-
基礎知識/変数とは?
https://w.atwiki.jp/mizcremorne/pages/73.html
*変数
「変数」とは、しばしばプログラミング解説書では「データを入れておく箱のようなもの」と説明されます。
プログラミングに特有の概念ですので、今までプログラムなんて見たことも聞いたことも食べたこともない人にとっては意味不明な存在でしょう。
まずは喩えを使って簡単に変数について説明してみたいと思います。
子供にお使いを頼むことを考えてみて下さい。
「肉屋さんへ行って、このメモに書いてあるものを買ってきて」
こんな指示を出したとしましょう。このときの「メモ」というのが変数に当たります。
メモには、「ひき肉」と書いてあるかもしれないし、「レバ刺し」と書いてあるかもしれません。
意表をついて「カボチャ」とか書いてある可能性だってあります・・・。
同じメモでも、書いてある内容によっていろいろと意味が変わってくるように、変数というのは中にいろいろなデータを書き込んでおくことができます。
中身が変わるから「変」数というわけです。
もちろん、メモを渡さずに具体的な品物を指示することもできます。
「肉屋さんへ行って、ひき肉を買ってきて」
「肉屋さんへ行って、レバ刺しを買ってきて」
この二つの指示でも問題はありませんが、いろいろなものを書ける「変数」=メモがあるなら、
「肉屋さんへ行って、このメモに書いてあるものを買ってきて」
指示はこの一つだけで良いのです。
あとは買ってきて欲しいものに応じてメモの中身を書き換えてやるだけです。
ひき肉だろうとレバ刺しだろうと、カボチャだろうと牛乳だろうと、あくまでも指示は、
「肉屋さんへ行って、このメモに書いてあるものを買ってきて」
この一つだけで済みます。これが変数を使うメリットです。
**変数の種類
さて、メモであればそこに何を書いてもかまわないのですが、LSLにおける変数の場合はそこまで便利ではありません。
変数の場合は中に書けるデータの種類が決まっています。
どういうことかと言うと、
「肉屋さんへ行って、このメモに書いてあるものを買ってきて」
この指示の場合、実際にはメモに書くのは「肉屋に売っているもの」でなければ任務を遂行することが出来ません。
指示を受けるのが人間であれば、メモを開いて、
「ニンジン」
とか書いてあったら、肉屋ではなく八百屋に行くくらいの機転が利きますが、コンピュータの場合はそうはいきません。
あくまでも肉屋に出かけていき、
「ニンジン下さい・・・」
肉屋の親父に哀れみの目で見つめられながらも、そう繰り返す他ありません。
コンピュータをそんな可哀想な状況に追いやらないために、変数には種類があるのです。
もちろん、「種類」と言っても、「肉」とか「野菜」とかではありません。
例えば、「種類」の一つには「整数」があります。
0とか1とか15321とか-798とか、小数部の無い数字です。
「整数」用の変数には、整数しか入れることができません。
また、「文字」を扱う変数もあります。
このタイプの変数には「文字」しか入れられません。
種類によって中身のタイプが決まっているので、コンピュータは正しく指示を実行できるのです。
どういうことかと言うと、例えば、LSLには「お金を支払う」という動作があります。
この動作を実行するときには「整数」タイプのデータを指示してやらなければいけません。
つまり、
「このメモに書いてある金額を支払いなさい」
という指示だということです。
指示できるのは「整数」タイプのデータだけですので、メモに書けるのは「整数」だけということになります。
もしもこの「メモ」に、整数以外のものが書けたとしたら・・・。
「"豚肉"を支払いなさい」
とか
「150.57L$を支払いなさい」
などの無茶な指示が出せますので、コンピュータは途方に暮れてしまいます。
そういうことの無いよう、「変数」には中に入れられるデータのタイプが決まっているのです。
今覚えなくてもいいですが、参考までにデータのタイプを示しておきます。
このタイプのことを、変数の「型」と言います(直訳ですが)。
【型の一覧】
|型名|説明|具体例|
|integer|整数|1,5,8,0,-13など|
|string|文字列|"hoge","huge","hage"など|
|float|小数|1.23, 0.02, -5.24など|
|vector|ベクタ|X,Y,Zの3つの小数値のセット、位置座標などに使用する <1.0, 2.4, -3.5>など|
|rotation|ローテーション|X,Y,Z,Sの4つの小数値のセット、回転のデータ <4.6, -3.2, 4.2, 1.0>など|
|key|キーまたはUUID|SL内のアバター/オブジェクトの識別番号、"66864f3c-e095-d9c8-058d-d6575e6ed1b8"など|
|list|リスト|複数の変数のセット、[1, "hoge", 3.56]など|
これが変数の型の全てです。
変数を使うときには、用途に合わせてどの型を使うか考えなければいけません。
**変数の用意
変数は自由に用意して使うことができますが、使うときにはやはりルールがあります。
何も言わずにいきなり、
「このメモのさぁ・・・」
と言っても、「このメモって・・・どのメモ?」と困惑されてしまいますので、あらかじめどのようなメモがあるのかをコンピュータに伝えておかなければいけません。
これを変数の「宣言」と言います。
変数を使用する前には、必ず「宣言」が必要になります。
つまり、変数を使うときには以下の2段階の手順を踏みます。
「ここに整数が書いてあるメモがあります」・・・宣言
「メモに書いてある金額を支払いなさい」・・・変数の使用
文章にするとなんだか回りくどいですが、このように書いてやらないと理解できないのがコンピュータです。
「宣言」をするときには、以下のように書きます。
integer money = 100;
先頭に書いてあるのは「型」です。
ここではinteger型、つまり整数型です。
次に半角スペースを開けて、変数の名前を書きます。
ここではmoneyという名前にしてみました。
この名前は英数字で自由につけられますが、何のための変数なのか、わかりやすい名前をつけておくと混乱せずに済みます。
「=」は変数に中身を入れるよ、という意味です。一般的な「等しい」ではありませんので注意して下さい。
ここでは、100という数値をmoneyという変数に入れているということになります。
最後の「;」は処理の区切りを意味します。
日本語でいうところの「。」みたいなものです。
「;」が無いと、コンピュータは次の行も一続きだと解釈してしまいます。
文章の区切りを明確にするためのものだと思ってください。
「=」の後ろは省略することもできます。
integer money;
この場合、moneyの中身にはデフォルト値が入ります。
整数型のデフェルト値は0です。
すなわち、
integer money = 0;
こう書くのとまったく同じです。
このようにして宣言をした後は、「money」と出てくれば、
「あぁ、整数が書いてあるメモのことね」
コンピューターはそう理解してくれます。
そして「money」に書いてある数値を自由に読み取ったり、別の数値を書き込んだりできるようになるのです。
**変数の中身
では次に、変数の中身を入れる方法について詳しく見てみましょう。
先ほどすでに、「=」で変数の中身を入れられることは述べました。
この「=」を使って中身を指定することを「代入」と言います。
integer money;
money = 100;
money = -200;
money = 10;
money = 1000;
こんなふうに、変数の中身は好きなだけ書き換えることができます。
あくまでも「書き換え」です。「追加」ではありません。
リスト型以外の変数は、常に一つだけしか値を書いておくことができません。
やたら小さいメモだと思ってください(^^;
新しい値を書こうと思ったら、前の値を消しゴムで消して、新しい値を書かないといけないのです。
上記の例ですと、moneyという変数には最終的に1000という数値が書いてあることになります。
他の型ではどうでしょうか。
例えばfloat型(小数型)の場合ですと、
float speed;
speed = 0.0;
speed = -2.8;
speed = 10.5;
中に入れる数字が小数になるだけで、使い方は一緒です。
では、string型(文字列型)はどうでしょうか。
string name;
name = "taro";
name = "jiro";
name = "saburo";
このようになります。
文字列の場合は、代入するデータを""でくくります。
もしも""を忘れてしまうと、
string name = shiro;
これはnameという変数に、shiroという「変数」の中身を代入する、と解釈されます。
shiroという変数が宣言されていなければ、当然エラーになります。
他の型についても書き方を列挙しておきます。
-ベクタ型:
vector position = <10.5, 1.0, -5.6>;
ベクタ型は3つの小数値を< >でくくります。小数値は「,」で区切られています。
-ローテーション型:
rotation kakudo = <1.4, -3.8, 0.2, 1.0>;
ローテーション型は4つの小数値を< >でくくります。小数値は「,」で区切られています。
ベクタ型とは小数値の数が違うだけです。
-キー/UUID型:
key avatar = "66864f3c-e095-d9c8-058d-d6575e6ed1b8";
キー/UUID型は文字列を同じように、""でくくります。
何やら意味不明の英数字が並んでいますが、これはセカンドライフ内のアバターやオブジェクトを識別するための番号です。
-リスト型:
list colors = ["red", "blue", "green"];
リスト型は[ ]でくくり、中身のデータは「,」で区切られています。
このように、型によって値の指定の仕方が違います。
間違った書き方をすると正しく値を代入することができません。
integer money = "hikiniku"; ・・・整数型に文字列を代入しようとしている
string name = 4.58;・・・文字列型に小数を代入しようとしている
vector positin = <1.4, -3.8, 0.2, 1.0>・・・ベクタ型にローテーション型を代入しようとしている
list items = "book";・・・リスト型に文字列を代入しようとしている
これらはエラーになります。
扱っている変数の型が何なのか、きちんと意識してコードを書く必要があるということですね。
**変数から変数へ
変数の型が同じであれば、変数から変数への代入が可能です。
string name1;
string name2;
name1 = "taro";
name2 = name1;
文字列型の変数、name1とname2を宣言しました。
name1に"taro"を代入します。
そして最後の行、
name2 = name1;
これは「name2にname1の中身を代入する」ということになります。
name1には"taro"が代入されていますので、name2にも同じく"taro"が代入されます。
他の型でも同様のことができます。
integer money1;
integer money2;
money1 = 100;
money2 = money1;
整数型の変数money1とmoney2があって、money1に100という数値を代入します。
さらにmoney2にmoney1の中身を代入しますので、money2も100になります。
**まとめ
LSLの「処理」の中で、「何を」「誰に」「どのくらい」「どのように」を扱うときに「変数」を使うと言いました。
そして「変数」には「型」があり、扱える値が決まっているという説明もしました。
例えばLSLの中で「誰に」を扱うときにはどんな変数を使えば良いでしょうか。
普段はあまり意識することがありませんが、セカンドライフのアバターには、固有の識別番号が付けられています。
これをUUID(Universal Unique Identifier)と言います。
UUIDを使えば特定のアバターを指定することができますので、「誰に」を扱うときには、UUID用の変数=key型変数を使うのが正解です。
では、「どのくらい」はどうでしょうか。
例えばお金を払う処理を行うときには、「どのくらい」払うのかを指定できます。
金額というのは整数ですので、これには整数型の変数、つまりinteger型を使うことになります。
このように、LSLで「何を」「誰に」「どのくらい」「どのように」を指定するには、指定したいものに適した変数を使うのです。
あくまでも大雑把にですが、以下にまとめてみました。
【型の用途】
|変数の型|扱える値|用途|
|integer型|金額、番号などの整数|「どのくらい」「何番目」など|
|String型|名前やメッセージなどの文字|「何を」(表示する内容など)|
|float型|小数、速度、割合|「どのくらい」(0.0~1.0の範囲で「割合」を示す場合もある)|
|vector型|位置座標、サイズ、力加減、方向、色データ|「どこへ」「どのくらい」など。色の場合は<赤,緑,青>の割合を指定する|
|rotation型|回転値|「どのくらい」(回転させる角度)|
|key型|アバターやオブジェクト等のUUID|「誰に」「何を」など|
|list型|値のセット|「何と何と何と何」のように値を並べて扱いたいときに使う|
もちろん、ここに挙げた用途が全てではありません。
どの型をどんな用途に使うのか、あくまでも参考です。
----
#comment
2009-06-07T14:11:58+09:00
1244351518