ラベル スクリプトの話 の投稿を表示しています。 すべての投稿を表示
ラベル スクリプトの話 の投稿を表示しています。 すべての投稿を表示

2009年3月20日金曜日

プリム変形もう一つ(おまけ)

前の日記でブリム変形のことを書いたのでもうひとつおまけ。
今回の建物では2階と3階のガラス窓が斜めに入ってます。
このガラス、どうやって角度や長さがぴったりと合わせてあるでしょう?

090319d.jpg


壁の三角形になっている部分は底辺が3m、高さが6mなので、
ピタゴラスの定理を使って計算すると、Excel風に表現するなら、
=SQRT(3^2 + 6^2)
となり、これを計算すると、
6.70820393249937...
うーん、既に面倒。。。><
これ、最初に豊島区にLifebound Cafeを作った時も、
反響板を兼ねた斜面のあるガラス天井にしたんだけど、
その時はホント泣きそうでした。。。
うまく合わない!><

前回、三角形のボタンを作るのに四角いブロック使いましたけど、
実は今度はその逆なんです。
四角いブロック型のガラス窓を作るのに三角柱を使います。
ということは、壁と同じ形、というわけですね。
複雑な計算をしなくても、壁をコピーしたプリムから変形して行きます。

090319e.jpg


写真2はわかりやすいようにそれぞれの面に色をつけてありますが、
まずは三角柱の「中空(空洞)」の数値を上げて
中が空洞になるようにします。
写真では断面が見やすいように75.0としてありますが、
実際にはガラス窓は薄いので最大の95.0になってます。

090319f.jpg


次に写真3のように、パスカットの始点と終点を設定します。
始点を0.25とすると全体の4分の1、
左側の面がカットされて消えます。
次に終点を0.75とすると更に全体の4分の1、
ここでは背面がカットされて消えます。
写真はこの状態を表しています。
そして更に、終点を0.5とすると
終点側から更に4分の1がカットされ、
つまりは緑色で塗った面も消え、
赤く塗った面だけが残ることになるわけです。
これが今回ほしかった形です。
横幅だけ調整して、
あとはガラスらしく透明度を上げてあげれば
壁にぴったりと合うガラス窓の出来上がりです。

前回は三角形を作るのに四角な立方体を使い、
今回は四角なガラスを作るのに三角柱を使い、
プリムの変形を覚えると
面倒なスクリプトや計算も回避できるという一例ですね。

スクリプトの話(番外編)〜どのプリムを選ぶか?

昨晩書きました通り、
今 Fanghammer 上空に作っているアヤシイ建物は
ちょっとしたエレベータを設置、
これで上下の階に移動します。
本当は階段の方が趣があるんだろうけれども、
階段は開口部を十分に取らないと
アバターの頭が引っ掛かったりしますし、
その開口部を作ったり、
それだけの空間を確保したりというのが面倒。^^;
つい場所をとらないエレベータにしてしまいます。

エレベータといっても、
ここでは大したことはしてません。
殆どインプレスR&Dから出ている
『セカンドライフで作るリンデンスクリプト入門』からの引き写し。

     *   *   *

vector gopos; // 現在の場所から目標地点までのベクトル
vector aimpos = <54,81,509>; // 目標地点

default {
  state_entry() {
    llSetSitText("Up to 2F"); // パイメニューに表示する内容
    gopos = aimpos - llGetPos();
    llOwnerSay((string)llGetPos()+"-" +
      (string)aimpos+"="+(string)gopos);
    llSitTarget(gopos, ZERO_ROTATION);
  }

  on_rez(integer num) {
    llResetScript();
  }

  changed(integer change) {
    if (change & CHANGED_LINK) {
      key sid = llAvatarOnSitTarget();
      if (sid != NULL_KEY) {
        llUnSit(sid); // 目標地点に着いたら立たせる
      }
    }
  }
}

     *   *   *

エレベータと言っても、
実際には椅子に座らせるのと同じことをやっています。
このスクリプトをエレベータのボタンに仕込めばOKなんですが、
ここで大事なことは、
gopos で示されるローカル座標は、
このボタンになるプリムを
レズした時の向きを元に計算されているということ。
つまり、プリムを回転させたりして向きを変えると、
あらぬ方向に飛んでいってしまいます。><
前に椅子に座らせるのは難しいと書きましたが、
ここでもその問題が出てくるのです。
そこで、このスクリプトを使う時は
絶対にプリムの向きを変えない、という方針でいつもやってます。
そこで、面倒くさがりの僕は、
よくボタンに球体のオブジェクトを使ってました。
球体なら上も下も、右も左もないもんね。^^;

でも、やっぱりエレベータのボタンとしては、
上に行くのか下に行くのか、
方向感のあるボタンの方がいいですね。
というわけで、今回は写真1のように三角形のボタンを設置しました。

090319a.jpg


ここで問題。w
この三角形のプリムはどんなプリムを使ったでしょう?
え? 当然三角柱だろうって?
でも三角柱を普通にレズすると写真2のようになります。
この問題の大前提はプリムの向きを変えないこと。
それではあのボタンの形にもっていくのは無理ですね。
近いのは四角錐ですが、
これは横から見た形が違う。
ボタンは横から見ると長方形をしています。。。

090319b.jpg


というわけで、答えをバラすと
実は今回使ったプリムはブロック、立方体でした。w
四角いのに何故? と疑問に思われる方もあるでしょうが、
実はこれ、「テーパー」というパラメータで変形してあるんです。
「テーパー」は X 方向、または Y 方向に
一方の辺をもう一方の辺より短くするパラメータです。
具体的には写真3を見て頂ければと思いますが、
普通は値が「0」で、ブロックなら一番左の形です。
これを X=0.25 とすると、X 方向の上の辺が25%縮みます。
0.5なら下の辺の半分の長さに、
そして、今回のように1.0とすると
上の辺の長さは「0」となり、三角形となってしまいました。
これに、Y も1.0にすると四角錐になりますね。
更に、マイナスの値を入れると、今度は上の辺はそのままで、
下の辺が短くなっていきます。

090319c.jpg


このように、プリムは変形のパラメータをいじることで、
全く異なった形にすることが可能です。
どう考えても2つ以上組み合わせないとできないような形も、
実はうまく変形すると1つのプリムで実現できたりします。
プリム節約のためにも勿論有用ですが、
今回のように、スクリプトに制限がある場合にも
プリムの変形は効果を発揮します。
みなさんもいろいろ試してみられては?w

P.S. 勿論、ボタンのプリムを回転させたところで、
目標地点までのローカル座標をきちんと計算できれば問題ないんですが、
どうも自分には無理です。
一つの軸に対して回転させると
他の軸の値も連動して変わるようなので、、、><
それで、頭悩ますよりは、向きを変えない、と決めてしまって
プリムの形状をいじった方が簡単なんです。w

2009年1月19日月曜日

椅子を作ったよ

そう言えば、さっき何気にSSに載せてましたけど、
久しぶりに椅子なるものを作ってみましたよ。
椅子と言っても、よく公園にあるような
円筒型のコンクリで出来た(多分)シンプルなものだけどね。w

090118d.jpg


「久しぶりに」と書いたのは、
椅子なんて、最初にカフェを作った時以来。
それ以降は知り合いの椅子を買って来たりで済ませてましたから。w
で、椅子って、インプレスのスクリプト入門の最初に上がってるくらい
基本中の基本なんだろうけど、
これって難しいですよねー。
慣れれば大したことないのかもしれないけど、
なかなか思うように座ってくれない。w

llSitTarget(vector 座る位置, rotation 座る向き)

なんだけど、この rotation が絡んで来るとさあ大変、という感じです。w
初心者の頃も苦労したけど、
実は先ほどもやっぱり苦労しましたよ。w

まぁ、今回は自分が作ったオブジェクトなんで
すぐに修正できましたけど、
これが人の作ったものだと修正するのは更にややこしいですね。
上の式の座る位置とか向きとかローカル座標なので、
プリムを回転してある場合は予期しない方向に動いたり
回ったりします。w

