Adsense

2012年9月30日日曜日

[LSL] ネギを投げるスクリプト

ネギを投げるスクリプト、、、ネギじゃなくても、モーションと組み合わせて「投げる」ということで結構苦労されている方も多いようなので、ひさしぶりに LSL の解説です。

で、みなさん苦労されているのは、いつもでてくる「グローバル座標軸」と「ローカル座標軸」、そして、アタッチしたときの軸の考え方、最後にダンス(アニメーション)中のアバタ―の向きの考え方が難しいのでは?と思います。

グローバルとローカル座標軸

グローバル座標軸とローカル座標軸の基本は以下の図になります。

グローバルローカル軸

これは、、OK ですね?

グローバル座標軸(東西)上で 1m 進むときのベクトル型の表現は <1.0, 0.0, 0.0> です。では、上の図のローカル座標軸(前後)で 1m 進むときのベクトル座標の表現はどうなるでしょう?
<1.0, 0.0, 0.0> としたいところですが、これではだめで、日本語で表現すると「今見ている方向の前に 1m 進むためには、東に X m、南に Y m、天に Z m 進む」としなければなりません。

えー、じゃー、ピタゴラスの定理を使って計算?ではありません。このような時の便利な関数が llRot2Fwd です。今向いている方向の前に1m進むためのグローバル座標軸上での移動距離を返してくれます。llRot2Fwd(今向いている方向) で 1m 前に進むためのベクトル型の数値を得ることができます。

では、今、向いている方向はどう取得するか、ということですが、それは llGetRot() になります。

なので、今向いている方向の前方に 1m 進むためのグローバル座標軸上の移動距離は

vector xv = llRot2Fwd(llGetRot());

でとれます。同様に今向いている方向の左右を yv、上下を zv とすると以下のようになります。

vector yv = llRot2Left(llGetRot());
vector zv = llRot2Up(llGetRot());

え? 50cm の場合は?うん、その場合は xv*0.5 でいいんですよ。3m の場合は xv*3 です。
前に1m、左に1m、上に1m の地点(targetV)までいくには、xv+yv+zv を移動すればいいわけです。

vector targetV = llRot2Fwd(llGetRot())+llRot2Left(llGetRot())+llRot2Up(llGetRot());

この長い targetV の式がポイントになります。

装着したオブジェクトとローテーション

ネギを投げるにはネギを「持って」いなければいけません。持つということは装着する、ということですが、スクリプトはその装着されたネギ(オブジェクト)に入ることになります。

すると、装着したオブジェクトの方向とアバタ―の方向は違ってきます。

装着軸

実線が装着したオブジェクトの前後左右上下軸で、破線がアバタ―の前後左右上下軸です。

装着したオブジェクト内のスクリプトでは両方のローテーションを取ることができます。

アバタ―の向き llGetRot()
オブジェクトの向き llGetLocalRot()

(正確に言うと、装着するオブジェクトが複数のプリムのリンクによって作成された場合は、ルートプリムか、子プリムかで変わりますが、まずは1プリム(もしくはスクリプトはルートプリムにしか入れない)をしっかりと理解しましょう)

イメージとしては、拳銃みたいなオブジェクトで、つねに銃身の向いている方向から弾丸を出したい、というような場合はオブジェクト(拳銃)の向きが重要ですよね。(あくまでイメージです。次のアニメーションの考え方を合わせないと投げるモーションや銃身から玉がでるスクリプトはうまくできませんよ)

アバタ―が向いている方向は、装着したオブジェクトの中から llGetRot() で取れる、と理解してくださいね。

ダンスをしている時のアバタ―/装着オブジェクトの向き

これが、、、落とし穴です。
ダンスをしていると、アバタ―が動きまわり、手にもったオブジェクトも動いているように見えます。でも、実はスクリプトから取れるアバタ―/オブジェクトの向きや位置はまったく変化していないのです。

うそーん?と思う方は以下の動画をご覧ください。3秒おきに llGetPos、llGetRot、llGetLocalPos、llGetLocalRot の数値をスクリプトが llOwnerSay で通知します。LocalPos や LocalRot は右手にもっているオブジェクトの既定装着場所からの相対的な位置・向きです。オブジェクトを持つ位置や向きはまったく変えていないのでゼロが並びます。llGetPos, llGetRot はアバタ―の位置と向きになりますよね。アバタ―が動けば変わるはず、、、、です。

