はじめに
LSLでは巨大なプログラムを作ることができません。
というのも一つのLSLのサイズは16KByteまでという制限があるためです。
この制限は、スクリプトコードの本体のサイズだけでなく、スクリプトが動作したときのメモリを含めたサイズです。
というのも一つのLSLのサイズは16KByteまでという制限があるためです。
この制限は、スクリプトコードの本体のサイズだけでなく、スクリプトが動作したときのメモリを含めたサイズです。
スクリプトを正しく書いていても、サイズが大きくなって16KByteの制限を越えてしまうと、
「Stack-Heap Collision」
などのエラーが発生し、スクリプトは動かなくなってしまいます。
「Stack-Heap Collision」
などのエラーが発生し、スクリプトは動かなくなってしまいます。
今回はこの問題に対処するための、中級者向けのお話をしておきます。
スクリプトのサイズ
メモリとかコードサイズと言われても何のことやら、という方もいらっしゃると思いますので、簡単に解説します。
解説なんかいらんからサンプルコードを見せい!という方はこちらまで読み飛ばしちゃって下さい。
解説なんかいらんからサンプルコードを見せい!という方はこちらまで読み飛ばしちゃって下さい。
スクリプトが動作するときには、当然ながらサーバーのパワーを使うわけですが、一つのスクリプトが動作するときに使えるパワーには限度があるということです。
ここで言う「サーバー側のパワー」というのは、スクリプトが動作するための場所のことです。
たとえば、学校の校庭を思い浮かべて見て下さい。
校庭ではいろいろなスポーツが出来るようになっていますが、面積には限りがありますよね。
テニスならば4面取れるけど、サッカーをするときは1面しか取れないとか。
コンピュータ上で何かプログラムが動くときは、校庭にコートを確保するのと同じように、プログラムが動作できるスペースを確保して動くようになっています。
このスペースのことを「メモリ」と言います。
たとえば、学校の校庭を思い浮かべて見て下さい。
校庭ではいろいろなスポーツが出来るようになっていますが、面積には限りがありますよね。
テニスならば4面取れるけど、サッカーをするときは1面しか取れないとか。
コンピュータ上で何かプログラムが動くときは、校庭にコートを確保するのと同じように、プログラムが動作できるスペースを確保して動くようになっています。
このスペースのことを「メモリ」と言います。
LSLは、このメモリの大きさが16KByteまでに制限されているのです。
校庭のごく一部だけを自由に使えるようなものです。
校庭のごく一部だけを自由に使えるようなものです。
16KByteというのがどのくらいのサイズかと言うと、例えば、
"こんにちは"
という文字データは、1文字4Byte、5文字なので20Byteです。
16キロByteというのは約16,000Byteのことですので、
"こんにちは"
に換算すると800個分ですw
"こんにちは"
という文字データは、1文字4Byte、5文字なので20Byteです。
16キロByteというのは約16,000Byteのことですので、
"こんにちは"
に換算すると800個分ですw
「おお、800回も"こんにちは"が言えるのか!」
とは思わないで下さい。
今回の記事のここまでの記述をデータ量に置き換えると、1,700Byteあります。
わずか30行程度の文章でそこまで行きますので、16,000Byte程度ならあっという間に埋まります。
とは思わないで下さい。
今回の記事のここまでの記述をデータ量に置き換えると、1,700Byteあります。
わずか30行程度の文章でそこまで行きますので、16,000Byte程度ならあっという間に埋まります。
あるいは、皆さんが今使っているかもしれない、インターネットのブラウザ。
私は先ほどインターネットエクスプローラを立ち上げてみましたが、何のページも表示していない状態で、使用メモリは6,000KByteでした。
6,000KByteというのは6,000,000Byteのことです。
16,000Byteと比べると桁違いですね(^^;
「たった16KByteしか使えないのか!」
と思っていただければ幸いです。
私は先ほどインターネットエクスプローラを立ち上げてみましたが、何のページも表示していない状態で、使用メモリは6,000KByteでした。
6,000KByteというのは6,000,000Byteのことです。
16,000Byteと比べると桁違いですね(^^;
「たった16KByteしか使えないのか!」
と思っていただければ幸いです。
スクリプトのサイズを制限しているのは、言うまでもなくサーバー負荷を軽減するためでしょう。
無制限にメモリを使えてしまったら、一本のスクリプトが校庭全面を占拠して、
「今日は俺様のリサイタルだ~!」
などという暴挙が可能になってしまいます。
そんな野望を阻止するため、スクリプトのサイズは16KByteに制限されているわけです。
無制限にメモリを使えてしまったら、一本のスクリプトが校庭全面を占拠して、
「今日は俺様のリサイタルだ~!」
などという暴挙が可能になってしまいます。
そんな野望を阻止するため、スクリプトのサイズは16KByteに制限されているわけです。
しかし・・・。
「お、お願いです、うちには相撲取りの息子が5人も居るんです・・・」
「ええい、黙れ!配給は一家につきイモ16個と決まっておる!」
「そ、そんな、それでは息子たちは食べていけません!」
「うるさいヤツめ、ならば食わなければよかろう!」
「ええい、黙れ!配給は一家につきイモ16個と決まっておる!」
「そ、そんな、それでは息子たちは食べていけません!」
「うるさいヤツめ、ならば食わなければよかろう!」
我々はそんな抑圧された環境下で、黙って耐え忍んでいくしかないのでしょうか。
モジュール化の概念
16KByteの制限はLSLの仕様ですので、これを打ち破ることは不可能です。
一つのスクリプトは必ず16KByte以内で収まるように作らなければいけません。
一つのスクリプトは必ず16KByte以内で収まるように作らなければいけません。
ですが、複雑なことをしようと思うと、どう頑張ってみたところで16KByteには収まりきらないという事態が起こりえます。
この限界は想像以上にすぐにぶち当たる壁です。
そして、正面からぶつかっていっても、絶対に越えることのできない困難な壁でもあります。
この限界は想像以上にすぐにぶち当たる壁です。
そして、正面からぶつかっていっても、絶対に越えることのできない困難な壁でもあります。
ではどうやったらこの壁を越えることができるでしょうか。
その答えは少年ジャンプあたりを読むと書いてあります。
その答えは少年ジャンプあたりを読むと書いてあります。
少年ジャンプのストーリーは「努力」「友情」「勝利」がテーマなんだそうです。
最近のジャンプは知りませんが、ドラゴンボールなどはまさに典型的ですよね。
最近のジャンプは知りませんが、ドラゴンボールなどはまさに典型的ですよね。
スクリプトの限界サイズは「努力」しても越えることができません。
こうなると「友情」に頼るしかありません。
一人の小さな手では何もできませんが、皆が集まると何かできてしまうのです。
こうなると「友情」に頼るしかありません。
一人の小さな手では何もできませんが、皆が集まると何かできてしまうのです。
これがLSLのモジュール化の発想です。
つまり、一つのスクリプトの限界は16KByteですが、複数のスクリプトを組み合わせ、全体として一つの大きなシステムにしてやるのです。
スクリプトの機能を分化し、連携して動くようにすることをモジュール化と言います。
モジュール化してやれば、トータルで見たときには16KByteの制限を越えるシステムを作ることが可能になります。
つまり、一つのスクリプトの限界は16KByteですが、複数のスクリプトを組み合わせ、全体として一つの大きなシステムにしてやるのです。
スクリプトの機能を分化し、連携して動くようにすることをモジュール化と言います。
モジュール化してやれば、トータルで見たときには16KByteの制限を越えるシステムを作ることが可能になります。
また、モジュール化には他のメリットもあります。
第一に開発効率が良くなります。
ダイアログモジュールやリッスンモジュール、アニメーション、サウンドなどをモジュール化しておくと、次にそれらの機能を使いたいときにはそのモジュールをほぼそのまま使うことが可能です。
一つ一つのスクリプトにいちいちリッスンやアニメーションのわずらわしいコードを書く必要がなくなります。
ダイアログモジュールやリッスンモジュール、アニメーション、サウンドなどをモジュール化しておくと、次にそれらの機能を使いたいときにはそのモジュールをほぼそのまま使うことが可能です。
一つ一つのスクリプトにいちいちリッスンやアニメーションのわずらわしいコードを書く必要がなくなります。
メンテナンス性の向上もみこめます。
モジュールごとに機能が分かれていれば、何か修正するときには一部のモジュールだけを更新するだけでOKになります。
例えばゲームの点数やRPGシステムの経験値など、データを保存するようなスクリプトの場合、スクリプトを修正するとデータはリセットされてしまいます。
しかしモジュール化によってデータを管理するスクリプトと、他の機能のスクリプトが分かれていれば、データ管理のスクリプトを修正しない限りはデータは保持されます。
モジュールごとに機能が分かれていれば、何か修正するときには一部のモジュールだけを更新するだけでOKになります。
例えばゲームの点数やRPGシステムの経験値など、データを保存するようなスクリプトの場合、スクリプトを修正するとデータはリセットされてしまいます。
しかしモジュール化によってデータを管理するスクリプトと、他の機能のスクリプトが分かれていれば、データ管理のスクリプトを修正しない限りはデータは保持されます。
また、拡張性も高くなります。
機能が分割されていますので、新しい機能を追加するときには別のモジュールを作って追加すれば良いのです。
例えば、オブジェクトの色が変わる機能を追加したいと思ったら、色変更用のモジュールを追加するだけです。
モジュール化を上手に実現しておくと、もとからあるスクリプトを一切いじらなくても大丈夫です。
機能が分割されていますので、新しい機能を追加するときには別のモジュールを作って追加すれば良いのです。
例えば、オブジェクトの色が変わる機能を追加したいと思ったら、色変更用のモジュールを追加するだけです。
モジュール化を上手に実現しておくと、もとからあるスクリプトを一切いじらなくても大丈夫です。
モジュール化の手法
モジュール化を行うには、別に難しいコードを書く必要はありません。
初級スクリプトでも解説しているリンクメッセージ機能を利用すれば良いのです。
初級スクリプトでも解説しているリンクメッセージ機能を利用すれば良いのです。
リンクメッセージは違うprimのスクリプト同士で通信を行う機能として紹介しましたが、実は同じprim内のスクリプト同士の通信も可能です。
- 同一prim内のスクリプトへのリンクメッセージ送信:
[[llMessageLinked]](LINK_THIS, integer ,string, key);
- 同一オブジェクト内(全リンクprim)のスクリプトへのリンクメッセージ送信:
llMessageLinked(LINK_SET, integer ,string, key);
この二つを知っていれば十分にモジュール化は可能です。
モジュールのイベントは基本的に全てlink_messageイベントに統一しておきます。
普通のLSLの構造は、「きっかけ」があったら「処理」を行うものだと何度も説明していますが、「きっかけ」をリンクメッセージだけに統一してしまうのです。
そして「処理」のほうは実現したい機能に応じたものを書きます。
つまり、
「リンクメッセージを受信したら、何らかの処理を行うスクリプト」
これがモジュールの基礎スタイルになります。
モジュールのイベントは基本的に全てlink_messageイベントに統一しておきます。
普通のLSLの構造は、「きっかけ」があったら「処理」を行うものだと何度も説明していますが、「きっかけ」をリンクメッセージだけに統一してしまうのです。
そして「処理」のほうは実現したい機能に応じたものを書きます。
つまり、
「リンクメッセージを受信したら、何らかの処理を行うスクリプト」
これがモジュールの基礎スタイルになります。
イベントをリンクメッセージのみに限定することで、考えるべきポイントは「いかに処理を行うか」だけになります。
処理を実現する仕組みのことを「ロジック」と言いますが、このことから、こうしたモジュールを「ロジック・モジュール」と私は勝手に呼んでいます(^^;
要するに「処理」が主体のモジュールのことです。
処理を実現する仕組みのことを「ロジック」と言いますが、このことから、こうしたモジュールを「ロジック・モジュール」と私は勝手に呼んでいます(^^;
要するに「処理」が主体のモジュールのことです。
一方、タッチやリッスンなど、外部からの操作を受け付けるモジュールもまた必要です。
アバターが直接リンクメッセージを送る方法はありませんので、
「タッチされたらリンクメッセージを送る」
「コマンドを聞いたらリンクメッセージを送る」
というような、先ほどの「ロジック・モジュール」とは逆の働きをするモジュールを用意しなければなりません。
これらは様々なイベントを受け取りますが、あくまでも「処理」は「リンクメッセージの送信」です。
私はこれらのモジュールを「イベント・モジュール」と呼んでます。
アバターが直接リンクメッセージを送る方法はありませんので、
「タッチされたらリンクメッセージを送る」
「コマンドを聞いたらリンクメッセージを送る」
というような、先ほどの「ロジック・モジュール」とは逆の働きをするモジュールを用意しなければなりません。
これらは様々なイベントを受け取りますが、あくまでも「処理」は「リンクメッセージの送信」です。
私はこれらのモジュールを「イベント・モジュール」と呼んでます。
- ロジック・モジュール
- イベント:リンクメッセージ
- 処理:任意
- イベント・モジュール
- イベント:任意
- 処理:リンクメッセージ
さてさて。
少し具体的に考えて見ましょう。
少し具体的に考えて見ましょう。
例えば、ドア・システムのモジュール群です。
あんまりモジュール化するメリットはないかもしれませんが、説明するのにわかり易いと思うので(^^;
あんまりモジュール化するメリットはないかもしれませんが、説明するのにわかり易いと思うので(^^;
ドアと言っても、いろんな種類のものがありますよね。
初級スクリプトの記事の中でも、回転して開くドアや自動スライドドアなどを作りました。
同じ自動ドアでも、センサーを使ったもの、衝突判定を使ったものなど、作り方を変えることもできました。
初級スクリプトの記事の中でも、回転して開くドアや自動スライドドアなどを作りました。
同じ自動ドアでも、センサーを使ったもの、衝突判定を使ったものなど、作り方を変えることもできました。
そんな風にいろいろな実現方法があるドアを、モジュール化することでひとまとまりの「ドア・システム」にし、どのモジュールを使うかによって回転ドアになったり、スライドドアになったり、はたまた自動ドアにする、手動ドアにするなど、自由自在に組み替えられるようにしてみたいと思います。
まず、ロジック・モジュール。
「処理」を実現する部分です。
今回はドアですから、「ドアの開閉」を実現できればどんなものでも良いでしょう。
「処理」を実現する部分です。
今回はドアですから、「ドアの開閉」を実現できればどんなものでも良いでしょう。
ドアの開閉方法はいくつか考えられますね。
- 回転
- スライド(横に限らず。上下とかも)
- 薄くなって消える
- 穴が開く/削れる(ホロウやパスカットを使用した変形)
- 開・閉のテクスチャ切り替え(開いた画像のときにはファントム化する)
他にもあるでしょうが、まぁこんなところで。
それからイベント・モジュール。
ドアが開く「きっかけ」となる部分です。
これもいろんなものが考えられます。
ドアが開く「きっかけ」となる部分です。
これもいろんなものが考えられます。
- タッチ(誰でもタッチ可能なものから、特定グループ、オーナーのみ等、様々)
- 合言葉(リッスンを利用したもの)
- センサー(自動ドア用)
- 衝突判定(自動ドア用)
- タイマー(特定時間で開閉)
基本的にはこんなところでしょうか。
やろうと思えばもっと変なのも出来ますが(^^;強い風が吹くと開くとかwww
やろうと思えばもっと変なのも出来ますが(^^;強い風が吹くと開くとかwww
以上のようなモジュールを用意したとすると、ドアを作る際、使いたいものを組み合わせてオブジェクトに放り込むだけでOKになります。
いちいちスクリプトを書き直す必要がなくなります。
いちいちスクリプトを書き直す必要がなくなります。
また、「風で開くドア」を作りたいと思ったら、追加するのはイベントモジュール一つだけで済みます。
ロジックモジュール部分はなんら変える必要がありません。
ロジックモジュール部分はなんら変える必要がありません。
そのように使い勝手が良く、拡張も楽なのがモジュール化のメリットです。
ドア・システム・スクリプト
具体的にコーディングしてみましょう。
ここに載せたスクリプトは自由に改造・再利用していただいて構いません。
組み合わせていろいろドアを作ってみるといいでしょう。
ここに載せたスクリプトは自由に改造・再利用していただいて構いません。
組み合わせていろいろドアを作ってみるといいでしょう。
なお、モジュール・スクリプトはオブジェクトの中に直接作るのではなく、インベントリ内に作ったほうが後で使いやすいかと思います。
基本的なモジュール
slide door module
まずは「ロジック・モジュール」をいくつか作ります。
「ドアの開閉の仕組み」です。
インベントリのお好みの位置に新しいスクリプトを作成し、"slide door module"という名前を付けて下さい。
そして以下のコードを記述します。
「ドアの開閉の仕組み」です。
インベントリのお好みの位置に新しいスクリプトを作成し、"slide door module"という名前を付けて下さい。
そして以下のコードを記述します。
slide door module vector pos; vector move_to = <0.0, 1.5, 0.0>; integer opened = FALSE; default { state_entry(){ pos = [[llGetLocalPos]](); } moving_end(){ if (opened){ pos = llGetLocalPos() - (move_to * [[llGetRot]]()); }else{ pos = llGetLocalPos(); } } link_message(integer send, integer num, string str, key id){ if (str == "door"){ if (num){ // open llSetPos(pos + (move_to * llGetRot())); opened = TRUE; } else { // close llSetPos(pos); opened = FALSE; } } } }
このモジュールはオブジェクト(prim)をY軸方向に1.5mスライドさせます。
スライドして開くドアの動きを実現したものです。
しかしながら、あくまでも「開閉の動き」だけしか実現していません。
何をしたときにドアが開くかについては一切記述されていませんので、アバターがタッチしたり体当たりしたところで何も起こりません。
スライドして開くドアの動きを実現したものです。
しかしながら、あくまでも「開閉の動き」だけしか実現していません。
何をしたときにドアが開くかについては一切記述されていませんので、アバターがタッチしたり体当たりしたところで何も起こりません。
standard door module
もう一つ「ロジック・モジュール」を作っておきましょう。
インベントリのお好みの位置に新しいスクリプトを作成し、"standard door module"という名前を付けて下さい。
そして以下のコードを記述します。
インベントリのお好みの位置に新しいスクリプトを作成し、"standard door module"という名前を付けて下さい。
そして以下のコードを記述します。
standard door module rotation rot; vector rotation_to = <0.0, 0.0, 90.0>; integer opened = FALSE; default { state_entry(){ rot = [[llGetLocalRot]](); } moving_end(){ if (opened){ rot = llGetLocalRot() / [[llEuler2Rot]](rotation_to * DEG_TO_RAD); }else{ rot = llGetLocalRot(); } } link_message(integer send, integer num, string str, key id){ if (str == "door"){ if (num){ // open llSetLocalRot(rot * llEuler2Rot(rotation_to * DEG_TO_RAD)); opened = TRUE; } else { // close llSetLocalRot(rot); opened = FALSE; } } } }
このモジュールはオブジェクト(prim)をZ軸基準に90度回転させます。
一般的なドアの動きを実現したものです。
しかしながら、先ほどのスライドドアと同様、あくまでも「開閉の動き」だけしか実現していません。
一般的なドアの動きを実現したものです。
しかしながら、先ほどのスライドドアと同様、あくまでも「開閉の動き」だけしか実現していません。
2つの「ロジック・モジュール」を載せましたが、これらのモジュールはどちらも、リンク・メッセージを受信したときに動き出します。
特に、リンクメッセージの文字列が"door"の場合にのみ「開閉動作」を行うようになっています。
リンクメッセージのinteger値がTRUEの場合は「開」、FALSEの場合は「閉」の動作です。
特に、リンクメッセージの文字列が"door"の場合にのみ「開閉動作」を行うようになっています。
リンクメッセージのinteger値がTRUEの場合は「開」、FALSEの場合は「閉」の動作です。
door main module
さて、それでは次に、この二つの「ロジック・モジュール」にリンクメッセージを送る部分のLSLを書いてみます。
インベントリのお好みの位置に新しいスクリプトを作成し、"door main module"という名前を付けて下さい。
コードは以下のようになります。
インベントリのお好みの位置に新しいスクリプトを作成し、"door main module"という名前を付けて下さい。
コードは以下のようになります。
door main module float auto_close_timer = 60.0; integer opened = FALSE; default { link_message(integer send, integer num, string str, key id){ if (str == "action"){ opened = (!opened) * (num == -1) + (num != FALSE) * (num != -1); llMessageLinked(LINK_SET,opened,"door",NULL_KEY); llSetTimerEvent(auto_close_timer * opened); } } timer(){ llSetTimerEvent(0.0); llMessageLinked(LINK_SET,FALSE,"action",NULL_KEY); } }
このロジックモジュールは、あらゆるドアに共通した性質・動作を実装したものです。
共通した性質とは「ドアには開・閉の2つの状態がある」という点です。
共通した動作とは「開けてから一定時間が経過すると自動的に閉まる」ことです。
スライドドアであろうと、普通のドアであろうと、開・閉の2状態があることには変わりないし、一定時間経つと自動で閉まる機能があってしかるべきでしょう。
つまりこのモジュールは「ドアの基本的な性質」を実現したものです。
あらゆるドアは全てこのモジュールをコアとして使います。
共通した性質とは「ドアには開・閉の2つの状態がある」という点です。
共通した動作とは「開けてから一定時間が経過すると自動的に閉まる」ことです。
スライドドアであろうと、普通のドアであろうと、開・閉の2状態があることには変わりないし、一定時間経つと自動で閉まる機能があってしかるべきでしょう。
つまりこのモジュールは「ドアの基本的な性質」を実現したものです。
あらゆるドアは全てこのモジュールをコアとして使います。
コードをざっとながめていただくと分かるかと思いますが、このモジュールはリンクメッセージ"action"を受信したときに動き、"door"というリンクメッセージを送信するだけの動きしかしません。
先ほど載せたスライドドア、スタンダードドアはどちらもリンクメッセージ"door"で動き出すようになっていましたから、このメインモジュールからのリンクメッセージを受けて動くということになります。
先ほど載せたスライドドア、スタンダードドアはどちらもリンクメッセージ"door"で動き出すようになっていましたから、このメインモジュールからのリンクメッセージを受けて動くということになります。
ドアの動きをスライド式にしたければ、スライド・ドア・モジュールを使い、一般的なドアにしたければスタンダード・ドア・モジュールを使います。
どちらのモジュールを使うにしても、メイン・モジュールは同一のものでOKになります。
どちらのモジュールを使うにしても、メイン・モジュールは同一のものでOKになります。
short touch module
さて、これでロジックモジュールができましたので、次に「イベントモジュール」を作ってみましょう。
「ドアの開閉するきっかけ」です。
インベントリのお好みの位置に新しいスクリプトを作成し、"short touch module"という名前を付けて下さい。
そして以下のコードを記述します。
「ドアの開閉するきっかけ」です。
インベントリのお好みの位置に新しいスクリプトを作成し、"short touch module"という名前を付けて下さい。
そして以下のコードを記述します。
short touch module float distance = 5.0; default { touch_start(integer detected){ if ([[llVecDist]](llGetPos(), [[llDetectedPos]](0)) <= distance){ llMessageLinked(LINK_SET,-1,"action",[[llDetectedKey]](0)); }else{ [[llInstantMessage]](llDetectedKey(0), "There is out of your reach. You can't touch. "); } } }
このモジュールは基本的にタッチイベントに反応しますが、距離の制限が付いています。
先頭に定義している変数distanceがタッチが有効になる距離です。
ここに記載した例(distance = 5.0)であれば、オブジェクトから5m以内ならタッチできますが、それより距離があるとタッチしたことになりません。
適切な距離からオブジェクトにタッチすると、リンクメッセージ"action"が送信されます。
このリンクメッセージはドアのメインモジュールで受信され、最終的にドアの開閉が行われます。
遠い位置からのタッチを無効にすることで、ドア開閉の動きはより自然になるはずです(ドアの側にいないのにタッチして開けられるのは不自然ですのでw)。
先頭に定義している変数distanceがタッチが有効になる距離です。
ここに記載した例(distance = 5.0)であれば、オブジェクトから5m以内ならタッチできますが、それより距離があるとタッチしたことになりません。
適切な距離からオブジェクトにタッチすると、リンクメッセージ"action"が送信されます。
このリンクメッセージはドアのメインモジュールで受信され、最終的にドアの開閉が行われます。
遠い位置からのタッチを無効にすることで、ドア開閉の動きはより自然になるはずです(ドアの側にいないのにタッチして開けられるのは不自然ですのでw)。
group touch module
もう一つイベントモジュールを作りましょう。
インベントリのお好みの位置に新しいスクリプトを作成し、"group touch module"という名前を付けて下さい。
インベントリのお好みの位置に新しいスクリプトを作成し、"group touch module"という名前を付けて下さい。
group touch module float distance = 5.0; default { touch_start(integer detected){ if (llVecDist(llGetPos(), llDetectedPos(0)) <= distance ){ if ([[llSameGroup]](llDetectedKey(0))){ llMessageLinked(LINK_SET,-1,"action",llDetectedKey(0)); }else{ llInstantMessage(llDetectedKey(0), "Group member only."); } }else{ llInstantMessage(llDetectedKey(0), "There is out of your reach. You can't touch. "); } } }
ほとんどショート・タッチと一緒ですが、グループ判定を付け加えてみました。
オブジェクトと同一グループをアクティブにしていないと反応しません。
グループメンバーが適切な距離からオブジェクトにタッチすると、リンクメッセージ"action"が送信されます。
そしてドアメインモジュールを経由し、ドアの開閉が行われることになります。
オブジェクトと同一グループをアクティブにしていないと反応しません。
グループメンバーが適切な距離からオブジェクトにタッチすると、リンクメッセージ"action"が送信されます。
そしてドアメインモジュールを経由し、ドアの開閉が行われることになります。
拡張モジュール
ロジック、イベントモジュールともに2つ載せましたが、どちらを使うかは用途に応じて選択すればOKです。
さらには、もっと他のモジュールを用意しても構いません。
さらには、もっと他のモジュールを用意しても構いません。
必要なことは、
1、イベントモジュールはリンクメッセージ"action"を送信する
2、ロジックモジュールはリンクメッセージ"door"を受信して動く
この2点です。
1、イベントモジュールはリンクメッセージ"action"を送信する
2、ロジックモジュールはリンクメッセージ"door"を受信して動く
この2点です。
さらに詳しく書くなら、リンクメッセージに指定する整数値と文字列は以下のような意味になります。
- イベントモジュールから送信するリンクメッセージ
整数値 | 文字列 | 意味 |
TRUE | "action" | ドアを開く |
FALSE | "action" | ドアを閉じる |
-1 | "action" | ドアが開いてれば閉じる、閉じていれば開く |
- ロジックモジュールで受信するリンクメッセージ
整数値 | 文字列 | 意味 |
TRUE | "door" | ドアを開く |
FALSE | "door" | ドアを閉じる |
以上のルールを逸脱しなければ、どのようなモジュールを追加することも可能です。
では拡張してみましょう。
sensor module
自動ドアに対応するため、センサーイベントを使ったイベントモジュールを追加してみます。
インベントリのお好みの位置に新しいスクリプトを作成し、"sensor module"という名前を付けて下さい。
せっかくですので、以前すくりぷたXさんがおっしゃっていた段階的な探知を実現してみましょう。
インベントリのお好みの位置に新しいスクリプトを作成し、"sensor module"という名前を付けて下さい。
せっかくですので、以前すくりぷたXさんがおっしゃっていた段階的な探知を実現してみましょう。
sensor module default { state_entry(){ [[llSensorRepeat]]("", "", AGENT, 15, PI, 10.0); } sensor(integer detected){ state wait_level2; } } state wait_level2 { state_entry(){ llSensorRepeat("", "", AGENT, 10, PI, 3.0); } sensor(integer detected){ state wait_level3; } no_sensor(){ state default; } } state wait_level3 { state_entry(){ llSensorRepeat("", "", AGENT, 5, PI, 0.25); } sensor(integer detected){ state activate; } no_sensor(){ state wait_level2; } } state activate { state_entry(){ llMessageLinked(LINK_SET,TRUE,"action",llDetectedKey(0)); llSensorRepeat("", "", AGENT, 5, PI, 5.0); } no_sensor(){ llMessageLinked(LINK_SET,FALSE,"action",llDetectedKey(0)); state wait_level3; } }
遠距離・長間隔の探知から近距離・短間隔の探知へと遷移していき、至近距離に入ったときにリンクメッセージTRUE,"action"を送信し、ドアを開きます。
逆に、至近距離に誰もいなくなったときにはリンクメッセージFALSE,"action"を送り、ドアを閉じます。
逆に、至近距離に誰もいなくなったときにはリンクメッセージFALSE,"action"を送り、ドアを閉じます。
これで自動ドアも作れるようになりました。
ショートタッチやグループタッチの代わりにセンサーモジュールを使えば、そのまま自動ドアになります。
ショートタッチやグループタッチの代わりにセンサーモジュールを使えば、そのまま自動ドアになります。
phantom door module
ロジックモジュールも拡張してみましょう。
バリアのように、薄れて消えるタイプのドアを作ってみます。
インベントリのお好みの位置に新しいスクリプトを作成し、"phantom door module"という名前を付けて下さい。
バリアのように、薄れて消えるタイプのドアを作ってみます。
インベントリのお好みの位置に新しいスクリプトを作成し、"phantom door module"という名前を付けて下さい。
phantom door module float alpha_max = 0.5; integer opened = FALSE; default { state_entry(){ llSetStatus(STATUS_PHANTOM, FALSE); } link_message(integer send, integer num, string str, key id){ if (str == "door"){ if (num){ if (!opened){ float a; // open for (a = alpha_max; a > 0.0; a -= 0.05){ [[llSetAlpha]](a, ALL_SIDES); llSleep(0.05); } llSetAlpha(0.0, ALL_SIDES); llSetStatus(STATUS_PHANTOM, TRUE); opened = TRUE; } } else { if (opened){ float a; // close for (a = 0.0; a < alpha_max; a += 0.05){ llSetAlpha(a, ALL_SIDES); llSleep(0.05); } llSetAlpha(alpha_max, ALL_SIDES); llSetStatus(STATUS_PHANTOM, FALSE); opened = FALSE; } } } } }
このモジュールはドアを透明にし、ファントム化(幻影)にします。
閉じるときは半透明(先頭の変数alpha_maxに定義した透明度)になります。
開閉のきっかけはリンクメッセージ"door"ですので、スライドドアやスタンダードドアの代わりとしてそのまま使えます。
閉じるときは半透明(先頭の変数alpha_maxに定義した透明度)になります。
開閉のきっかけはリンクメッセージ"door"ですので、スライドドアやスタンダードドアの代わりとしてそのまま使えます。
モジュール化の効果
以上でスクリプトを合計7本、載せました。
今回載せたスクリプトによって、何種類のドアが作れるでしょうか?
今回載せたスクリプトによって、何種類のドアが作れるでしょうか?
イベントモジュール、メインモジュール、ロジックモジュールの3つを組み合わせて使いますが、組み合わせは以下の通りです。
No | イベント | メイン | ロジック | ドアの機能 |
1 | short touch | main | slide | タッチするとスライドして開くドア |
2 | short touch | main | standard | タッチすると回転して開くドア |
3 | short touch | main | phantom | タッチすると透明になるドア |
4 | group touch | main | slide | スライドして開くグループ専用ドア |
5 | group touch | main | standard | 回転して開くグループ専用ドア |
6 | group touch | main | phantom | 透明になるグループ専用ドア |
7 | sensor | main | slide | スライドして開く自動ドア |
8 | sensor | main | standard | 回転して開く自動ドア |
9 | sensor | main | phantom | 透明になる自動ドア |
全部で9種類のドアが実現可能であることがおわかりいただけるかと思います。
スクリプトは7種類しか書いていないのに、9種類の機能が実現できる、これがモジュール化の利点です。
イベントモジュールやロジックモジュールを追加するたびに実現可能なドアは増えます。
例えば、イベント・ロジックともにもう一つずつ追加すると、スクリプトの数は9個で、作れるドアは4×4の16種類になります。かなりのお得感♪
スクリプトは7種類しか書いていないのに、9種類の機能が実現できる、これがモジュール化の利点です。
イベントモジュールやロジックモジュールを追加するたびに実現可能なドアは増えます。
例えば、イベント・ロジックともにもう一つずつ追加すると、スクリプトの数は9個で、作れるドアは4×4の16種類になります。かなりのお得感♪
まぁ、今回取り上げたのはドアですから、わざわざモジュール化しなくてもどれも簡単に作れるだろうとは思います。
しかし、より複雑なものを作るにあたり、モジュール化を念頭にして作っていくのと、ひたすら一つのスクリプトとして組み上げるのでは、後々の効率が大きく異なってきます。
例えば、今回作ったイベントモジュールなどは他のものを作るときにも流用可能なはずです。
しかし、より複雑なものを作るにあたり、モジュール化を念頭にして作っていくのと、ひたすら一つのスクリプトとして組み上げるのでは、後々の効率が大きく異なってきます。
例えば、今回作ったイベントモジュールなどは他のものを作るときにも流用可能なはずです。
仲間と一緒に複雑なシステムを組み上げるなんていうときにも便利です。
リンクメッセージでやり取りする内容さえ決めておけば、あとはそれぞれに分業が可能になりますので。
リンクメッセージでやり取りする内容さえ決めておけば、あとはそれぞれに分業が可能になりますので。
一つのスクリプトのサイズ上限を克服するための小細工が、ここまでくると実に有用な開発手法であることがお分かりいただけるかと思います。
なんでもかんでもモジュール化すれば良いとは言いませんけども(^^;
なんでもかんでもモジュール化すれば良いとは言いませんけども(^^;
モジュール化のデメリット
良いところばかり挙げて問題点に触れないわけにもいきませんので、最後に補足です。
一つのLSLモジュールは16kのサイズであると、最初に書きました。
逆説的に言うなら、モジュールを1つ作ると16kのサーバーリソースを消費する、とも言えます。
逆説的に言うなら、モジュールを1つ作ると16kのサーバーリソースを消費する、とも言えます。
モジュール化が便利だからといって何でもかんでも分割していたら、トータルのサイズはどんどん大きくなっていきますので、一概にメリットばかりではなくなってきます。
また、モジュール間のやりとりにはリンクメッセージを使っていますが、同一prim内のスクリプトが増加すると、一度のリンクメッセージでそれらが一斉に反応することになります。
listenなどに比べれば負荷は軽いとは言え、数が増せばそれなりに重くなっていくのは確かです。
listenなどに比べれば負荷は軽いとは言え、数が増せばそれなりに重くなっていくのは確かです。
最も良いのは、各モジュールを16k単位に分割することです。
ぴったり分けるのは困難ですから、可能な範囲でモジュールを一つにまとめ、なるべく無駄のない形に仕上げることが最良の道になります。
ぴったり分けるのは困難ですから、可能な範囲でモジュールを一つにまとめ、なるべく無駄のない形に仕上げることが最良の道になります。
例えば、一つのスクリプトで済むところを4つも5つものモジュールにするというのは少々安易です。
バランスの問題になりますので、難しいところではありますが。
バランスの問題になりますので、難しいところではありますが。