これで思い出したのが、pira Noel さんの水族館に併設されている
Cosmic Tree Cafe のカウンターの椅子。
pira Noel さんや Sena Pinklady さんと話そうと思って座ると
何故かカウンターに背を向けて座る椅子がいくつかあるのです。w
ぴらさんは困っていましたが、
僕は「いじけ椅子」と呼んで笑っていました。

090118e.jpg


座るだけじゃなくて、楽器を弾いたりテレポートしたり
llSitTarget() はよく使う、基本的な関数なんですけど、
ホントうまく設定するのが難しいですね。
久しぶりに初心に返ったヒロシでありました。w

2008年12月30日火曜日

スクリプトの話(最終回)〜シーンを切り替える「ステート」

今年も残すところあとわずかとなり、
今年中に片付けておかなければならないことを
バタバタと片付ける毎日ですが、
そう言えば、そう、
この連載も最終回を残すのみで止まっておりました。
というわけで、駆け足で幕を引きたいと思います。w

     *   *   *

さて、ダイアログを表示してボタンで選択させるなど、
処理を分岐する時に一番お世話になるのは「if文」ですね。
ただ、この「if文」が適しているのは、
「もし〜の時」の「〜」の部分がそれぞれ対応関係にある場合ですね。
例えば「1」と「1より大きい数の時」とか、
ステータスが「open」と「close」の時、
色が <1.0,1.0,1.0> と <0.0,0.0,0.0> の時、というように
比較対照が可能な場合です。

それでは、私の「世界時計HUD」のような場合はどうでしょう。
タッチするとダイアログが開き、
「Colors」と「Calculate」というボタンがあります。
「Colors」を選ぶと今度は色名のボタンが出て来て
どれかを選ぶと文字色が選んだ色に設定されます。
一方、「Calculate」を選ぶと、今度は都市の名前のボタンが現れ、
都市を選ぶと更に時刻を選ぶボタンが、
それも3ページにわたって表示され、
都市と時刻の選択の結果として、
その都市でその時間の時の世界の各都市の時刻の一覧が表示されます。

080830a.jpg

080830b.jpg

080830c.jpg


おわかりでしょうか?
「Colors」と「Calculate」は同じような選択ボタンでありながら、
その後の動きは全く違うのです。
これをもし「if文」を使って書いたとしたら
それはややこしいことになりますし、
予期しない変数の動きに翻弄されることになるかもしれません。
いっそのこと、「Colors」と「Calculate」それぞれを押した時、
全く別の処理の流れにするようにしてしまえば……。

こんな時便利なのが、「ステート」です。
ステートとは文字通りには「状態」ということです。
が、ここでは、この連載のはじめの方で
スクリプト=「台本」、
リンデンのスクリプトもアバターやオブジェクトを主人公とした
ドラマや劇だと考えればいいと書きましたが、
そういう意味では「ステート」は「シーン」、
「幕」とか「場面」に当たるものだと考えればいいでしょう。

ステートで一番有名なのが「default」というステートです。
defaultしかないスクリプトは
1場面だけでできた劇のようなものです。
しかし、多くの劇ではいくつかのシーンから構成され、
シーンが異なると主人公の行動も心の動きも変わったりします。
同じロミオも人生を憂う弱々青年ロミオだったり、
ジュリエットの心に光を点す王子様のような存在であったり、
ティボルトと剣で戦う時には勇敢な男であったりします。
同じように、リンデンのスクリプトも
「ステート」をいくつか設定することで、
複雑な動きをコントロールすることができるようになります。

例えば、上の「世界時計HUD」の例では、
「Colors」ボタンを押すと「色設定用のステート」に映り、
色の設定をし終わると、default ステートに戻って来ます。
同じように「Calculate」ボタンを押すと
「時刻計算用のステート」に移り、
都市と時刻を指定して世界各地の一覧を表示し、
ここで「OK」ボタンを押すと default に戻って来るのです。
実際の処理は複雑なので、イメージだけ書いておくと
こんな感じです。

(グローバル変数定義)

(時刻表示用の関数定義)

default {
  state_entry(){
    (初期設定の処理)
  )

  timer(){
    (時刻更新の処理)
  }


  touch_start(integer total_number) {
    (時計がタッチされた時の処理)
  }

  listen(integer channel, string name, key id, string message){
    if(message == "Colors") {
      state select_colors;
    }else if (message == "Calculate") {
      state select_calculate;
    }
  }
}

state select_colors {
  (「Colors」ボタンを選んだ時の処理)
}

state select_calculate {
  (「Calculate」ボタンを選んだ時の処理)
}

大体こんな感じです。
想像がおつきになるかと思いますが、
実際は select_colors のステートの中でも
select _calculate のステートの中でも
またダイアログを表示してボタンを選んで、という処理になりますので、
この中にもまた touch_start や listen があり、
ボタンの値によって「if文」で処理が分かれるので、
もっと複雑にはなります。
が、ステートを変えると便利なのは、
select_colors のステートにある時は、
select_calculate や default ステートの時のことを
全く考えずに、色設定のことに集中して考えられる点です。
これが「if文」だと、常に「そうでない場合はどうなるか」を
頭の片隅で考え続けながらプログラムを書いていかなければなりません。
これは、最初の頃に書いたように
プロでも間違いを犯しやすい状態と言えます。
ステートはリンデン・スクリプトの中でもとっつきにくい、
わかりにくいもののように思われていますが、
使いこなすと却って初心者のプログラミングを
楽にしてくれるものでもあるのです。
「if文」で条件分岐するのが難しい、と思ったら、
思い切って新しいステートを宣言して導入してみることを
ここではお薦めしておきたいと思います。

     *   *   *

というわけで、20回にわたって書いてきました「スクリプトの話」、
一応今回を以て終了とさせて戴きたいと思います。
スクリプトというものをプログラムと捉えるよりも、
アバターやオブジェクトが動き出すドラマ、物語の台本として考え、
そこで行き詰まった時にどうすればいいか、
そのコツのようなものを書いてきたつもりです。
これがスクリプトは難しい、どうもうまく動かないという方々の
ご参考に、少しでもなれば幸いです。
まだまだ本当はあれもこれも書いておいた方がいいのかな、
などと思うところはありますが、
とりあえずはここで止めておくことにしまして、
もし皆さんからのご意見、ご要望、ご質問などありましたら、
このSNSのメッセージ、またはIMでお寄せ頂ければ
またどこかで書かせて頂くこともあると思います。
その時はどうぞよろしくお願いします。

最後までお付き合い頂いた皆様、
本当にありがとうございました。
それでは、また!

2008年11月26日水曜日

スクリプトの話〜番外編・色の名前

ここ2回ほど、色を指定する話題を出しましたので
それに関連して色の話を。。。

僕は会社に勤めながら
一方ではWEBの制作なんかもしてるわけなんですが、
そうするとどうしても色を扱うことになるんですね。
で、例えば「白」を表現するのに、WEBであれば
「#FFFFFF」というコードを使いますし、
会社でエクセルのマクロを組む時なんか
(「あ〜、ヒロシ君、この管理表ね、
終わった課題の行はグレーにして
重要度が「高」のものはピンクになるようにしといてねー。」)
RGBで「255, 255, 255」と表現したりします。
これだけでも一杯一杯なのに、
リンデンスクリプトでは <1.0, 1.0, 1.0> というのを
覚えなきゃならなくなりました。
うわー、大変!
どれか一つに統一してくれ〜!

ところが、実はこの3つ、同じものだったのですね。
RGBというくらいで、光の3原色、
赤と緑と青をどのくらい混ぜるか、というのを表しています。
ご存知のように、赤と緑と青、全部を全て100%ずつ混ぜると
白になります。
そして、この3つ全てが全くない状態、
それが暗闇の黒、というわけですね。
と、すると、リンデンの <1.0, 1.0, 1.0> というのはわかりやすい。
最初、色はベクトル型、と聞いて、
数学の時間を思い出し、妙な矢印を思い出していた僕ですが、
何のことはない、この3つの数字は、赤、緑、青を
どのくらい混ぜるかを示しているだけですね。
<1.0, 1.0, 1.0> は全てが100%ということですから白、
そして、<0.0, 0.0, 0.0> は黒、というわけです。

となると、いわゆるRGBは?
WEBカラーのしくみは?

