2016年01月24日
飛行機をつくる:(1)~もっとも短い(?)飛行機のサンプル
スクリプトを担当させていただいているteCraftのてっちゃんがSLTVのインタビューで、「自分がスカイで作業せず、誰でも制作途中の状態が見れるようにしているのは、飛行機を作る仲間が増えると良いなと思っての事だ」とおっしゃっておられ、なるほどそういう意図でしたかと納得したので、飛行機を作る事をテーマにスクリプトについても若干書いてみようかなと思います。(なにしろスクリは作っているところを見せる事はできませんから!)
下のURLがてっちゃんの作業場です。インワールドのツールで造形をされているので、タイミングが合うと作り方を見ることが出来るでしょう。ほかのジャンルでも、ものづくりに興味がある方には参考になる事も多いかと存じます。
Hope Island/通称”てっちゃん城”または”てっちゃん御殿”
スクリなんて触ったことも無い!という方は対象にしませんので、多少大雑把な説明になるかもしれません。スクリを本当にこれから書いてみたい方はまずはBlack-Sheepさんの入門あたりをざっとご覧ください。サンプルも豊富でまとまっています。
さて、早速手始めに、下の枠内のスクリプトが飛行機のスクリプトのもっとも基本的な形だと思います。100行ちょっとの短いスクリプトですが、下の動画の様に、この程度でもそれなりに飛行機してくれます。
key pilot=NULL_KEY; //パイロットのUUID
float throttle=0.0; //スロットル状態
vector angular_motor=ZERO_VECTOR; //舵操作状態
//-----------------------------------------
//初期化関数 init()
//-----------------------------------------
init()
{
pilot=NULL_KEY; //pilotを初期化します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理を切ってその場に停止させます。
}
//-----------------------------------------
//エンジン設定関数 planeEngine()
//-----------------------------------------
planeEngine()
{
llSetVehicleType(VEHICLE_TYPE_AIRPLANE); //乗り物タイプを飛行機にします。
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE,0.0); //舵操作への反応速度を最高感度にします。
}
default
{
state_entry()
{
//初期化イベント
llSitTarget(<0.0,0.0,0.5>,ZERO_ROTATION); //アバターの座る位置
llSetClickAction(CLICK_ACTION_SIT); //クリックしたときの動作を「座る」にします。
llSetCameraEyeOffset(<-5.0,0.0,1.0>); //座った時のカメラの位置を指定します。
llSetCameraAtOffset(<2.0,0.0,0.0>); //座った時にカメラが見ている位置を指定します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理をオフにします。
planeEngine();
}
on_rez(integer start_param)
{
init();
}
changed(integer change)
{
//プリムやスクリプトに状態変化があった場合のイベント
if (change & CHANGED_LINK)
{
//リンクが変更になった
key _av=llAvatarOnSitTarget(); //座っているアバターのUUIDを取得します。
if (_av!=NULL_KEY) //NULL_KEYでない⇒誰かが座っている。
{
if (pilot==NULL_KEY) //pilotがNULL_KEY⇒誰かが初めて座った。
{
pilot=_av; //pilotに座った人のUUIDを設定
llRequestPermissions(pilot,PERMISSION_TAKE_CONTROLS); //パイロットのキーやマウス操作の監視の権限を要求します
}
}
else //座っているアバターのUUIDがNULL_KEYなら誰かが立ち上がったか、または最初から座っていません。
{
init();
}
}
}
run_time_permissions(integer permissions)
{
//権限が許可または不許可であった場合に発生するイベント
if (permissions & PERMISSION_TAKE_CONTROLS) //アバターがキーやマウス操作の監視を許可した場合
{
//各種のキー操作の監視と、通常の動作の停止を要求します
llTakeControls(CONTROL_UP | CONTROL_DOWN | CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT | CONTROL_ML_LBUTTON | CONTROL_LBUTTON, TRUE, FALSE);
llSetStatus(STATUS_PHYSICS,TRUE); //物理をオンにします。
}
}
control(key name, integer keyPress, integer edges)
{
//llTakeControls()で要求したキー・マウス操作が行われた場合に発生するイベント
integer _keyDown=keyPress & edges; //押されているキーのうちで状態が変ったキーは今押され始めたキーです。keyPressとedgesのビット論理和(AND)を取ると取得できます。
integer _keyUp=~keyPress & edges; //押されているキーの逆のうちで状態が変ったキーは今離されたキーです。keyPressのビット反転とedgesのANDを取ると取得できます。
//--------------------------------------------------------
//スロットル(前進速度)
//PgUp(CONTROL_UP)で増速、PgDwn(CONTROL_DOWN)で減速します。
//--------------------------------------------------------
if (keyPress & CONTROL_UP) throttle=throttle+0.01; //この書き方もできるし。。
if (keyPress & CONTROL_DOWN) throttle-=0.01; //この書き方も出来ます。
if (throttle>1.0) throttle=1.0; //最大を超えそうなら最大にします。
else if (throttle<0.0) throttle=0.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//エルロン(ロール・左右傾斜)
//←→キー(CONTROL_ROT_LEFT/CONTROL_ROT_RIGHT)で操作します。
//--------------------------------------------------------
if (keyPress & CONTROL_ROT_LEFT) angular_motor.x-=0.1;
if (keyPress & CONTROL_ROT_RIGHT) angular_motor.x+=0.1;
if (_keyUp & CONTROL_ROT_LEFT) angular_motor.x=0.0; //飛行機では通常、キーを離したらならすぐに回転力をゼロにリセットします。
if (_keyUp & CONTROL_ROT_RIGHT) angular_motor.x=0.0;
if (angular_motor.x>1.0) angular_motor.x=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.x<-1.0) angular_motor.x=-1.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//エレベーター(ピッチ・上下首ふり、水平尾翼)
//↑↓キー(CONTROL_FWD/CONTROL_BACK)で操作します。
//飛行機スクリプトでは慣習的に↑キーで機首下げです。
//↓キーで機首上げです。
//--------------------------------------------------------
if (keyPress & CONTROL_FWD) angular_motor.y+=0.1;
if (keyPress & CONTROL_BACK) angular_motor.y-=0.1;
if (_keyUp & CONTROL_FWD) angular_motor.y=0.0;
if (_keyUp & CONTROL_BACK) angular_motor.y=0.0;
if (angular_motor.y>1.0) angular_motor.y=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.y<-1.0) angular_motor.y=-1.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//ラダー(ヨー・左右首ふり、垂直尾翼)
//SHIFT+←→キー(CONTROL_LEFT/CONTROL_RIGHT)で操作します。
//--------------------------------------------------------
if (keyPress & CONTROL_LEFT) angular_motor.z+=0.1;
if (keyPress & CONTROL_RIGHT) angular_motor.z-=0.1;
if (_keyUp & CONTROL_LEFT) angular_motor.z=0.0;
if (_keyUp & CONTROL_RIGHT) angular_motor.z=0.0;
if (angular_motor.z>1.0) angular_motor.z=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.z<-1.0) angular_motor.z=-1.0; //最少を下回りそうなら最小にします。
llSetForce(<0.0,0.0,llGetMass()*9.8*throttle>,TRUE); //揚力を設定
//速度の最大値を27.0m/sで、それに0.0~1.0をかけて設定します。
llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<27.0*throttle,0.0,0.0>);
//ロール、ピッチ、ヨー回転にそれぞれ最大値に現在の状態をかけて設定します。
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<10.0*angular_motor.x,1.0*angular_motor.y,0.5*angular_motor.z>);
}
}
スクリプトのテストは簡単です。何か適当なプリムを作って、中に新規スクリプトを作り、上の枠内の全てをペーストして保存し、座ると起動します。
前後左右の矢印キーで上下左右、PageUpとPageDownキーで速度が増減します。
じっくり見るには上の枠内では読みにくいと思いますので、LSLエディターをダウンロードして起動した後に、枠内をコピー&ペーストし、CTRL+Dキーを押して整形してください。F6キーを押して、「LSL Syntax seems OK(LSLの文法は見たところOKだ!)」とダイアログが出れば成功です。
ではさっそく、上から順に中身を見ていきます。
//初期化関数 init()
//-----------------------------------------
init()
{
pilot=NULL_KEY; //pilotを初期化します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理を切ってその場に停止させます。
}
//-----------------------------------------
//エンジン設定関数 planeEngine()
//-----------------------------------------
planeEngine()
{
llSetVehicleType(VEHICLE_TYPE_AIRPLANE); //乗り物タイプを飛行機にします。
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE,0.0); //舵操作への反応速度を最高感度にします。
}
オレンジ色の部分は「グローバル変数」というものを「これらを使います」と宣言している部分です。グローバル(global)とは「地球全体の,世界的な,グローバルな」といった意味ですが、つまりこのスクリの中であればどこであっても使える変数、という意味です。「=」記号は「等しい」ではなく、「代入しなさい」という命令です。スクリの中での「等しい」は「==」と=記号を二つ並べます。
次に来る白色の部分は「関数」という物を宣言しています。形式は単純で、
名前(必要なら引数)
{
中身いろいろ
}
という形をとります。高校生の頃に(いやいやながら)勉強した数学の関数「y=f(x)」とか、「z=f(x,y)」とかいうあれと同じで、名前を決めてその中身で行う計算を定義しています。本当に名前を「f」とか「g」とか数学の教科書の様にしても良いのですが(笑)、後からみて分かるように何をするものであるのか名前を工夫しておくのが普通です。
一度このようにして冒頭に作っておくと、スクリのなかではどこであっても、名前(引数があるなら値)と名前と引数を与えてやれば、いつでも呼び出して実行が出来るようになります。
このスクリでは無理に作る必要もないのですが、後々何度も使いそうな定型の処理は関数化しておくと直すのが楽です。
「//」とスラッシュを二つ並べた以降は、改行されるまでは実行されないメモになります。このスクリでは少しやりすぎですが、こうやって作っている時に「//なんだかメモ」というのをこまめに書き残しておくと、後々改造する時に自分が何をしたくてこうやったのかを自分宛のメモとして残しておくことが出来ます。(経験的に2か月もするとすっかり忘れてしまいます!)
init()
名前の通り、スクリで使う変数やパラメーターを初期化する内容を集めておくのに作りました。今は不要ですが、後々例えば音を再生したり、着陸したら車スクリと同じ動きをするように変化させたり改造していくと、パイロットが降りたりエンジンを停止させたりした時にスクリを初期状態に戻したくなる事が幾つものタイミングであるはずなので、同じ処理をしそうな初期化はあらかじめ全部ここにまとめておく、という前提で用意しました。今のところは内容的にはパイロットのUUIDをNULL_KEY(無効)に設定し、物理プリムである事をやめて通常のプリムに戻しています。
planeEngine()
飛行機として飛ぶことが出来る様に、スクリの「乗り物設定」をしている部分です。最も単純な飛行機スクリに必要な乗り物設定はこの二つだけです。手始めに「VEHICLE_TYPE_AIRPLANE」つまり「乗り物タイプは飛行機」を設定し、引き続き「VEHICLE_ANGULAR_MOTOR_TIMESCALE」をゼロつまり「乗り物の回転操作の反応感度を可能な限り今すぐ」に書き換えています。
この「default」の部分の{から}までの中身が、具体的なスクリの動作を書く部分になります。「default」とは「デフォルトの状態」を書く部分で、スクリの中に必ず一つなければなりません。ほかの「状態」を作る事も出来るのですがよっぽど特殊な事でなければ作る必要が無いので、「全体で使う変数と関数以外はdefaultの中に書くものだ」というお約束で十分です。
default{ }の中にはさらに{と}で開始と終了を記した、「state_entry()」や「changed()」といった大きなブロックが内蔵されています。これらは用語では「イベント」と呼ばれていて、このスクリの場合は図にすると下の様になります。
セカンドライフのスクリは全てこのような「~したら中身を実行する」という部品の中に、「その状態が発生したら実行」する内容を記述していくことになります。
スクリプトを自由に書けるレベルの人はこの「~したら**する」という物を上の図のような平面図だけではなく、関係性や順序でもイメージします。(本当に慣れてくると関係図が"見え”ます。つまり、脳の視覚野を本当に使っているのだと思います)
例えばこの飛行機の場合は、
0)スクリが保存されたりREZされたりしたら最初の設定をする
1)人が座る⇒キー操作の受付開始
2)人がキー操作取得を許可⇒物理にして動けるようにする
3)キー操作をした⇒旋回やスピードの変更を実行
4)人が立ち上がった⇒乗り物として動くことをやめて初期化
というストーリーがまず思い描かれます。そこでそれらに使えそうなイベントを下のURLで探すと、該当するものがこのスクリに登場するイベントである、という事になりますので、具体的にそれらの中に役割分担を割り振って書いていく事になります。
http://wiki.secondlife.com/wiki/Category:LSL_Events/ja
図にしてみると上の図のようになっていて、実行に順序があるのは「座った(control)」から「許可された(run_time_permissions)」の所だけで、他はいつでも起こり得る独立した要素になっています。乗り物スクリは車でも船でも、原理的には上の図のような構造になっているのですが、ある程度のレベルのスクリプターは常にこのような図のどの要素で何を行わせればよいのかを、作りたい事を要素に分解して思い描いています。これを思い描けるかどうかが好きな物を自由に作れるか、の分かれ目になります。
スクリで何かを自由に作れるレベルになりたい場合は、まずはLSLのイベントは丸暗記出来るまで何度も読み返して記憶すべきです。何故なら、イベントの種類と内容を知っているだけで「~したら**する」の「~したら」の部分は全部知っていることになるからです。後半の「**する」の部分はそれだけを”Google”ればサンプルスクリはたくさん見つかりますので、見つけたサンプルを抜粋してイベントの中に詰め込んでいく事になります。
state_entry
スクリが保存されたかリセットされた時にだけ行えばよい初期設定をまとめています。具体的には、アバターが座った時の位置を指定し(着席が出来るようにし)、マウスを乗せたときのアイコンが椅子マークになるようにし、座った時のカメラ位置を指定し、念の為物理を切り、冒頭で作った関数planeEngine()を実行して乗り物設定に飛行機を設定させています。
後は、プリムを物理にすれば飛行機になりますし、物理を切ればその場で停止してくれます。準備完了です。
on_rez
例えば飛行中に「取る」メニューでテイクされた場合などには飛行中の状態が残っているので、REZされた際には関数init()を実行して念の為初期化、という事を行っています。llResetScript()を使ってスクリ自体をリセットしてしまっても今の場合は良いのですが、ユーザー設定を記憶させておくなどの要素が使えなくなってしまうので、乗り物の場合はリセットよりも自分で初期化すべきものを初期化してやる方が良い気がします。
changed
LSLではifは、「(」から「)」の中身の計算結果がゼロであれば次の{から}の中身は実行されず、ゼロ以外(マイナスもOK)であれば実行されるという場合分けになります。
記号「&」はビット演算子と言われる計算記号で、+-×÷と同じような数式の記号です。Windowsの電卓を起動して「表示(V)→プログラマ(P)」として、適当な数字を入力した後に「2進」を選ぶと0と1が並びます。いわゆる二進数ですが、「&」は、数字をこのような2進数にした時に、二つの数字の間で両方とも1である場所は1、片方または両方が0である場所は0にした数字を計算します。例えば、数字の7は「0111」、数字の13は「1101」なので、「&」の結果は「0101」つまり5になります。
LSLのchangedイベントでは、イベント引数の変数changeとCHANGED_LINKとの&計算が0でない場合にはリンクが変更されたという意味になります。また、アバターがプリムに座るとアバターは座ったプリムにリンクされますので、誰かが座った場合にはリンクが変化し、この部分が実行されます。
llAvatarOnSitTarget()を実行した結果がNULL_KEYでない場合は誰かが座ったという事ですので、一旦llAvatarOnSitTarget()の実行結果をUUIDを保存する変数_avに設定して、「_av!=NULL_KEY」で確認しています。「!=」は「等しくない」という比較計算を表す記号ですので、つまり「変数_avはNULL_KEY(無効)」でない⇒「誰か座っている」という意味になります。
この変数_avの様に、スクリの冒頭で使う事を宣言せず、イベントの中など含まれている領域で使用を宣言された変数は「ローカル変数」と言います。自分を囲んでいる{と}の間でだけ有効で、その外側では使えません。
_avが無効でない事を確認した後にさらに、「pilot==NULL_KEY」で変数「パイロット」が無効である場合だけという条件を規定しています。現在のこの短いスクリでは不要なのですが、二人乗りの飛行機にしたい!とか後々の改造の場合に効いてくる(かもしれない)お約束の手順です。
そして、_avが無効(NULL_KEY)でない場合だけ、llRequestPermissionsを唱えてキー操作の監視の許可を要求しています。
その後に出てくる「else」は、「そうでなければ」の意味で、「if (_av!=NULL_KEY)」と対になっています。つまり、_av!=NULL_KEY(誰か座っている)が正しい間はelse以後は実行されませんが、誰も座っていないのにリンクが変更になるとこの「else」以後が実行されます。内容的にはスクリの冒頭で作っておいた関数init()をここでも実行し、スクリのパラメーターを初期化させています。on_rezでもinit()を呼び出しています。このように同じことをあちこちでさせたい場合には関数を上の方で書いておくと、関数だけいじれば全体に反映される、という後々の修正の楽さを確保できます。
run_time_permissions
キー操作の許可が得られるとこのイベントが実行されるので、llTakeControls()を唱えて監視したいキー操作項目を指定します。記号「|」(SHIFT+¥)は「&」と同じくビット演算子で、こちらはいずれか片方が1である部分を1にした数字を計算します。
また、物理をここでオンにして、state_entryでplaneEngineを実行して設定しておいた乗り物設定が有効になります。
control
ユーザーが行ったキー操作を具体的に解釈して飛行機として作動させている中心部です。
この部分はコメントにある通り、「今押されたキー」と「今押すのをやめたキー」を計算で割り出しています。controlイベントではkeyPressの位置の引数に現在押されているキーが、edgesの位置の引数には変化があったキーが格納されて呼び出されてきます。また記号「~」は&と同じくビット計算で、0と1を全てのビット位置で逆転させる計算をします。なので、「keyPress & edges」とは、「現在押されていて変化があったキー」のビット位置だけが1になる計算ですし、「~keyPress & edges」は「現在押されているキーでないキーで変化があったキー」のビット位置だけが1になる計算になります。つまり前者は、「今押されたキー」ですし、後者は「今押すのをやめたキー」の意味になります。使う使わないはともかくとして、この2者の計算も乗り物ではお約束ですので、changedイベントの冒頭でとりあえず計算しておきます。
以後の部分はコメントメモにある通り具体的に速度や舵の操縦状態の解釈になっているのですが、このスクリプトでは小数点付数字(float)の変数trottleに0~1の数字を、また、三次元vectorの変数angular_motorのx、y、zの各軸に0~1の値を設定しています。この意図は、0=0%、1=100%という意味です。このようにしておき、最後の部分の
の所で乗り物機能に速度と回転を指定しています。
最初から最大値~最小値までを足したり引いたりしても良いのですが、そういう風にするとこのchangedイベントの中にあるキー操作の解釈のif文全部を書き換えないと後で微調整が出来ません。乗り物は特に作った後の微調整が勝負を決めるスクリになりがちですので、このタイプの、
とりあえず0~1の間の値を入れておいて最後に最大値をかけて反映する
というパターンはお約束のテクニックとして覚えておくと良いと思います。
もう少し詳しくいうと、これは「線形補間」というテクニックの省略された姿で、線形補間の計算式の本来の姿は、
中間結果=(1.0-T)×最小値+T×最大値
という形で、変数Tは0~1を取ります。Tがゼロの時は最小値になり、Tが1の時には最大値になるようにして中間の状態も作れる形式で、今の場合は最小値はゼロなので前半が省略された形になっている、という事です。
さて、このスクリが特徴的な行がたった一つだけあります。それは、
llSetForce(<0.0,0.0,llGetMass()*9.8*throttle>,TRUE); //揚力を設定
という命令です。llSetForce()はプリムにちからをかけ続ける命令なのですが、関数の内容を日本語で言うと、「自分の真上方向に重さ×9.8×スロットルの値分の力をかけ続けなさい」という意味になっています。9.8は高校で習った重力加速度そのもので、これに重さがかかりますので、結局、スロットルが100%で飛行機が水平なら重力による力と反対方向に重力と同じだけの力をかけ続けなさい、という命令になっています。
殆どの飛行機スクリのサンプルではこの命令の代わりに重力を小さくする命令を使っていて、実際にその方がこんな余計な計算をしなくて済む分簡単になります。
では、何故自分の上方向に力をかけるのでしょうか?背面になれば重力が倍になるのに??
こういう小さなところにちょっとしたアレンジが入るだけでも、個性的な動き(味付け)をした乗り物が作れます。私の判断ではこの理由でこちらの方が良いと思っています。
貴方なら、どんな味付けにしますか?
次回はこのスクリプトにもう少し高度なカメラ操作をつけてみることにします。
次を読む
下のURLがてっちゃんの作業場です。インワールドのツールで造形をされているので、タイミングが合うと作り方を見ることが出来るでしょう。ほかのジャンルでも、ものづくりに興味がある方には参考になる事も多いかと存じます。
Hope Island/通称”てっちゃん城”または”てっちゃん御殿”
スクリなんて触ったことも無い!という方は対象にしませんので、多少大雑把な説明になるかもしれません。スクリを本当にこれから書いてみたい方はまずはBlack-Sheepさんの入門あたりをざっとご覧ください。サンプルも豊富でまとまっています。
さて、早速手始めに、下の枠内のスクリプトが飛行機のスクリプトのもっとも基本的な形だと思います。100行ちょっとの短いスクリプトですが、下の動画の様に、この程度でもそれなりに飛行機してくれます。
key pilot=NULL_KEY; //パイロットのUUID
float throttle=0.0; //スロットル状態
vector angular_motor=ZERO_VECTOR; //舵操作状態
//-----------------------------------------
//初期化関数 init()
//-----------------------------------------
init()
{
pilot=NULL_KEY; //pilotを初期化します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理を切ってその場に停止させます。
}
//-----------------------------------------
//エンジン設定関数 planeEngine()
//-----------------------------------------
planeEngine()
{
llSetVehicleType(VEHICLE_TYPE_AIRPLANE); //乗り物タイプを飛行機にします。
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE,0.0); //舵操作への反応速度を最高感度にします。
}
default
{
state_entry()
{
//初期化イベント
llSitTarget(<0.0,0.0,0.5>,ZERO_ROTATION); //アバターの座る位置
llSetClickAction(CLICK_ACTION_SIT); //クリックしたときの動作を「座る」にします。
llSetCameraEyeOffset(<-5.0,0.0,1.0>); //座った時のカメラの位置を指定します。
llSetCameraAtOffset(<2.0,0.0,0.0>); //座った時にカメラが見ている位置を指定します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理をオフにします。
planeEngine();
}
on_rez(integer start_param)
{
init();
}
changed(integer change)
{
//プリムやスクリプトに状態変化があった場合のイベント
if (change & CHANGED_LINK)
{
//リンクが変更になった
key _av=llAvatarOnSitTarget(); //座っているアバターのUUIDを取得します。
if (_av!=NULL_KEY) //NULL_KEYでない⇒誰かが座っている。
{
if (pilot==NULL_KEY) //pilotがNULL_KEY⇒誰かが初めて座った。
{
pilot=_av; //pilotに座った人のUUIDを設定
llRequestPermissions(pilot,PERMISSION_TAKE_CONTROLS); //パイロットのキーやマウス操作の監視の権限を要求します
}
}
else //座っているアバターのUUIDがNULL_KEYなら誰かが立ち上がったか、または最初から座っていません。
{
init();
}
}
}
run_time_permissions(integer permissions)
{
//権限が許可または不許可であった場合に発生するイベント
if (permissions & PERMISSION_TAKE_CONTROLS) //アバターがキーやマウス操作の監視を許可した場合
{
//各種のキー操作の監視と、通常の動作の停止を要求します
llTakeControls(CONTROL_UP | CONTROL_DOWN | CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT | CONTROL_ML_LBUTTON | CONTROL_LBUTTON, TRUE, FALSE);
llSetStatus(STATUS_PHYSICS,TRUE); //物理をオンにします。
}
}
control(key name, integer keyPress, integer edges)
{
//llTakeControls()で要求したキー・マウス操作が行われた場合に発生するイベント
integer _keyDown=keyPress & edges; //押されているキーのうちで状態が変ったキーは今押され始めたキーです。keyPressとedgesのビット論理和(AND)を取ると取得できます。
integer _keyUp=~keyPress & edges; //押されているキーの逆のうちで状態が変ったキーは今離されたキーです。keyPressのビット反転とedgesのANDを取ると取得できます。
//--------------------------------------------------------
//スロットル(前進速度)
//PgUp(CONTROL_UP)で増速、PgDwn(CONTROL_DOWN)で減速します。
//--------------------------------------------------------
if (keyPress & CONTROL_UP) throttle=throttle+0.01; //この書き方もできるし。。
if (keyPress & CONTROL_DOWN) throttle-=0.01; //この書き方も出来ます。
if (throttle>1.0) throttle=1.0; //最大を超えそうなら最大にします。
else if (throttle<0.0) throttle=0.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//エルロン(ロール・左右傾斜)
//←→キー(CONTROL_ROT_LEFT/CONTROL_ROT_RIGHT)で操作します。
//--------------------------------------------------------
if (keyPress & CONTROL_ROT_LEFT) angular_motor.x-=0.1;
if (keyPress & CONTROL_ROT_RIGHT) angular_motor.x+=0.1;
if (_keyUp & CONTROL_ROT_LEFT) angular_motor.x=0.0; //飛行機では通常、キーを離したらならすぐに回転力をゼロにリセットします。
if (_keyUp & CONTROL_ROT_RIGHT) angular_motor.x=0.0;
if (angular_motor.x>1.0) angular_motor.x=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.x<-1.0) angular_motor.x=-1.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//エレベーター(ピッチ・上下首ふり、水平尾翼)
//↑↓キー(CONTROL_FWD/CONTROL_BACK)で操作します。
//飛行機スクリプトでは慣習的に↑キーで機首下げです。
//↓キーで機首上げです。
//--------------------------------------------------------
if (keyPress & CONTROL_FWD) angular_motor.y+=0.1;
if (keyPress & CONTROL_BACK) angular_motor.y-=0.1;
if (_keyUp & CONTROL_FWD) angular_motor.y=0.0;
if (_keyUp & CONTROL_BACK) angular_motor.y=0.0;
if (angular_motor.y>1.0) angular_motor.y=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.y<-1.0) angular_motor.y=-1.0; //最少を下回りそうなら最小にします。
//--------------------------------------------------------
//ラダー(ヨー・左右首ふり、垂直尾翼)
//SHIFT+←→キー(CONTROL_LEFT/CONTROL_RIGHT)で操作します。
//--------------------------------------------------------
if (keyPress & CONTROL_LEFT) angular_motor.z+=0.1;
if (keyPress & CONTROL_RIGHT) angular_motor.z-=0.1;
if (_keyUp & CONTROL_LEFT) angular_motor.z=0.0;
if (_keyUp & CONTROL_RIGHT) angular_motor.z=0.0;
if (angular_motor.z>1.0) angular_motor.z=1.0; //最大を超えそうなら最大にします。
else if (angular_motor.z<-1.0) angular_motor.z=-1.0; //最少を下回りそうなら最小にします。
llSetForce(<0.0,0.0,llGetMass()*9.8*throttle>,TRUE); //揚力を設定
//速度の最大値を27.0m/sで、それに0.0~1.0をかけて設定します。
llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<27.0*throttle,0.0,0.0>);
//ロール、ピッチ、ヨー回転にそれぞれ最大値に現在の状態をかけて設定します。
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<10.0*angular_motor.x,1.0*angular_motor.y,0.5*angular_motor.z>);
}
}
スクリプトのテストは簡単です。何か適当なプリムを作って、中に新規スクリプトを作り、上の枠内の全てをペーストして保存し、座ると起動します。
前後左右の矢印キーで上下左右、PageUpとPageDownキーで速度が増減します。
じっくり見るには上の枠内では読みにくいと思いますので、LSLエディターをダウンロードして起動した後に、枠内をコピー&ペーストし、CTRL+Dキーを押して整形してください。F6キーを押して、「LSL Syntax seems OK(LSLの文法は見たところOKだ!)」とダイアログが出れば成功です。
ではさっそく、上から順に中身を見ていきます。
key pilot=NULL_KEY; //パイロットのUUID
float throttle=0.0; //スロットル状態
vector angular_motor=ZERO_VECTOR; //舵操作状態
//-----------------------------------------float throttle=0.0; //スロットル状態
vector angular_motor=ZERO_VECTOR; //舵操作状態
//初期化関数 init()
//-----------------------------------------
init()
{
pilot=NULL_KEY; //pilotを初期化します。
llSetStatus(STATUS_PHYSICS,FALSE); //物理を切ってその場に停止させます。
}
//-----------------------------------------
//エンジン設定関数 planeEngine()
//-----------------------------------------
planeEngine()
{
llSetVehicleType(VEHICLE_TYPE_AIRPLANE); //乗り物タイプを飛行機にします。
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE,0.0); //舵操作への反応速度を最高感度にします。
}
オレンジ色の部分は「グローバル変数」というものを「これらを使います」と宣言している部分です。グローバル(global)とは「地球全体の,世界的な,グローバルな」といった意味ですが、つまりこのスクリの中であればどこであっても使える変数、という意味です。「=」記号は「等しい」ではなく、「代入しなさい」という命令です。スクリの中での「等しい」は「==」と=記号を二つ並べます。
次に来る白色の部分は「関数」という物を宣言しています。形式は単純で、
名前(必要なら引数)
{
中身いろいろ
}
という形をとります。高校生の頃に(いやいやながら)勉強した数学の関数「y=f(x)」とか、「z=f(x,y)」とかいうあれと同じで、名前を決めてその中身で行う計算を定義しています。本当に名前を「f」とか「g」とか数学の教科書の様にしても良いのですが(笑)、後からみて分かるように何をするものであるのか名前を工夫しておくのが普通です。
一度このようにして冒頭に作っておくと、スクリのなかではどこであっても、名前(引数があるなら値)と名前と引数を与えてやれば、いつでも呼び出して実行が出来るようになります。
このスクリでは無理に作る必要もないのですが、後々何度も使いそうな定型の処理は関数化しておくと直すのが楽です。
「//」とスラッシュを二つ並べた以降は、改行されるまでは実行されないメモになります。このスクリでは少しやりすぎですが、こうやって作っている時に「//なんだかメモ」というのをこまめに書き残しておくと、後々改造する時に自分が何をしたくてこうやったのかを自分宛のメモとして残しておくことが出来ます。(経験的に2か月もするとすっかり忘れてしまいます!)
init()
名前の通り、スクリで使う変数やパラメーターを初期化する内容を集めておくのに作りました。今は不要ですが、後々例えば音を再生したり、着陸したら車スクリと同じ動きをするように変化させたり改造していくと、パイロットが降りたりエンジンを停止させたりした時にスクリを初期状態に戻したくなる事が幾つものタイミングであるはずなので、同じ処理をしそうな初期化はあらかじめ全部ここにまとめておく、という前提で用意しました。今のところは内容的にはパイロットのUUIDをNULL_KEY(無効)に設定し、物理プリムである事をやめて通常のプリムに戻しています。
planeEngine()
飛行機として飛ぶことが出来る様に、スクリの「乗り物設定」をしている部分です。最も単純な飛行機スクリに必要な乗り物設定はこの二つだけです。手始めに「VEHICLE_TYPE_AIRPLANE」つまり「乗り物タイプは飛行機」を設定し、引き続き「VEHICLE_ANGULAR_MOTOR_TIMESCALE」をゼロつまり「乗り物の回転操作の反応感度を可能な限り今すぐ」に書き換えています。
default
{
(なんだか色々)
}
{
(なんだか色々)
}
この「default」の部分の{から}までの中身が、具体的なスクリの動作を書く部分になります。「default」とは「デフォルトの状態」を書く部分で、スクリの中に必ず一つなければなりません。ほかの「状態」を作る事も出来るのですがよっぽど特殊な事でなければ作る必要が無いので、「全体で使う変数と関数以外はdefaultの中に書くものだ」というお約束で十分です。
default{ }の中にはさらに{と}で開始と終了を記した、「state_entry()」や「changed()」といった大きなブロックが内蔵されています。これらは用語では「イベント」と呼ばれていて、このスクリの場合は図にすると下の様になります。
セカンドライフのスクリは全てこのような「~したら中身を実行する」という部品の中に、「その状態が発生したら実行」する内容を記述していくことになります。
スクリプトを自由に書けるレベルの人はこの「~したら**する」という物を上の図のような平面図だけではなく、関係性や順序でもイメージします。(本当に慣れてくると関係図が"見え”ます。つまり、脳の視覚野を本当に使っているのだと思います)
例えばこの飛行機の場合は、
0)スクリが保存されたりREZされたりしたら最初の設定をする
1)人が座る⇒キー操作の受付開始
2)人がキー操作取得を許可⇒物理にして動けるようにする
3)キー操作をした⇒旋回やスピードの変更を実行
4)人が立ち上がった⇒乗り物として動くことをやめて初期化
というストーリーがまず思い描かれます。そこでそれらに使えそうなイベントを下のURLで探すと、該当するものがこのスクリに登場するイベントである、という事になりますので、具体的にそれらの中に役割分担を割り振って書いていく事になります。
http://wiki.secondlife.com/wiki/Category:LSL_Events/ja
図にしてみると上の図のようになっていて、実行に順序があるのは「座った(control)」から「許可された(run_time_permissions)」の所だけで、他はいつでも起こり得る独立した要素になっています。乗り物スクリは車でも船でも、原理的には上の図のような構造になっているのですが、ある程度のレベルのスクリプターは常にこのような図のどの要素で何を行わせればよいのかを、作りたい事を要素に分解して思い描いています。これを思い描けるかどうかが好きな物を自由に作れるか、の分かれ目になります。
スクリで何かを自由に作れるレベルになりたい場合は、まずはLSLのイベントは丸暗記出来るまで何度も読み返して記憶すべきです。何故なら、イベントの種類と内容を知っているだけで「~したら**する」の「~したら」の部分は全部知っていることになるからです。後半の「**する」の部分はそれだけを”Google”ればサンプルスクリはたくさん見つかりますので、見つけたサンプルを抜粋してイベントの中に詰め込んでいく事になります。
state_entry
スクリが保存されたかリセットされた時にだけ行えばよい初期設定をまとめています。具体的には、アバターが座った時の位置を指定し(着席が出来るようにし)、マウスを乗せたときのアイコンが椅子マークになるようにし、座った時のカメラ位置を指定し、念の為物理を切り、冒頭で作った関数planeEngine()を実行して乗り物設定に飛行機を設定させています。
後は、プリムを物理にすれば飛行機になりますし、物理を切ればその場で停止してくれます。準備完了です。
on_rez
例えば飛行中に「取る」メニューでテイクされた場合などには飛行中の状態が残っているので、REZされた際には関数init()を実行して念の為初期化、という事を行っています。llResetScript()を使ってスクリ自体をリセットしてしまっても今の場合は良いのですが、ユーザー設定を記憶させておくなどの要素が使えなくなってしまうので、乗り物の場合はリセットよりも自分で初期化すべきものを初期化してやる方が良い気がします。
changed
if (change & CHANGED_LINK)
{
云々・・・
}
となっていますが、この「if (change & CHANGED_LINK)」は「もし、プリムのリンクが変化したら」を意味します。{
云々・・・
}
LSLではifは、「(」から「)」の中身の計算結果がゼロであれば次の{から}の中身は実行されず、ゼロ以外(マイナスもOK)であれば実行されるという場合分けになります。
記号「&」はビット演算子と言われる計算記号で、+-×÷と同じような数式の記号です。Windowsの電卓を起動して「表示(V)→プログラマ(P)」として、適当な数字を入力した後に「2進」を選ぶと0と1が並びます。いわゆる二進数ですが、「&」は、数字をこのような2進数にした時に、二つの数字の間で両方とも1である場所は1、片方または両方が0である場所は0にした数字を計算します。例えば、数字の7は「0111」、数字の13は「1101」なので、「&」の結果は「0101」つまり5になります。
LSLのchangedイベントでは、イベント引数の変数changeとCHANGED_LINKとの&計算が0でない場合にはリンクが変更されたという意味になります。また、アバターがプリムに座るとアバターは座ったプリムにリンクされますので、誰かが座った場合にはリンクが変化し、この部分が実行されます。
llAvatarOnSitTarget()を実行した結果がNULL_KEYでない場合は誰かが座ったという事ですので、一旦llAvatarOnSitTarget()の実行結果をUUIDを保存する変数_avに設定して、「_av!=NULL_KEY」で確認しています。「!=」は「等しくない」という比較計算を表す記号ですので、つまり「変数_avはNULL_KEY(無効)」でない⇒「誰か座っている」という意味になります。
この変数_avの様に、スクリの冒頭で使う事を宣言せず、イベントの中など含まれている領域で使用を宣言された変数は「ローカル変数」と言います。自分を囲んでいる{と}の間でだけ有効で、その外側では使えません。
_avが無効でない事を確認した後にさらに、「pilot==NULL_KEY」で変数「パイロット」が無効である場合だけという条件を規定しています。現在のこの短いスクリでは不要なのですが、二人乗りの飛行機にしたい!とか後々の改造の場合に効いてくる(かもしれない)お約束の手順です。
そして、_avが無効(NULL_KEY)でない場合だけ、llRequestPermissionsを唱えてキー操作の監視の許可を要求しています。
その後に出てくる「else」は、「そうでなければ」の意味で、「if (_av!=NULL_KEY)」と対になっています。つまり、_av!=NULL_KEY(誰か座っている)が正しい間はelse以後は実行されませんが、誰も座っていないのにリンクが変更になるとこの「else」以後が実行されます。内容的にはスクリの冒頭で作っておいた関数init()をここでも実行し、スクリのパラメーターを初期化させています。on_rezでもinit()を呼び出しています。このように同じことをあちこちでさせたい場合には関数を上の方で書いておくと、関数だけいじれば全体に反映される、という後々の修正の楽さを確保できます。
run_time_permissions
キー操作の許可が得られるとこのイベントが実行されるので、llTakeControls()を唱えて監視したいキー操作項目を指定します。記号「|」(SHIFT+¥)は「&」と同じくビット演算子で、こちらはいずれか片方が1である部分を1にした数字を計算します。
また、物理をここでオンにして、state_entryでplaneEngineを実行して設定しておいた乗り物設定が有効になります。
control
ユーザーが行ったキー操作を具体的に解釈して飛行機として作動させている中心部です。
integer _keyDown=keyPress & edges; //押されているキーのうちで状態が変ったキーは今押され始めたキーです。keyPressとedgesのビット論理和(AND)を取ると取得できます。
integer _keyUp=~keyPress & edges; //押されているキーの逆のうちで状態が変ったキーは今離されたキーです。keyPressのビット反転とedgesのANDを取ると取得できます。
integer _keyUp=~keyPress & edges; //押されているキーの逆のうちで状態が変ったキーは今離されたキーです。keyPressのビット反転とedgesのANDを取ると取得できます。
この部分はコメントにある通り、「今押されたキー」と「今押すのをやめたキー」を計算で割り出しています。controlイベントではkeyPressの位置の引数に現在押されているキーが、edgesの位置の引数には変化があったキーが格納されて呼び出されてきます。また記号「~」は&と同じくビット計算で、0と1を全てのビット位置で逆転させる計算をします。なので、「keyPress & edges」とは、「現在押されていて変化があったキー」のビット位置だけが1になる計算ですし、「~keyPress & edges」は「現在押されているキーでないキーで変化があったキー」のビット位置だけが1になる計算になります。つまり前者は、「今押されたキー」ですし、後者は「今押すのをやめたキー」の意味になります。使う使わないはともかくとして、この2者の計算も乗り物ではお約束ですので、changedイベントの冒頭でとりあえず計算しておきます。
以後の部分はコメントメモにある通り具体的に速度や舵の操縦状態の解釈になっているのですが、このスクリプトでは小数点付数字(float)の変数trottleに0~1の数字を、また、三次元vectorの変数angular_motorのx、y、zの各軸に0~1の値を設定しています。この意図は、0=0%、1=100%という意味です。このようにしておき、最後の部分の
//速度の最大値を27.0m/sで、それに0.0~1.0をかけて設定します。
llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<27.0*throttle,0.0,0.0>);
//ロール、ピッチ、ヨー回転にそれぞれ最大値に現在の状態をかけて設定します。
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<10.0*angular_motor.x,1.0*angular_motor.y,0.5*angular_motor.z>);
llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<27.0*throttle,0.0,0.0>);
//ロール、ピッチ、ヨー回転にそれぞれ最大値に現在の状態をかけて設定します。
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<10.0*angular_motor.x,1.0*angular_motor.y,0.5*angular_motor.z>);
の所で乗り物機能に速度と回転を指定しています。
最初から最大値~最小値までを足したり引いたりしても良いのですが、そういう風にするとこのchangedイベントの中にあるキー操作の解釈のif文全部を書き換えないと後で微調整が出来ません。乗り物は特に作った後の微調整が勝負を決めるスクリになりがちですので、このタイプの、
とりあえず0~1の間の値を入れておいて最後に最大値をかけて反映する
というパターンはお約束のテクニックとして覚えておくと良いと思います。
もう少し詳しくいうと、これは「線形補間」というテクニックの省略された姿で、線形補間の計算式の本来の姿は、
中間結果=(1.0-T)×最小値+T×最大値
という形で、変数Tは0~1を取ります。Tがゼロの時は最小値になり、Tが1の時には最大値になるようにして中間の状態も作れる形式で、今の場合は最小値はゼロなので前半が省略された形になっている、という事です。
さて、このスクリが特徴的な行がたった一つだけあります。それは、
llSetForce(<0.0,0.0,llGetMass()*9.8*throttle>,TRUE); //揚力を設定
という命令です。llSetForce()はプリムにちからをかけ続ける命令なのですが、関数の内容を日本語で言うと、「自分の真上方向に重さ×9.8×スロットルの値分の力をかけ続けなさい」という意味になっています。9.8は高校で習った重力加速度そのもので、これに重さがかかりますので、結局、スロットルが100%で飛行機が水平なら重力による力と反対方向に重力と同じだけの力をかけ続けなさい、という命令になっています。
殆どの飛行機スクリのサンプルではこの命令の代わりに重力を小さくする命令を使っていて、実際にその方がこんな余計な計算をしなくて済む分簡単になります。
では、何故自分の上方向に力をかけるのでしょうか?背面になれば重力が倍になるのに??
こういう小さなところにちょっとしたアレンジが入るだけでも、個性的な動き(味付け)をした乗り物が作れます。私の判断ではこの理由でこちらの方が良いと思っています。
貴方なら、どんな味付けにしますか?
次回はこのスクリプトにもう少し高度なカメラ操作をつけてみることにします。
Posted by RBK Drachnyd(しお) at 23:46│Comments(0)
│LSLで作る