アバターがダンスモーションで動き回っているのに、llGetPos() で取れる数値は踊っている時は <200.33750, 234.75000, 2495,57000> のままで、llGetRot() で取れる向きはゼロ(これは、常にゼロ、という意味ではなく、グローバル座標軸上で東を向いている状態です。向きが変わればゼロではありません)になっています。もちろん、ダンスを始める前、ダンスをやめた後で歩き回っている時の位置や向き(Z軸のみ)は変わっています。

ダンスを踊っている(モーションを再生している)時に動いているアバタ―って、ファントムのようにすり抜けるでしょ?実体はあくまでダンスを開始する直前の位置や向きのまま、モーションによってアバタ―が動いている「ように」見えている、とイメージしてください。なので、よく「ステージ上でダンスしているアバタ―をライトで追いかけるスクリプト」なんて話がでるのですが、ダンスしているアバタ―の位置は装着スクリプトなどでは取れないので簡単ではないのです。

相対位置からネギを投げる(REZする)

ではどうするか?ということですが、オブジェクトを投げる瞬間のアバタ―の位置もしくはオブジェクトが出現(REZ)される位置を、実体がいる場所からどのくらい離れているかを見つけるしかありません。実体との相対位置、相対角度は、実体がどんな位置にいようが、方向を向いていようが変りません。相対位置と角度がわかれば、「そのタイミング」でオブジェクトをREZ すればいい、ということですね。

相対位置と向きは、、、試行錯誤で見つけるしかありません。とはいえ、以下のようなスクリプトで相対位置と角度を探すことができます。

使い方:
・チャンネル 39 を使ったチャットコマンドです。もちろんソースから変更可能です。
・投げるオブジェクトを、スクリプトの入ったプリムのコンテンツフォルダーにいれます。(オブジェクトの属性は、物理属性でかつ一時にするとよいでしょう、、というか、してください)
プリムを装着して、/39 shoot でオブジェクトがでます。(物理設定されていれば、ポロン、、、と落ちますw。されていないとその場にとどまります。)
・上に飛ばしたい場合は /39 velz 10 といれると、ローカルのz軸(上下)の上方向に初速 10 m/s で rez されるように設定されます。/39 shoot で確認できます。
・前に飛ばしたい場合は /39 velx 10 といれます。前方かつ上方向の場合は /39 velz 10 のあとで、/39 velx 10 とすることで前方向と上方向を合成した「斜め前上」に向かって rez されます。
・位置は /39 posx 2 で前方向2m進んだ場所から rez される設定になります。/39 posx –2 で後ろに下がります。/39 posx -2 のあとで /39 posx –4 とすると、6m 下がった位置(-6)になります。posy や posz で左右、上下の調整が可能です。
・REZされるオブジェクトの向き(発射方向ではありません。REZされた瞬間のオブジェクトの向きです)は /39 rotz 10 などで z 軸で10度、という調整可能です。
・/39 info で設定している値を表示します。
・そのほかのコマンドもありますが、、、ソース見て下さいw
・最終的に相対位置と向きがきまったら、そのベクトル型の数値を 4行目の vector rezVel (投げ出す方向と速度)と 7行目の vector OFFSET に設定してあげると、/39 shoot だけで投げるようになります。
・ただし、以下のサンプルはエラー、例外処理がまったくありません。あくまで相対位置を探し、オブジェクトを発射するサンプルとして考えてくださいね。 