そう。まず、RGBの方は名前から言っても
リンデンと同じなのでしょう。
(255, 255, 255) は赤と緑と青のレベルだ。
でも何故255なのか?
これが0スタートということを考えると、
つまりは256の段階があることになり、、、
うーん、256とはいかにもコンピュータらしい数字だ。
そもそもこの数字は何なのか。。。

実はこの10進法の255を16進法で表すと「FF」なのです!
16進法で最大の数である「F」が2つつながった形、
つまりは16×16を表現しているわけですね。
ということは!
そう。
無意味に思えたWEBカラーの「#FFFFFF」も
何のことはない、「FF, FF, FF」だったのであり、
つまりは「255, 255, 255」であったわけなのです。
で、255では扱いにくいので、これを1.0に置き換えたのが
リンデンのスクリプトというわけです。
みんな同じ数字だったのだ!^^

と考えると、つまりこれらは全て、
赤と緑と青の組み合わせを示しているわけですから、
一々覚えたり、チャートを見たりしなくても
感覚的に、簡単に表記できるようになりますね。

つまり、「赤」は赤100%なので    <1.0, 0.0, 0.0>
「青」は青100%なので、      <0.0, 0.0, 1.0>
「緑」は緑100%なので、      <0.0, 1.0, 0.0>
「黄色」は赤と緑の組み合わせなので、<1.0, 1.0, 0.0>
「空色(アクア)」は緑と青で、   <0.0, 1.0, 1.0>
「赤紫」は赤と青で、        <1.0, 0.0, 1.0>
そして3色全開の白が         <1.0, 1.0, 1.0>
暗闇の黒は              <0.0, 0.0, 0.0>
というわけですね。

気づけば何てことないですし、
気づいている人には何を今更、という話でしょうが、
僕自身、これらの色を覚えられずに手こずりましたので、
敢えてここにメモとして残しておくことにします。
ご参考まで。w

スクリプトの話(19)〜リストを使おう

前回はバーニング・ライフで使った
色が順番に変わるオブジェクトを例に
周期的に状態を変える方法について考えました。
そこで今回はその続きのような感じで、
もう少し考えてみたいと思います。

同じバーニングライフの僕のキャンプには、
星をイメージしたパーティクルが飛び交っていて
これが一定時間が経つと色がランダムに変化するような
そんな仕掛けにしてありました。

ランダムと言えば、乱数を発生させる関数を持って来ればできそうですが、
ここで扱うのは色というベクトル型の変数であるし、
何よりランダムと言っても、とんでもない色ばかり出て来ても困るので、
出来れば指定した色の中でランダムに変わるのが望ましいのです。
そんな欲張りなことができるのか?
そう、出来れば好きな色のリストみたいのがあって、
その中で順番がメチャクチャに入れ替われば……。

と思ってはたと思いつきました。
そうです。リストです。
整数型や文字列型のように頻繁に出て来るわけではないので、
目立たない、ジミ〜な存在ですが、
リンデン・スクリプトにはリスト型という変数があるのです。
これはいろんなものをまとめて放り込んでおける
なかなか便利な変数なのです。

昨日と同じく、青、赤、黄に白を加えた4色の間で
ランダムに色を変化させるということにして、
この4色のリストを作りましょう。
宣言の方法も簡単です。
要素をブラケット(四角い括弧)で囲んで
カンマで区切って並べるだけです。

list lstColors = [<0.0,0.0,1.0>, <1.0,0.0,0.0>, <1.0,1.0,1.0>,
  <1.0,1.0,1.0>];

今、lstColors というリスト変数の中は、青、赤、黄、白という色が
順番に並んでいます。
ここで関数の登場。その名もズバリ!

llListRandomize()

文字通り、リストをランダムにする関数です。w
ランダムにした後どうするか?
出来たリストから1番目の要素を取り出してあげればいいのです。
そして、その要素を取り出すのが、変数によって異なりますが、

llList2〜()

という関数で、今回はベクトル型なので、

llList2Vector()

を使います。
昨日のスクリプトを参考にして、
20秒毎に色をランダムに変化させるには次のようにします。

default {
  state_entry(){
  }

  touch_start(integer total_number){
    llSetTimerEvent(20);
  }

  timer(){
    llSetColor(llList2Vector(llListRandmize(lstColors), 0),
      ALL_SIDES);
  }
}

llList2Vector() の2番目の引数が
リストの何番目の値を取ってくるかを示すもので、
1番目は「0」になります。
関数が入れ子入れ子になっててややこしそうですが、
何ともシンプル、1行で終わってしまいました。w

ん? ということは?
昨日の順番に色が変わるスクリプトも
このリストを使えばすっきりするのでは?

i = 0;

default {
  state_entry(){
  }

  touch_start(integer total_number){
    llSetTimerEvent(20);
  }

  timer(){
    llSetColor(llList2Vector(lstColors, i), ALL_SIDES);
    i++;
    if (i >= llGetListLength(lstColors) - 1) {
      i = 0;
    }
  }
}

llGetListLength() は、リストの要素の個数を返す関数です。
例えば今回の場合、青、赤、黄、白の4個になります。
但し、リストの順番も、カウンター変数 i の値も
「0」からスタートしているので、1を引いているわけですが、
昨日の「if 文」がずらずらっと並んだものに比べると
何ともシンプルになりましたね。
カウンターの値を増やしながら、
そのカウンターの値と同じ順番の色を持って来い、
と言っているわけです。
そしてカウンターが要素の数だけ増えると
また0に戻してあげているのですね。

リストはこのように順番に処理させる時にも使えますし、
それから、先に書いたようにいろんな要素を入れられるので、
データベースのような使い方もできます。
例えば、昨日ご紹介した、仲間のブログを表示する
メッセージボードですが、
あれは実はセカンドライフの中から僕のRLのサイトにアクセスして、
次のようなCSVファイルを作らせています。

記事1のURL, 記事1のタイトル, 記事1の更新日
記事2のURL, 記事2のタイトル, 記事2の更新日
記事3のURL, 記事3のタイトル, 記事3の更新日……

もうお分かりですね?
タイマーでこのファイルを1行ずつ取ってくるのですが、
その時に、1行の3つの要素、
記事のURLとタイトルと更新日とをリストに入れるのです。
そしてそのリストからタイトルと更新日を取り出して表示に使い、
URLは取り出したらそれをタッチスクリプトに入れて、
メッセージボードをタッチするとその記事がブラウザで開くように
設定してあるわけです。

080821b.jpg


今のところ、説明が飛ばしすぎで分からない、という方は
ごめんなさい、あまり気にしなくてもいいです。
とにかく、リストを使うといろんな複雑な処理も
簡単に行えるようになるのだ、
というくらいお分かり頂ければ幸いです。

あまり馴染みのないリスト型ですが、
このように複雑な「if 文」を使わずに済んだりと
いろいろ便利な使い方ができるので、
皆さんもいろいろ研究してみられてはいかがでしょう?

それでは今日はこの辺で。

2008年11月25日火曜日

スクリプトの話(18)〜3つ以上の状態判定

いやぁ、この連載も随分間が空いてしまいました。
今回含めて残りあと3回なんですけどね、
一気に行けそうなのに、なかなか時間がとれないでいます。
まぁ、のんびり最後までお付き合い下さい。

     *   *   *

さて、前回は状態の判定をするのに
「1」と「0」を使う話をしました。
人間が物事を判断する基準は
大抵が「OK」か「NG」
「Yes」か「No」
「いい」か「悪い」
つまりは「真」か「偽」かに還元できるもので、
それだからこそ、コンピュータはその人間の真似をして
「1」と「0」との組み合わせだけで複雑な処理をするわけですね。
「1」と「0」の間で旗を上げ下げして処理を分岐するのは
いかにもコンピュータ・プログラムらしいとも言えますし、
同時に人間的と言えるのかもしれません。

ところで、物事の判断というのは
必ずしも二者択一であるとは限りません。
楽しいお昼休み、今日の昼食はどこにするか
ラーメンか牛丼かカレーか
はたまたイタリアンかフレンチか……?
結局は毎日のように行っていると飽きてしまうので、
これらの店に順繰りに行くかもしれません。w

