今年のLSLCONが無事終わりました。
振り返ればいろいろ準備不足な点がまだまだ浮かんでしまいますが、それでも、私は今年のLSLCONで自分が担当した作業に、自信を持って合格点をつけたいです。
このようなイベントは、決して誰か一人の人間が努力して運営できるものではありません。
特にLSLCONでは、多くの展示作品の出品に支えられている部分が大きいです。
運営メンバーだけでなく、より多くの人が、「来年もLSLCONがあるといいよね」と考え、行動することで、このイベントが継続していくはずです。
私は、(セカンドライフが続いている限り) LSLCONが続いて欲しいなと強く願っています。
10月になりました。私にとっては「LSL Conventionの月」です(笑
2007年に始まったこのイベントも今年で3回目です。
私は、昨年に続き今年も運営っぽい位置でこのイベントに参加しています。昨年あたりからの強いSL逆風?で、「今年は展示作品の応募は寂しくなるんじゃないかなぁ」などと弱気な予測を立てていたのですが、蓋を開けてみると60作品以上も応募があり、まだまだセカンドライフでスクリプトを書いている人はたくさんいるんだなぁと驚いていたりもします。
さて、そんなLSLCONですが、スクリプターさんから「前に見に行ったけど驚くようなスゴイ作品はなかった」というような声をたまーに耳にすることがあります。展示への出品をお願いしても、そういった理由で参加していただけない方などもいらっしゃるようで、ちょっと残念に思ったりしていました。(私たちの説明がまだまだ至らないという理由もあると思います)
ただ、正直に書きますと、私自身も2007年に第1回LSLCONのただの観客だった頃は、同じような感想を持っていました。
実際のところ、一通り「LSLでできること」が分かってしまうと、スクリプト作品を見て「そんなことができるのか!」と驚くことはほとんどなくなり、「あー、これはあの関数を使ってごにょごにょ」と作り方まで分かってしまうんですよね。そして、「自分にも作れるし、たいしたことないなぁ」という感想ばかりになってしまいがちです。
では何故、私や他の運営スタッフが自分たちの多大な時間を割いてこのイベントを続けているのでしょうか。
LSLではいろいろな事ができるので、展示作品はいくつかのカテゴリーに分かれています。例えば「アミューズメント」だったり「ツール」だったり。そして、特定の分野、例えば「乗り物」に特化したスクリプトばかりを書いている人もいれば、全般的にいろいろやっていたりする人もいたり、アニメーション関連ばかりをやっている人もいたりします。そのようないろいろな人の作品が一堂に会するのがLSL Conventionの展示です。
特定の分野に特化したスクリプトばかりを書いている人には、その分野での何らかのノウハウだったり、こだわりなどがあったりします。また、スクリプトを使うユーザーも様々で、分野によってはユーザーが「使いやすい」「一般的」と捉える機能に差があったりもします。
このような、機能的にただLSLの関数などを組み合わせて機能を実現するという部分以外のノウハウだったり、目線や価値観などを、得ようと思う人にとってはたくさん得られるのがLSL Conventionの展示(およびイベントそのもの)だと私は考えています。
このように書くと、何かを求めているスクリプターにとっては「不親切なイベント」なのかもしれませんね。
でも、「受け身のままでは楽しめない」という点は、Second Life住人なら頷いていただける方が多いんじゃないかと私は思っています。
スクリプトを書いている方なら、是非とも会場に足を運んでいただいて、スクリプター同士で苦労話に花を咲かせたりしてみてはいかがでしょうか?
そんなLSL Convention Japan 2009は、10月10日(土)からです!
お楽しみに!
※「運営に参加するともっと楽しいよ」という話もいつか書きます・・・いつか・・・;;
関連ブログ・リンク:
セカンドライフのメイングリッドのサーバー(SIM)のバージョンアップが今月行われて、1.27になりますが、このバージョンアップにはLSL関連で久々に大きな機能追加が行われています。それが「HTTP-IN」と呼ばれている機能です。
HTTP-INは何か?というのを端的に説明すると、「これまでのXML-RPCをHTTPでできるようにした」ものです。
具体的には、以下のようなことができるようになります。
HTTP-INで割り当てられるURLは、例えば以下のようなものです。
このURLの「sim3893」という部分は、リージョンのホストに対応しています。つまり、SIMが変わればURLも変わります。
実際には、URLのハンドリングがちょっと難しいので、そこを理解しておかないと利用するのが難しそうです。以下に、URLが無効になってしまう例を挙げます。
このように、URLが頻繁に変わってしまいます。
以下、HTTP-IN関連で追加された関数・イベント・定数の一覧です。
現在はまだNew Script SIMはバージョンアップされていないようなので、今日のスクリプターの会ではどこかバージョンアップされているSIMに移動して、HTTP-INをアレコレ試してみたいですね。
関連リンク:
さて、ドアシリーズもこのエントリで多分最後ですが、先のエントリのドアを「滑らかに(ゆっくり)開閉する」バージョンを作成してみました。
どういった動きなのかは、以下のムービーをご確認ください。
スクリプトは、以下のようになっています。タイマーによるドアの動き制御が追加されています。また、それに合わせてステート分割も行いました。
///////////////////////////////////////////////////////////////////// // ドアのサンプルスクリプト(door3.lsl) // タッチしたアバターの位置によって開く向きが変わる // 滑らか開閉バージョン // Released into the public domain by Hidenori Glushenko ///////////////////////////////////////////////////////////////////// // 初めに「door_init.lsl」スクリプトでプリムを変形させておいてください。 ///////////////////////////////////////////////////////////////////// rotation DOOR_ROT_ORG; // ドアの基準回転 float DOOR_DIV_EULER = 150.0; // ドアの開閉角度 float DOOR_OPEN_SEC = 10.0; // ドアが開いている時間(秒) float DOOR_MOVE_TARGET; integer TIMER_COUNT; integer TIMER_MAX = 40; // ドアの開閉速度 // 大きいとゆっくり default { state_entry() { llSetBuoyancy(1.0); llMinEventDelay( 0.01 ); llSetStatus(STATUS_PHANTOM, FALSE); llSetStatus(STATUS_BLOCK_GRAB, TRUE); state DoorClose; } } state DoorClose { touch_start(integer total_number) { DOOR_ROT_ORG = llGetRot(); // タッチしたアバターの位置関係を調べる vector av_offset = (llDetectedPos(0)-llGetPos())/DOOR_ROT_ORG; float av_offset_z = av_offset.z; // 位置関係に応じた回転を計算 DOOR_MOVE_TARGET = (av_offset_z/llFabs(av_offset_z)) *DOOR_DIV_EULER; state DoorOpen; } } state DoorOpen { state_entry() { TIMER_COUNT = 0; llSetTimerEvent(0.05); llSetStatus(STATUS_PHANTOM, TRUE); } touch_start(integer total_number) { // タッチされたらタイマー間隔を戻す llSetTimerEvent(0.05); } timer() { rotation rot; integer count = ++TIMER_COUNT; // 閉まる時用の変数補正 if (TIMER_COUNT > TIMER_MAX) { count = 2*TIMER_MAX - count; } // 閉まり始めた時の処理 if (TIMER_COUNT == TIMER_MAX + 1) { llSetStatus(STATUS_PHANTOM, TRUE); llSetTimerEvent(0.05); } // 現在のカウンタ変数から、角度を求める float x = PI_BY_TWO*count/(float)TIMER_MAX; float new_rot_euler = DOOR_MOVE_TARGET * llPow(llSin(x), 2.0); llRotLookAt(rot = (llEuler2Rot(<0.0,new_rot_euler,0.0> *DEG_TO_RAD))*DOOR_ROT_ORG,1.0,1.0); // 開ききったら止める if (TIMER_COUNT == TIMER_MAX) { // タイマー間隔を切り替える llSetTimerEvent(DOOR_OPEN_SEC); llSleep(0.2); llSetStatus(STATUS_PHANTOM, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); // 角度反映処理 llSetStatus(STATUS_PHYSICS, FALSE); // 角度反映処理 llSetRot(rot); // 閉まったら状態を変更してステート遷移 } else if (TIMER_COUNT >= (TIMER_MAX*2)) { llSleep(0.2); llSetStatus(STATUS_PHYSICS, TRUE); // 角度反映処理 llSetStatus(STATUS_PHYSICS, FALSE); // 角度反映処理 llSetRot(DOOR_ROT_ORG); state DoorClose; } } state_exit() { llSetStatus(STATUS_PHANTOM, FALSE); llSetTimerEvent(0.0); } }
ドアの開閉の動きは、llRotLookAt関数を使用しています。
この関数を使用すると、オブジェクトを(ディレイなく)回転させることができます。(回転がllTargetOmegaと同じようにビューア側での処理になるため)
ただし、これまたllTargetOmegaと同じように、サーバー上でオブジェクトが回転しているわけではないため、開いた状態をシミュレータ側に反映させる処理が必要になります。
また、現在はドアの動きをsinの2乗(llPow(llSin(x), 2.0) (xは0~π/2)という計算式を使っていますが、ここを変更すると、開く動きを変えられます。
タイマーでドアの角度を処理していく部分は、いろいろな書き方が考えられる部分で、もっとうまいやり方がありそうです。「こうするのがいい」というのがあれば、是非教えてください!
関連エントリ(ドアシリーズ):
さて、前のエントリで「タッチイベントで検出したアバターの位置(=llDetectedPos)が見た目とずれている」ということを書きましたが、実際これはどういった法則でずれているのか考えてみました。
:
:
:
!
実はこれ、私が以前ChairKitを作成した時に調べていたことと結局同じでした(笑)
結論としては次のようになっています。
さて、今回のテスト用に次の3つのスクリプトを用意しました。
スクリプトA:タッチされたアバターの座標へ移動する(llDetectedPos)
default { touch_start(integer total_number) { vector pos; llSetPos(pos = llDetectedPos(0)); llOwnerSay((string)pos); } }
スクリプトB:設定されているsittargetの位置へ移動する(オブジェクト名、sittargetのvectorは固定です)
default { touch_start(integer total_number) { llSensor("chair_base", NULL_KEY, PASSIVE | SCRIPTED, 10.0, PI); } sensor(integer num_detected){ vector pos; llSetPos(pos = (llDetectedPos(0) + <0.2,0,0.5>*llDetectedRot(0))); llOwnerSay((string)pos); } }
スクリプトC:タッチされたアバターの座標からsittargetを逆算して、求めた位置へ移動する
vector detectedPos2SitTarget(key avatar, vector pos, rotation rot){ vector tp = llGetAgentSize(avatar); pos = (pos + (llRot2Up(rot) * tp.z * 0.02638) - (llRot2Up(rot) * 0.4)); return pos; } default { touch_start(integer total_number) { key avatar = llDetectedKey(0); vector pos = detectedPos2SitTarget( llDetectedKey(0), llDetectedPos(0), llDetectedRot(0)); llOwnerSay((string)pos); llSetPos(pos); } }
このようなスクリプトを赤、黄、青の球のプリムに入れて、いろいろな状態でタッチしてみました。
以下の実験では、座っているプリムに <0.2, 0.0, 0.5> というsittargetが設定されています。
|
|
![]() |
![]() |
黄色(sittargetの位置へ移動)は、当然ですが、sittargetの位置へそのまま移動します。
赤は、sittargetの黄色ボールより上の位置に移動します。真上から見た図で、X、Y座標が同じであることが分かります。
この状態から、青いボールにタッチして移動させます。
|
![]() |
![]() |
![]() |
青いボールにタッチすると、黄色いボールと中心が重なる位置に移動します。
座っているプリムの位置を変えたり、アバターのアニメを変えたり(sittargetの位置は変えていません)、タイニーアバターになってみたりしても、この3つのボールの位置関係は同じままです。
■座標Aと座標Bの変換式
では、座標Aと座標B、つまり赤いボールと黄色いボールの間の座標の関係および変換はどのようになっているのでしょうか。ChairKitの作成段階(その前段階のバージョン1を作っていた頃に)いろいろ調べてたどり着いたのが、PWikiの次のページでした。
上記ページに、Strife Onizukaさんが作成した次のようなコードが掲載されています。
GetSitTarget
list GetSitTarget(integer prim, key av) {//WARNING: llGetObjectDetails can introduce an error that goes as far as the 5th decimal place! //This is highly unlikely to be ever noticed unless compounded over time. //Do not use while moving (like in a moving vehicle)!!! vector tp = llGetAgentSize(av); if(tp) { if(prim == LINK_THIS)//llGetLinkKey doesn't like LINK_THIS prim = llGetLinkNumber(); list details = [OBJECT_POS, OBJECT_ROT]; rotation f = llList2Rot(details = (llGetObjectDetails(llGetLinkKey(prim) , details) + llGetObjectDetails(av, details)), 1); rotation r = llList2Rot(details, 3) / f; return [((llList2Vector(details, 2) - llList2Vector(details, 0)) / f) + (llRot2Up(r) * tp.z * 0.02638) - <0.0, 0.0, 0.4>, r]; } return []; }//Written by Strife Onizuka
これが、「sitしているアバターのローカル座標から、(概算の)sittargetを取得する」関数です。
この関数ではローカル座標(sittargetは設定するプリム基準)を計算しているので分かりづらいですが、まとめてさらに変形すると以下のような計算式になっています。
[sittargetの座標] = [検出したアバターの座標]
+ llRot2Up([アバターの回転]) * [アバターの身長] * 0.02638
- llRot2Up([アバターの回転]) * 0.4
妙な定数が出てきて、正当性は何とも言えませんが、SSで見るように、実際にsittargetの位置とほぼ一致します。
ただ、標準的な人間アバターであればこの計算式はほぼ正しいようですが、タイニーアバターになった場合、やや誤差が大きくなります(下のSS参照。5cmほどずれています)
つまり、アバターの身長に依存する部分が上の式では単純な1次関数ですが、もう少し複雑な計算になっているのだと思われます。
が、この計算式でも現時点ではほとんど問題が無いと思います。
(PWikiに長い間掲載されていて、文句などがついていないと言うこともあります)
時間があれば、ここから「(概算)腰の位置」を求める関数も作っておこうかと思いますが、今日はここで力尽きました・・・・。