integer cmdChan = 39;
integer cmdHandle;
string rezObject = "";
vector rezVel;
vector rezRotV;
integer rezParam;
vector OFFSET;
default
{
    state_entry()
    {
        string s = llGetInventoryName(INVENTORY_OBJECT,0);
        if(s!=""){
            rezObject=s;
        }
        //オーナーの cmdChan のSayのみ
        cmdHandle = llListen(cmdChan,"",llGetOwner(),"");
        llListenControl(cmdHandle,TRUE);
    }
   
    attach(key id)
    {
        if(id!=NULL_KEY){
            llResetScript();
        }
    }

    listen(integer chan, string name, key id, string msg)
    {
        list t = llParseString2List(msg,[" "],[""]);
        string sCmd = llToUpper(llList2String(t,0));
        llOwnerSay("cmd:"+sCmd);
        if(sCmd=="SHOOT"){
            llOwnerSay(rezObject);
            if(rezObject!=""){
                llRezObject(rezObject,
                            //Position 投げ出す位置
                            llGetPos()+
                            llRot2Fwd(llGetRot())*OFFSET.x+
                            llRot2Left(llGetRot())*OFFSET.y+
                            llRot2Up(llGetRot())*OFFSET.z,
                            //Velocity 投げ出す方向と速度
                           //投げ出す方向とスピードはここの設定がポイント!!!

                            llRot2Fwd(llGetRot())*rezVel.x+
                            llRot2Left(llGetRot())*rezVel.y+
                            llRot2Up(llGetRot())*rezVel.z,
                            //Rotation 投げ出されるオブジェクトそのものの向き
                            llEuler2Rot(rezRotV)*llGetLocalRot(),
                            rezParam);
            }
            //実際に「投げる」場合はここに自分を消す関数をいれます。
            //複数プリムからなっている場合は llSetLinkPrimitiveParamsFast ですが、
            //プリムの「色」を複数使っている場合は message を使ってプリム間通信で
            //LINK されたプリムの表示・非表示をやります。単体なら llSetAlpha で。

//ここから下は微調整のためのスクリプト
        }else if(sCmd=="POSX"){
            OFFSET.x += llList2Float(t,1);
            llOwnerSay("NEW OFFSET:"+(string)OFFSET);
        }else if(sCmd=="POSY"){
            OFFSET.x += llList2Float(t,1);
            llOwnerSay("NEW OFFSET:"+(string)OFFSET);           
        }else if(sCmd=="POSZ"){
            OFFSET.x += llList2Float(t,1);
            llOwnerSay("NEW OFFSET:"+(string)OFFSET);           
        }else if(sCmd=="POS"){
            OFFSET = llList2Vector(t,1);
            llOwnerSay("NEW OFFSET:"+(string)OFFSET);           
        }else if(sCmd=="VELX"){
            rezVel.x += llList2Float(t,1);
            llOwnerSay("NEW VELOCITY:"+(string)rezVel);
        }else if(sCmd=="VELY"){
            rezVel.y += llList2Float(t,1);
            llOwnerSay("NEW VELOCITY:"+(string)rezVel);           
        }else if(sCmd=="VELZ"){
            rezVel.z += llList2Float(t,1);
            llOwnerSay("NEW VELOCITY:"+(string)rezVel);
        }else if(sCmd=="VEL"){
            rezVel = llList2Vector(t,1);
            llOwnerSay("NEW VELOCITY:"+(string)rezVel);
        }else if(sCmd=="ROTX"){
            rezRotV.x += llList2Float(t,1)*DEG_TO_RAD;
            llOwnerSay("NEW ROT-VECTOR:"+(string)rezRotV);
        }else if(sCmd=="ROTY"){
            rezRotV.y += llList2Float(t,1)*DEG_TO_RAD;
            llOwnerSay("NEW ROT-VECTOR:"+(string)rezRotV);           
        }else if(sCmd=="ROTZ"){
            rezRotV.z += llList2Float(t,1)*DEG_TO_RAD;
            llOwnerSay("NEW ROT-VECTOR:"+(string)rezRotV);           
        }else if(sCmd=="ROT"){
            rezRotV = llList2Vector(t,1)*DEG_TO_RAD;
            llOwnerSay("NEW ROT-VECTOR:"+(string)rezRotV);
        }else if(sCmd=="INFO"){
            llOwnerSay("Global Rot:"+(string)llRot2Euler(llGetRot())+", Local Rot:"+(string)llRot2Euler(llGetLocalRot()));
        }else if(sCmd=="TIMER"){
            if(llToUpper(llList2String(t,1))=="ON"){
                llSetTimerEvent(3.0);
                llOwnerSay("Timer On");
            }else{
                llSetTimerEvent(0.0);
                llOwnerSay("Timer Off");
            }
        }
    }
   
    timer()
    {
        llOwnerSay("llGetPos():"+(string)llGetPos()+",llGetRot():"+(string)(llRot2Euler(llGetRot())*RAD_TO_DEG));
        llOwnerSay("llGetLocalPos():"+(string)llGetLocalPos()+",llGetLocalRot():"+(string)(llRot2Euler(llGetLocalRot())*RAD_TO_DEG));
    }
}

位置、方向を決める長い llRot2Fwd, llRot2Left, llRot2Up と llGetRot の式は最終的には以下にまとめることができます。

//Position 投げ出す位置 の部分
llGetPos()+OFFSET*llGetRot();

//Velocity 投げ出す方向と速度 の部分
rezVel*llGetRot();

最後の仕上げは REZ するタイミング

こちらは前回のブログの記事をご覧いただければと思います。

mldu5.5+ のマクロでチャットコマンド

長くなりましたが、ご参考になれば幸いです~

[追記] 位置や回転(向き)をもっと理解したい~という方は、よろしければ過去に投稿した記事を参考にしてみてください。
[LSL] 位置と回転について (7回にわけて位置と回転について書いています)