今年のバーニングライフの
僕のキャンプにあった巨大なメッセージボードは、
実は20秒おきに色が変わる設定ができます。
確か7色くらいを設定して
順番に変わるようにしてあったのですが、
そこでどんな処理をしているかと言うと。。。

080914b


話を簡単にするために
色を青、赤、黄の3色に限定します。
そして、タッチするとタイマーがスタートするとしましょうか。

integer i = 0;

default {
  state_entry(){
  }

  touch_start(integer total_number){
    llSetTimerEvent(20);
  }

  timer(){
    if (i=0){
      llSetColor(<0.0,0.0,1.0>, ALL_SIDES);
    } else if (i = 1){
      llSetColor(<1.0,0.0,0.0>, ALL_SIDES);
    } else if (i = 2){
      llSetColor(<1.0,1.0,0.0>, ALL_SIDES);
    }
    i++;
    if (i = 3) {
      i = 0;
    }
  }
}

実際にはもっと複雑ですが、
大体こんなイメージです。
大事なのは、変えて行きたい色の順番に数字をふっておいて、
変更するごとに1を足しているところです。
(そう。i++ は i = i + 1 のことですね。
こういう略した書き方をするとプログラムも
シンプルにすっきりとするので
じゃんじゃん使いましょう。w)
そして、順番の最後のところで
数字を振り出しの「0」に戻してあげてるところですね。
これでまた1...2...と数え上げて行き、
またまた最後の所で0に戻り、、、と繰り返していくわけです。

先に書いたお昼に何を食べるか、という件も、
これをカレンダーに仕込んでおけば、
昨日はカレーを食べたから今日はイタリアン、
なんて教えてくれるかもしれません。w

あ、実際にはこれの応用で、
僕の Lifebound Records 本店のメッセージボードが稼働しています。
仲間のブログの最新記事を順繰りに表示していますが、
記事と合わせてその方のアイコンに変わるようになっているのも
この仕掛けが使ってあるのです。

080821d.jpg


それでは今日はこの辺で。

2008年11月8日土曜日

スクリプトの話(17)〜「旗を立てる」・状態の判定

さて、私のSL内の(一応)会社である Lifebound Records には
写真に見られるように左右にスライドして開く自動ドアがあります。
この自動ドアは私が豊島区で初めてお店を作った時に
初めて取り組んだスクリプトものでもあります。
豊島区からスタトラ、メインランドと移ってきて
再びこのお店に設置することにしたのです。

081107.jpg


ところで、この自動ドア、
よくよく考えると不思議ですよね。
左右に開く、右側の方に焦点を当てて考えると、
開く時には元の位置から右方向に移動してしまうわけです。
そして閉まる時には開いた場所から今度は左方向に移動して
元の位置に戻ってくることになります。
左側のドアはこの逆の動きになりますね。

ということは、このドアは、右のも左のも、
今自分がどこにいて、次のイベント
(人が近づくとか遠のくとかーーセンサー・イベントですね)
が起こると、右に動いたらいいのか、左に動いたらいいのか
知っていることになります。
言い換えれば、今ドアが開いた状態なのか、閉じている状態なのかを
それぞれのドアは知っている、ということになりますね。

どうやったらこれら左右のドア・オブジェクトに
開いているとか閉じているということを教えられるでしょう?

こういう時、オブジェクトの状態を表す変数を用意してあげます。
integer(整数)型で、普通の状態、つまり閉じている時は0,
変化があった時、つまり開いた時の状態を1として、
名前は例えば isOpen、つまり「開いている」という名前にしましょう。
開いていれば isOpen = 1、
閉じていれば isOpen = 0
というわけです。

ここでドアを開くようにオブジェクトを動かす関数を openDoor() ,
閉じるように動かす関数を closeDoor() とすると、

integer isOpen = 0

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

実際にはもっと複雑な処理になりますが、
イメージはこんな感じです。
初期状態としては、ドアは閉まっているわけですから、
isOpen には「0」が代入されています。
そして、「if文」では、
isOpen が「0」、つまり閉じている時は
ドアが開く処理をして、ここが大事なのですが、
処理した後に、isOpen に「1」を代入、
つまり、ドアが開いているという状態に変えています。
この状態で再びイベントが発生すると、
今度は isOpen = 1 となっているので、
ドアを閉じる処理が走り、
そしてその処理が終わると今度は isOpen に「0」を代入、
ドアが閉じている状態に変化させるわけです。

このように何かの状態を判断させなければならないような時、
integer型の変数を用意して「1」と「0」を代入して、
現在のその変数の値を見て処理を分岐させるのはよくあるやり方です。
ちょうどスイッチの「ON/OFF」のような働きをしているわけですね。
こういうはたらきの変数をプログラムの世界で何と言っているのか
私はよく知らないのですが、
元々データベースの世界から入った私にとっては
「フラグ」という表現がぴったりしているように感じます。

「フラグ」というのは勿論英語の「flag」、つまり「旗」のことです。
変化があった時にパッと旗を上げて注意を促すような感じで、
実際にデータベース用のブラウザでデータベースの状態を見ていると、
注意すべき項目のところに、ピコンと真っ直ぐに旗を立てたように
「1」とデータが入っているのです。

プログラムの世界でも同じで、
何か変化があった時、状態が変わった時は
この旗をピコンと上げる変数を用意してあげればいいというわけです。
状態の違いで処理を変えたい場合は
是非この旗を上げるような変数を使ってみて下さい。
きっといろんな場面で便利ですよ。w

2008年11月6日木曜日

スクリプトの話(16)〜宿題の答え=発想の転換を

さて、既に15回を越えましたこの稿も、
今回からはスクリプトを書く時のヒントのようなものについて
触れていってみたいと思います。
そのまずは一回目。。。

前回変数の初期化について書いた時に、
僕自身が作った「世界時計HUD」の例を挙げました。
これは写真にあるように、画面右上に世界の時間を表示するもの。
時間の下にあるバーをクリックして文字色を変えたり、
時間の計算をしたりすることができます。

081105a.jpg


前回話題にしたのは、
折角お好みの色に変更しても、
次にログインした時に文字色が初期化されてしまうので、
最後に設定された色を覚えさせることはできないか、
というものでした。
さて、どうするか?

実は、前回これについては別の機会に、と書いたのは
記事自体が長くなったからというのもありますが、
実は、私自身、どういう処理をしたのか
全く覚えていなかったからです。^^;
そこで実際にスクリプトを見てみましたが、
それでも暫くは意味不明でした。w
そして、あっ、と思い出しました。

最後にどんな色に設定したか、
それをどうやって覚えさせるか、
そしてそれを起動時に取得するか。
ノートカードを利用することは考えられますが、
あんまり複雑は処理はしたくありません。
そこで、僕はじーっとこのHUDとにらめっこしたのです。

もう一度このHUDの写真をよく見て下さい。
実は、こうやって時間を並べるのに使っているのは
何のことはない、例の llSetText() なのです。
そう、フローティング・テキスト、
で、何の上にフロートしているかというと、
設定用のボタンとなっている下のバーですね。
ということは、そう、
このバーは HUD なので平面ですが、
実際にはブロック・オブジェクトなんです。

と、ここまで書けばおわかりでしょうか。
文字色の設定をする時に、このブロックも
同じ色に設定しているのです。
そして、このブロックの色は一旦設定されたら
起動時もその色のままのはずです。
私が state_entry() で書いている処理は次の1行でした。

  llGetColor(4);

これはそのオブジェクトのかっこ内の番号で示された面の
色を取得する関数です。
ここで4番というのがこのHUDのバーとなっている面の番号です。
文字の色そのものは保存できないので、
このオブジェクトの色をまず取得し、
その色を今度は文字の色として
設定し直すということをやっていました。
これであれば、敢えて色を保存したり
それを呼び出したりなんて複雑な処理はいらなくなりますね。
自動的に残ってしまっている値を
そのまま使えばよかったわけです。
ただ、ここで、
文字そのものに拘らず、同じ色を使っているオブジェクトに
視点を移したのが解決の鍵となりました。

リンデンのスクリプトに用意された関数は、
例えばエクセルの関数などに比べると随分少ない気がします。
例えば llSet〜 (値設定用の関数)はあるのに
セットとなるべき llGet〜(値取得用の関数)がないとか、その逆とか、
えー、何でこれないの〜? と驚くこともしばしばです。
基本的にはなかったらできません。
が、そこでないからできない、と諦めるかどうか。
何か他の方法はないか、
処理したい対象(今回の場合文字の色)を直接扱うのが無理なら、
他のもの(今回の場合バーのオブジェクト)を処理することで
回り道しながらでも実現できるかもしれません。
場合によっては、SLの中から抜け出して、
自分のWebサーバで処理させてもう一度SLに返すといったような
荒技まで考える必要があるかもしれません。

明らかに直球ではムリ!
そんな時こそ苦しみながらも
何か手はないかと考えるのが楽しみになってきますね。
そして、自分でも信じられないような解決を見いだした時の
嬉しさといったら!

。。。なんだけど、
自分がやったことを完全に忘れていたヒロシでした。w
今回はこの辺で。

     *   *   *

P.S. オブジェクトに色をつけたり、
テクスチャを貼ったりする時に、
面を指定したい時がありますね。
関数としては、

  llGetColor()/llSetColor()
  llGetTexture()/llSetTexture()
  llGetPrimitiveParams()/llSetPrimitiveParams()

などがあります。
が、自分が設定したい面が何番になるのか、
LSL Portal の「Face」の項を見てもよくわかりません。
こんな時は編集モードで、「テクスチャを選択」の状態で、
指定したい面を選択、丸に十字が出ているのを確認します。
そして、メニューから
Advance > Rendering > Selected Texture Info を選ぶか
ctrl + alt + shift + T(Macの方は cmd + option + shift + T)
と操作すると、画面左下に2行の情報が表示されます。
その最後の「face X」の「X」が面の番号ということになります。

081105b.jpg

081105c.jpg


大抵のスクリプトの入門書などでは
面の値に ALL_SIDES つまり全ての面を設定しているものが多く、
私自身面番号の指定で苦労したので
ここに補足として書いておきます。

2008年11月4日火曜日

スクリプトの話(15)〜変数の初期化

前回は変数の値が有効に使える範囲
「スコープ」について説明しました。
これと関連して知っておかなければならないのが
変数の初期化のことですね。

例えばグローバル変数を使う時、
いくつかの処理で値を変えて使いたい場合は
その都度、その処理に都合のよい値に変えてあげる、
代入し直してあげなければなりません。

例えば、タッチした時だけ "I love you!" と言わせたいのであれば、

string strMessage = "Hello, Avatar!";

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

  touch_start(total_number){
    strMessage = "I love you!";
    llSay(0, strMessage);
  }
}

のように、定義し直してあげる必要がありますね。
特にグローバル変数を使う時は、
現在その変数の値(中身)がどうなっているか、
これからやろうとする処理で変更しておく必要がないか
確認しておくことが必要です。

ところで、上の例では一番最初に
敢えて「=」で具体的な値を代入していますが、
これがなかったらどうなるでしょう?
例えば次の例で、

string strText;
vector vecTextColor;
float fltAlpha;

default {
  state_entry() {
    llSetText(strMessage, vecTextColor, fltAlpha);
  }
]

よくあるフローティング・テキストのスクリプトですが、
果たしてこのテキストは表示されるでしょうか?
されるとしたら何色で、どんなメッセージでしょうか?

そう。
メッセージは何も見えません。
何故なら、定義された3つの変数は
何れも初期値が指定されていないので、
文字列は何もない文字列 ""、
ヴェクトル型の色は <0.0, 0.0, 0.0>、つまり「白」、
フロート型の不透明度は 0.0、つまり「完全に透明」、
となっているからです。

こんなこと、わかり切ったことのようですが、
何故敢えて書いているかというと自分でも失敗したからです。w
普通のオブジェクトならいいのですが、HUDの場合、
ログインするごとに default / state_entry() が走ります。
ということは!
以前、世界時計HUDを作った時に、そのプロトタイプでは、
折角文字色を好みの色に変更できるのに、
ログインし直すとまた白に戻ってしまう、という現象が起きたのです。
何も手を打たなければ、上のフローティング・テキストと
同じようなことになってしまい、
必ず色は白になりますね。^^;

これを回避するには、最後に変更した色を覚えさせておいて、
その色を state_entry() の時に読ませるようにする工夫がいりますが、
まぁ、これは別の機会にお話することに致しましょう。
今日は、変数を使う場合には初期化する必要がないか、
逆に初期化されていないか、
スクリプトの結果が違ったり動かない場合は、
そこをチェックする必要があることだけ覚えておいて下さい。

     *   *   *

今回までのところで、
スクリプトが動かないという状況を避ける為の方法、
また、動かない場合のチェックの仕方についての話は終わりにします。
当初は6回くらいで終わらせるつもりでしたが、
書き出したら結構長くなっちゃいましたね。^^;

次回からは、これまでの話を元に、
複雑な処理をする場合などの Tips を
いくつかご紹介していきたいと思います。
それでは、また!

     *   *   *

P.S. llSetText() について補足説明しておきます。

一度フローティング・テキストを設定したけれども
やっぱり消したい、という場合、
llSetText() そのものをスクリプトから削除しても
フローティング・テキストは消えません。
これ、すごい不思議なんだけど。w

と、ここまで書けば、上の記事を読んだ方はおわかりですね?
一度セットしたフローティング・テキストを消すには
llSetText() を生かして次のようにします。

  llSetText("Hello, Avatar!", <0.0, 0.0, 0.0>, 1.0);
       ↓ 下のように変更!
  llSetText("", <0.0, 0.0, 0.0>, 1.0);

そう! 表示していた文字列だけを消せばいいのです!
ご存知の方には何でもないことでしょうけど、
これもまた僕が失敗したことなので、
敢えて説明しておきますね。。。^^;

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 をそれぞれの処理の{}の中で、つまりローカルで使えば、
他のところでその中身がどうなってるか気にせずに
何度も使い回すことができます。

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

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

2008年10月29日水曜日

スクリプトの話(13)〜「llOwnerSay」の活用

さて、もしプログラムがうまく動かなかったら、
その原因をどうやって調べるか?
一番怪しいのは前回も書きましたように
条件が分岐したりするところです。
条件の設定を誤ったり、
思いもよらない値が来て、その行き先がない場合などですね。

こんな時、ポイントポイントで、
その時の結果がどうなっているかを確認するとよいのです。
例えば、変数の値は自分が期待したものになっているか、
処理が行われた結果がどうなっているか、といったことです。
どうやって確認するか?
ここで、「llOwnerSay」という関数を使います。
ご存知の通り、オーナーにだけ聞こえるようにしゃべってくれる、
アレですね。
変数の値や処理の結果を喋らせて、
その箇所ではOKなのかどうか、確認していくことになります。
OKならその次のどこか
NGならその前のどこかがおかしことになります。
検討がつけばいいのですが、
どうにも検討がつかない時は、
変数を設定したり、計算したりするポイントポイントごとに
llOwnerSay を入れてしゃべらせてあげるといいでしょう。

例を挙げます。

最近 YouTube テレビが流行っていますが、
(私も出していますが。。。^^;)
これは大抵の場合、YouTube そのままは見られないので、
mp4 というQuickTimeで扱える形式に変換してくれるサイトへ
リンクを貼ることで実現します。

しかし、ということは、
QuickTime のメディアを置いている場所であれば
YouTube でなくても見られるわけです。
であれば、YouTube のチャンネルの時だけ変換して、
それ以外の URL は変換しない、という処理ができないでしょうか?

string channelURL // ノートから読み込んだURL

// "youtube" という言葉が含まれているかで判断
if (llSubStringIndex(channelURL, "youtube"){
  channelURL = llInsertString(channelURL,
    llSubStringIndex(channelURL, "youtube")+7, "mp4");
}else{
  channelURL = channelURL;
}

// ここで確認のために入れてみる
llOwnerSay(channelURL);

else は不要かもしれませんが、敢えて比較のために入れてみました。
これで、例えば

「http://www.youtube.com/watch?v=ABCDEFG」

を channelURL に読み込まれるようにすると llOwnerSay は、

「http://www.youtubemp4.com/watch?v=ABCDEFG」

としゃべってくれるでしょう。
これはうまくいくはずです。
ところが、あるURLを読ませるとうまく映像が映りません。
その時 llOwnerSay はこう言っていました。

「http://www.youtubemp4mp4.com/watch?v=TUVWYXYZ」

そうです。もともと変換されたサイトのURLを読んだのですね。
なので余計な文字が入ってしまい、サイトを開けないのです。
これで先の「if文」は
もうちょっと考えないといけないことがわかりました。

このように llOwnerSay を使うと処理の結果が目に見えるので
どこが間違ったか、はっきりして便利です。
是非どんどん使ってみて下さい。
あ、勿論、ミスが修正できたら消してもいいですし、
「//」をつけてコメントにしてもいいですね。

それでは、また。

2008年10月28日火曜日

スクリプトの話(12)〜開発の現場から

久しぶりに連載の再開です。
ここでちょっとリンデン・スクリプトそのものから離れて、
実際のソフトやシステムの開発現場での話をしたいと思います。

プロの開発現場では、当然それは商品として出されるものですから
お客さんに迷惑をかけるわけにはいきません。
そこで、徹底したテストを何度も繰り返します。
テストというのは普通は合格して喜ぶものですが、
プログラムの開発では、寧ろバグが出ることを喜びます。w
バグというのは大抵の場合、
プログラマーが気づかない所、想定外のところで起こりますが、
これに早く気づけば、あとでお客さんに迷惑をかけなくて済む
というものです。

従って、テストはあらゆる可能性を試します。
プログラマーがこんなのあり得ないだろう、と思って
敢えて手を抜いたようなところもちゃんとテストします。
なので、大抵の場合、テストを考える人、テストを実行する人と
プログラマーは別の人、というのが基本ですね。

実は、プログラムが途中で止まったり、異常終了したりするのは、
この「想定外」という部分で起こります。
それはつまり、途中で「if文」などで分岐したのだけれど、
if や else if のどの条件にも合ってなくて、
プログラムが行き先を失ってしまう、ということです。
それからもう一つは、ループの条件に
やはり予期していない値が来てしまい、
条件を満たせないで、いつまでも繰り返しの終わらない
いわゆる無限ループに入ってしまう場合ですね。

これらのバグ出しをするためのテストを行えれば
最初から順序よくプログラムを流すので
どこで停まってしまうかはわかりますが、
さすがに一人で作るリンデン・スクリプトで
そこまで大がかりなテストをしていたのでは
たまったものではありません。
そこで、スクリプトがうまく動かず、
どうも途中で停まってしまっているような場合
どのようにして原因を調べるか、
次回はその方法について書いていきたいと思います。
お楽しみに。

2008年10月15日水曜日

スクリプトの話(11)〜最後はコメント!

さて、リンデン・スクリプトは基本的に自分しか見ないものだけれど、
時間が経てば自分も他人になってしまい、
何でこんなものを書いたかすらわからなくなることもある、
だからこそ、見やすくわかりやすいスクリプトを書こう、
そんな趣旨でここ何回か書いてきました。
しかし、字下げや名前の付け方だけではカバーできないところもあります。
そもそも何故そんな変数や関数を作ったのか、
後から見ると忘れてしまってることもあります。
そんなややこしいことをする時は、
コメントを入れておくといいですね。
この変数は何故必要なのか、
この関数はどういうはたらきをするのか
簡単に書いておくといいのです。

コメントというのは文字通り説明ですが、
コンピュータがプログラムの部分とは見なさい文のことです。
リンデン・スクリプトでは、スラッシュを2つ入れると、
その後、行の最後までに書いてあるものは無視されます。

関数の時はその関数のすぐ前に
こんな風に書いておくといいでしょう。

// 現在の自分の位置を取得する関数
getNowPos()

// ドアを開ける処理
openDoor()

こんな感じです。

変数の場合は、変数はズラズラとスクリプトの冒頭に並ぶこともあり、
いちいち上下にコメントが入ったのでは
何がなんだかわからなくなります。
そこで、その変数の後に書くことをお勧めします。

list colorList // ボタンに表示する色のリスト
string colorName // ボタンで選択した色の名前
vector colorValue // 選択した色のベクトル値

といった具合です。

コメント「//」の始まる縦の位置を揃えておくと
もっと見やすくなりますね。

僕はかなり長い「if文」や
「touch_start」と「Listen」と「dataserver」
それぞれの間が長い処理、
一旦外の関数に出てまた戻ってくる処理など
結構長丁場のスクリプトを書くことがあります。
そうすると、その長いスクリプトから
目的の場所を見つけるのに結構時間がかかったりします。
そんな時も、このオレンジ色で表示された日本語のコメントは
ぱっと目に入ってきて助かります。
是非是非自分のために
コメントをつけることをお勧めします。

     *   *   *

ここ数回は、コーディング・スタイルという、
殆どスクリプトの形を整えることを中心に書いてきました。
これらは別にスクリプトの動きに直接影響するわけではありません。
なので、面倒でもあり、手を抜きたい部分でもあります。
が、逆にこの形の部分を最初にしっかりとしておけば
間違いも減り、仮に間違えてもすぐ気づくので
より早くスクリプトを完成させることもできるのです。

そして!
実はここまで長々と書いてきたことは全て
うまく動かないスクリプトの原因を知るための前提なのです。
原因の調べ方については、いよいよ次回から書いていきます。
大変お待たせしましたが、是非楽しみにしていて下さい。

それでは、また。

スクリプトの話(10)〜変数、関数の名前

見やすいスクリプトを書く方法として、
前3回にわたって字下げや改行について書いてきました。
この他にわかりやすくする方法に
変数や関数の名前の付け方というのがあります。

変数や関数は自分で自由に作れるところですので、
自分さえわかっていればいいのですが、
前に書きましたように、しばらく経つと
その自分が他人になってしまうことがあるのも事実です。
そこで、やはり決め事をしておいた方がいいのですが、
ここまで来るともう、いろんな人がいろんな事を言ってますので、
実はこの項は書こうかどうしようかと
今まだ迷っている位なのです。
が、こうしたことも初心者の方にとって
何かの参考になるかもしれず、と思い、
やはり書かせて戴くことにします。
さらっと読んで頂いて、
自分に合った名前の付け方をされることを願っています。

まずは関数から行きましょう。
自分で作った関数の名前は
その関数のはたらき、処理内容がすぐわかるように
「動詞」+「目的語」という形で、単語をくっつけて
最初の単語は小文字、続く単語は始まりの文字を大文字、
というようにつけるといいと思います。
例えば、「今いる位置を取得する」という関数の場合、

getNowPosition

または Position を更に略して

getNowPos

といった具合です。
それから、ドアを開ける関数なら

openDoor

ノートカードからその内容(文字列)を読み込む関数なら

getStringFromNote
或いは、
loadStringFromNote

といった具合です。

因みに、この、最初だけ小文字で初めて
途中の頭文字を大文字にする書き方はキャメル型と呼ばれています。
キャメル(camel)とは英語で「ラクダ」のこと。
頭が小さくて背中がボコボコしてるのがラクダみたいだからとか。w

変数も同じような感じで名前をつければいいと思います。
もっとも、今度は動詞は不要ですね。
できれば、どういう型のものかもわかるようなものがいいですね。
例えば、前に出した例で、色に絡む変数を考える時、
これをキャメル型で書くとしたら、

色名を入れておくリスト型 colorList
色そのものの名前=文字列型 colorName
色を指定するベクトル型 colorValue
色の順番を表す整数型 colorNumber

といった感じですかね。
前に話しました、型を表す頭文字を使うとすると、それぞれ、

lstColorList
strColorName
vecColorValue
intColorNumber

といったところでしょうか。
因みに、この書き方にも呼び方があって、
こちらは「ハンガリアン」と呼ばれています。
何故ハンガリーなのか、興味ある方は調べてみて下さい。w

まぁ、最初に書きましたように、
ここは自由な部分ですのでこの程度で止めておきます。
何れにしても、自分すら他人になる可能性もあるので、
誰が見てもわかりやすい名前にしておく
というのが大事です。

それでは、今日はこの辺で。

2008年10月13日月曜日

スクリプトの話(9)〜長い時は改行しよう

081013b


上の写真は前に書いた、自分の土地に置いておいて
タッチすると自分のものでないプリムの数を教えてくれるスクリプト。
その実際のエディターの画面です。
せっかく見やすいように字下げをしてみたものの、
5行目の関数のところが長くて次の行にまではみだしてしまい、
またまた見にくくなってしまってます。
これではエラーが出た時の行数も数えにくい。。。

こんな時は思い切って、関数の途中でも改行してしまいましょう。
ええー、そんなことしていいの? と思われる方もあるでしょうが、
思い出して下さい。
コンピュータは「;」が出てきて初めて命令の文の最後と判断します。
それで「;」を付け忘れるとシンタックスエラーだよ、
と怒られたのですが、
これを逆手に取れば、
「;」が出てくるまでは途中に改行があろうが何があろうが、
コンピュータは途中で切れてるなんて思わないわけです。

そこで、適当な単語やコンマの位置などで改行してしまいます。
勿論、キーワードや単語の途中で切ってはダメです。
ここでは「myPos,」のところで切ってみましょう。
更に、処理の頭と間違えないように、ここでも字下げしましょう。

default {
  touch_start(integer total_number) {
    integer numPrims;     // 自分のものではないプリム数
    vector myPos = llGetPos(); // オブジェクトのある場所
    numPrims = llGetParcelPrimCount(myPos,
      PARCEL_COUNT_OTHER,FALSE);
    llSay(0, (string)numPrims + " are not mine!");
  }
}

さっきより見やすくなりました。
これを保存した結果が次の写真です。
無事コンパイルされて保存されました。

081013c


というわけで、長くなったらどんどん改行しちゃいましょう。
その方が見やすくなって
自分自身わかりやすくなりますから。

それでは、今回はこの辺で。

スクリプトの話(8)〜「if」の場合

前回は字下げがスクリプトを見やすくするということを書きました。
ここでやっかいなのが「if」の処理の場合です。
何故かというと、「if」の場合はその後単純に「}」で終わることよりも、
「else if...else if...」つまりそうでない場合、というのが
いくつも続く場合があるからです。
だからこそ使い勝手のいい道具であるわけですが。。。

例えば、タッチすると色が変わるスクリプトを作っていて、
ダイアログで色名を選んだ後の処理をしているところを想定して、
前回の【例1】に忠実に書いていってみましょう。

【例1】
if (message == "yellow")
{
  llSetColor(<1.0, 1.0, 0.0>, ALL_SIDES);
}
else if (message == "red")
{
  llSetColor(<1.0, 0.0, 0.0>, ALL_SIDES);
}
else if (message == "blue")
{
  llSetColor(<0.0, 0.0, 1.0>, ALL_SIDES);
}

う〜ん、何とも間延びしたのと、
結局このif文、どこまで続いているのか
わかりにくくなってますね。
分岐もいくつあるのか。。。

そこで、ここでは【例2】の方法をとりながら、
その変形でいくのをお勧めします。
つまり、

if (message == "yellow") {
  llSetColor(<1.0, 1.0, 0.0>, ALL_SIDES);
} else if (message == "red") {
  llSetColor(<1.0, 0.0, 0.0>, ALL_SIDES);
} else if (message == "blue") {
  llSetColor(<0.0, 0.0, 1.0>, ALL_SIDES);
}

何と、「else if(elseの場合も同じ)」のキーワードを
前の条件の終わりの「}」の後に書くのです。
こうすると大元の「if」と条件の終わりの「}」が並び、
しかも、「}」の数をぱっと見ただけで
条件がいくつに分岐してるかわかるというものです。

私個人は一番間違えやすいのはこの「if」の分岐のところです。
複雑なものになってくるとすぐにたくさん分岐してしまい、
その途中で何かを書き漏らすということはしばしばです。
それを防ぐためにも上の書き方は有効です。
なので、個人的には結果として、【例2】の方法を
お勧めしたいところです。

さて、これに関連してもう一つ。
「do〜while文」の書き方についても説明しておきます。

繰り返しの処理をするのに「while文」というのと
「do〜while文」というのがあります。
似てますが、使い方も処理の結果も全然違うので
注意が必要です。

「while文」は簡単で、

while (繰り返し条件){
  繰り返す内容
}

という形で、繰り返し条件に当てはまれば
「{」〜「}」の処理を繰り返すことになります。
言い換えると、条件に当てはまらなければ
「while文」の処理は一度も実行されません。

対する「do〜while文」は、

do {
  繰り返す内容
} while (繰り返し条件)

という形をしていて、必ず一回処理をして、
処理が終わったら繰り返し条件を確認して、
当てはまったら繰り返し、
当てはまらなかったらこの文を抜けて次の処理へと進みます。

ここでも、【例1】の方法で、

do
{
  繰り返す内容
}
while (繰り返し条件)

と書いてもいいのですが、
そうすると「do」の終わりの「}」が「while」の前にあって、
あたかも「while」は別物、「while文」のはじまりのように
見えてしまします。

今見たように「while文」と「do〜while」文は
処理の内容が全然違うので、
この違いを際だたせるためにも、「do〜while文」の場合は
先に示したように、

do {
  繰り返す内容
} while (繰り返し条件)

とするのがよいのです。
これで「while」は「do」で始まる処理の
終わりの部分なんだよ、というのがはっきりしました。
「else if」の場合と似てますね。

それでは長くなりましたので、今回はこの辺で。

スクリプトの話(7)〜間違いを減らすために……

さて、これまでスクリプトについて6回にわたって書いてきましたが、
殆どはそんなのは知ってる、という内容だったと思います。
実は本題は今回から。
ひとつには、どうやったら間違いを減らせるのか、
もう一つはどうみても間違ってなさそうなのに何故動かないか
その原因追求、トラブルシューティングの方法について
考えていってみたいと思います。

今回はまずその前半。
どうやったら間違いを減らせるか、です。
それはズバリ、スクリプトを見やすく書く、です。
では見やすいスクリプトとはどういうものでしょう。

実際のプログラムやアプリケーションの開発現場では、
一人でプログラムを全部書くということはありません。
大抵は部分部分分担してプログラムを作っていくことになります。
それから、実はよくあることですが、
あるプログラムを担当していた人が
急に別なプロジェクトに引き抜かれて
逆に別なプロジェクトにいた人がその人を引き継ぐ
なんてこともあります。
そうなると、一人一人が好きにプログラムを書いていたのでは
何が何だかわからなくなり、収集がつかなくなります。
少なくともプロジェクト内部の人間が見れば
誰でもすぐにわかるプログラムを書いておく必要があります。
そこで、大抵の場合、コーディング規約というものが作られます。
こういう時はこう書くんだよ、というルールですね。

この規約は、勿論、プロジェクトの数だけあると言っていいのですが、
大まかなところは大体世界的に共通していて、
リンデンが推奨しているスクリプトの書き方もほぼそれに準じてます。
スクリプトの場合は自分一人で書くわけですから、
自分の好きなように書いていいように思えますが、
実際は、何日もかけて書いているスクリプトだと
過去の自分も他人みたいなものです。
僕も動かなくてほったらかしにしてたスクリプトを
何ヶ月か後で見た時に
我ながらややこしいスクリプトだなぁ、と難儀した覚えがあります。w
こんなわけで、リンデンの推奨を参考に
自分なりの規約、スタイルを作っておくといいでしょうね。

このコーディング・スタイルということでは
これだけで本が1冊できるくらいのものですので、
ここでは簡単に要領だけ述べていきたいと思います。
その第一は、インデント、行の始まりを下げていく書き方です。

間違いで一番多いのが文法の誤りシンタックス・エラー
その中でも多いのがかっこが対になってないもの、
と前に書きましたが、これを防ぐ目的と。
それからその処理がどこからどこまで続いているか
一目でわかりやすくするために、
処理のまとまりを示す「[」「}」の単位で
行の始まりをどんどん右にずらしていく書き方です。
いつもの例で見ていきましょう。

【例1】
default
{
  state_entry()
  {
    llSay(0, "Hello, Avatar!");
  }

  touch_start(integer total_number)
  {
    llSay(0, "Touched.");
  }
}

【例2】
default {
  state_entry() {
    llSay(0, "Hello, Avatar!");
  }

  touch_start(integer total_number) {
    llSay(0, "Touched.");
  }
}

おわかりになりますか?
【例1】は、「[」を独立した行に書く書き方で、
これだと同じ階層に「[」と「}」がいるので、
対応関係は見やすいですね。
但し、難点もあって、
やたら縦に長く間延びすること。
スクリプトエディタの画面は小さいので
あまり長いと追うのが大変になってきます。

【例2】は、「[」をそのまとまりを示すキーワードの後に書くもので、
これだと随分とすっきりまとまって見えます。
慣れないうちはわかりにくいと思うかもしれませんが、
考えてみれば、終わりの「}」を忘れることはあっても、
始まりの「{」を忘れることはなく、
寧ろキーワードと終わりの「}」が対になっていることがはっきりして、
そのキーワードの処理がどこで終わってるか、
わかりやすいとも言えます。

どのくらい字を下げるか、ですが、
C言語のスタイルブックなどを見てみると
4文字下げがいいという人、
いや、2文字下げがいいという人様々ですが、
ここでは素直にタブキーを使って送ることにしましょう。
スクリプトエディターではタブを使うと
自動的に4文字下げるようです。

というわけで、まだやっていなければ、
【例1】でも【例2】でも
自分に合っている、わかりやすいと思う方法で
うまく字下げをして書いてみて下さい。
きっと見やすくなるはずですよ。

長くなりましたので、今日はこの辺で。
それでは、また!

2008年10月11日土曜日

スクリプトの話(6)〜実はよくある入力ミス!

さて、前の2回で、文法的な間違いのシンタックス・エラーと
型の違いからおこるタイプ・ミスマッチについて触れました。
これらはいかにもプログラム的な、決まり事の世界で、
初心者には難しい感じのする部分ですが、
実は意外によくあるのがもっと単純な入力ミス、
スペルミスによる間違いです。

リンデン・スクリプトはC言語の仕様によく似ているのですが、
C言語同様、アルファベットの大文字と小文字を区別します。
つまり、大文字と小文字を間違えただけでもう動きません。
llSay() は誰でもよく知っている有名な関数ですが、これを

llsay()

と書いただけで、もう別物とみなしてしまい、恐らくは、

「Name not defined within scope」
(決められた範囲内でこの名前は定義されていません)

と言われてしまうことでしょう。
逆に言うと、ちゃんと定義しているはずなのにこれが出てくるのは
スペルミスの可能性が高いということになります。

前にも書きましたが、スクリプトのエディタはよくできていて、

ステートとリンデンで用意されている関数は赤い文字、
イベントは水色の文字、
型を表すキーワードと文字列は緑色の文字
リンデンで用意されている定数は紫色の文字
コメントはオレンジの文字

に色が変わります。
llsay と書いて色が赤くならなかったら、スペルミスということです。
llsay を llSay に変えた途端に赤くなるでしょう?
この色を頼りに入力ミスによるエラーを減らせます。

ただ、自分で定義した変数や関数は色が黑のままです。
なので、この場合は、定義したところから
コピー&ペーストして使うことをお勧めします。
以前、Lifebound Records にある最新ブログ情報ボードを作った時に、
どうも期待通りに動かないのでよくよく調べてみると、
親スクリプトで「getRss」というメッセージを送っていて、
子スクリプトでは「getrss」を待っていた、ということがありました。
いつまで待っても小文字の「getrss」が送られてこないので、
スクリプトも実行されないのでした。
コピーしていれば防げたミスでした。

この関連で触れておきたいのが、
変数名を変更しなければならなくなった場合です。
複雑なスクリプトを書いているうちに
どうしても似たような変数が必要になってきた、
そこで、最初に定義した変数の名前も変更することにした、
というような場合ですね。
この時、普通に定義からコピー&ペーストしたのでは
今度はペースト漏れが生じて、
またまたそこでエラーが発生する可能性が出てきます。
こんな時は「置換」の機能を使います。

081011a


スクリプト・エディターの「Edit」メニューを開くと
一番下に「検索/置換」というのがあります。
これを選んで開く画面の
「検索」の入力ボックスに変更前の変数名をコピー&ペースト、
「置換」の入力ボックスに新しい変数名を入力します。
そして「検索」というボタンをクリックすると
「検索」のボックスに入れたのと同じ文字を探して強調してくれます。
変更してよければ「置換」ボタンをクリック、
すると「置換」のボックスに入れた変数名に変更して、
次に変更前の文字がある場所に飛んで行ってくれます。

081011b
081011c


「すべて置換」というボタンもありますが、
もしかして変えてはいけないところもあるかもしれないので、
「置換」ボタンで1つずつ確認しながら変更するのをお勧めします。
もし変えたくない文字のところに来たら、
「検索」をクリックすると、置換せずに次の場所を探します。
こうして画面から強調される文字がなくなったら
変数名の変更は全て終わったことになります。

今回は意外とよくある入力ミス、スペルミスを減らすための
コピー&ペーストと
検索/置換の機能について書いてみました。
もしまだやったことがなければ
是非試してみて下さいね。
それでは、今回はこの辺で。

スクリプトの話(5)〜次によくある間違い

前回は一番多く見るエラーとして、

「ERROR : Syntax error」

を取り上げましたが、
今回は次によく見るものとして

「ERROR : Type mismatch」

を見ていきましょう。

まず例です。
次のスクリプトは、自分の土地に置いて、
タッチすると自分のもの以外のプリムがあるか、
その数はいくつかを教えてくれるものを作ろうとしています。

default
{
  touch_start(integer total_number)
  {
    integer numPrims;     // 自分のものではないプリム数
    vector myPos = llGetPos(); // オブジェクトのある場所
    numPrims = llGetParcelPrimCount(myPos,PARCEL_COUNT_OTHER,FALSE);
    llSay(0, numPrims + " prims are not mine!");
  }
}

これを実行しようとすると、

「(7, 43) : ERROR : Type mismatch」

と言われてしまいました。
8行目の「llSay...」のところに何か問題がある。。。
そうです。
「Type mismatch」とは、一緒に処理しようとしている
変数の「型」が合っていないことを言います。
ここでは「integer」=整数である「numPrims」と文字列を
足そうとしたので怒られてしまったわけです。
今回のようにどうしても異なる型を足したい時は、
型を変換する必要があります。
今回の場合は、llSay が要求しているのが文字列ですから
整数の numPrims を文字列に変換する必要があるのです。
こんな時は変換先の型名をかっこでくくって
その変数の前につけてあげるといいですね。
上記の例の場合、

llSay(0, (string)numPrims + " prims are not mine!");

としてあげればスクリプトは無事コンパイルできて実行できます。

上の例の場合は簡単なので、まず間違えることはありませんが、
複雑なスクリプトになってくると
同じような名前の変数名が増えてきます。
例えば、オブジェクトの色を変えるスクリプトを作る時に、
選択できる色の種類を入れておくリスト型の colorlist、
リストから取得した色名を処理する文字列の colorname、
実際に色を設定する時に使うベクトル型の colorといった
3つの変数を用意する場合などです。
長いスクリプトを書いているうちに、
あれ? 色の名前は color だっけ、colorname だっけ?
なんて自分でもわからなくなることはよくあることです。w
そんな時に、この型間違い、
「ERROR : Type mismatch」
に出会しますね。

自分で定義した変数で間違っちゃうというのは
何ともトホホな話ですが、
それでも発生する以上は回避しなければなりません。
そんな時お勧めなのが、変数の前に型を表す文字をつけておくことです。
さっきの色の例で言うと、

リスト型のものは lstColor
文字列型のものは strColorName
ベクトル型のものは vecColor
もし整数型のものもあったら intColorNum

なんていう風にしておくと、例えば

llSay(0, "Now color is " + vecColor);

なんて書いた瞬間に、あれ、これは型が違う、と気づきやすくなります。
この方法は最近のプログラミングの世界では
あまり流行らなくなってきてるようですが、
初心者のうちは型を強く意識する上で有効だと思いますので
是非試してみて下さい。

それでは、長くなりましたので、
今回はこの辺で。