BlackSheep-LSL@Wiki 基礎知識 > フロー制御とは?
※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

フロー制御


フロー制御というのは、条件によって実行するコードを変えたり、繰り返し処理を行ったりするものを言います。
プログラムの処理の流れ(フロー)をコントロールするものなので、「フロー制御」と呼ばれます。

まずはフロー制御の種類を挙げておきます。

フロー制御 説明
if-else 条件分岐。条件に応じて実行するコードを変える。
whle ループ処理。条件を満たす間延々と繰り返し処理を行う。
do-while ループ処理。処理を行った後、条件を満たす場合は繰り返す。
for ループ処理。条件を変化させながら繰り返し処理を行う。
return 復帰(リターン)。ユーザー関数やイベントを終了する。
jump ジャンプ。指定したコードの行に処理を飛ばす。
state ステート。LSLのステートを変更する。

if-else


フロー制御の中でも最も使用頻度が高いのがif-elseでしょう。
とてもプログラムらしい制御文だと言えます。
構文から見てみましょう。

【基本形】
if (論理演算式) {
  論理演算式がTRUEであるとき、ここに書いたコードが実行される
}

条件の判定には「論理演算?」を使います。
論理演算式とは「name == "miz"」とか「money < 500」のような式です。これがif文の条件になります。
条件が成り立つとき(=論理演算式の結果がTRUEのとき)に、if文の中身が実行されます。

if (ピアノが弾ける) {
  想いの全てを歌にする
}

上記の例では、もしも「ピアノが弾ける」なら、「想いの全てを歌にする」ことになります。
「ピアノが弾けない」場合は何も起こりません。
もちろん、LSLでこんなコードがあるわけではありません、念のため。

ところで・・・。
西田さんは本当はピアノを持ってないし、上手に弾けません。
「ピアノが弾けない」場合にも何か処理を書きたい場合はどうするのでしょうか。

【応用形1】
if (論理演算式) {
  論理演算式がTRUEであるとき、ここに書いたコードが実行される
} else {
  論理演算式がFALSEであるとき、ここに書いたコードが実行される
} 

この形を使います。
elseは「そうじゃないとき」のような意味合いになります。

if (ピアノが弾ける) {
  想いの全てを歌にする
} else {
  伝える言葉が残される
} 

このようにすると、もしも「ピアノが弾け」たなら「想いの全てを歌に」しますが、ピアノが無かったり、聞かせるほどの腕もない場合は「伝える言葉が残される」ことになります。
よくわからない人はタウンページのCMでも見てください。

さて、ピアノの弾き方にもいろいろあるわけです。
時と状況に応じていろいろに弾き分けたい場合があります。
条件がたくさんあるようなときですね。

そんなときは以下のようにします。

【応用形2】
if (論理演算式1) {
  論理演算式1がTRUEであるとき、ここに書いたコードが実行される
} else if (論理演算式2) {
  論理演算式2がTRUEであるとき、ここに書いたコードが実行される
} else {
  論理演算式1も2もFALSEであるとき、ここに書いたコードが実行される
} 

こんなふうに、「else if」を使って条件式を次々に並べることができます。

if (雨が降る夜なら) {
  雨のよに弾く
} else if (風吹く夜なら) {
  風のように弾く
} else if (晴れた朝なら) {
  晴れやかに弾く
} else {
  きみと夢見ることもない
} 

このように書くと、
「雨が降る夜」は「雨のよに」
「風吹く夜」には「風のように」
「晴れた朝」には「晴れやかに」
弾きますが、雨でも風でも晴れでもない場合は、
「きみと夢見ることも」なくなります。

喩えばかりではアレですので、最後にlslの具体例を示しておきます。

if (color == "red") {
  llSetLinkColor(LINK_SET, <1.0, 0.0, 0.0>, ALL_SIDES);
} else if (color == "green") {
  llSetLinkColor(LINK_SET, <0.0, 1.0, 0.0>, ALL_SIDES);
} else if (color == "blue") {
  llSetLinkColor(LINK_SET, <0.0, 0.0, 1.0>, ALL_SIDES);
} else {
  llSetLinkColor(LINK_SET, <1.0, 1.0, 1.0>, ALL_SIDES);
} 

変数colorの中身に応じて、オブジェクトの色を変えるif文です。
llSetLinkColorの部分はオブジェクトの色を変える関数です。

while/do-while


続いてループ処理を行うwhile文です。
do-whileというのもありますが、この二つは条件判定の位置が異なるだけです。

まずはwhile文から構文を見てみましょう。

【whileの構文】
while (論理演算式) {
  論理演算式がTRUEの間は延々とここに書いたコードを繰り返す
} 

これは以下のような場合に使います。

while (俺の目が黒い) {
  ここは通さない
} 

かの有名な「俺の目が黒いうちはここを通さない」がこれです。
whileは条件判定部分がTRUEのうちは、決してループを抜け出すことがありませんので、まさに「通れません」。
通るためにはヤツの目を黒以外の色にする必要があります。

もしも以下のようなwhile文を書いたらどうなってしまうでしょうか。

while (TRUE) {
  llSay(0, "俺を倒してから行け!");
}

このwhile文では条件判定が常にTRUEです。
ということは、永久にループし続けることになります。
これを「無限ループ」と言います。
無限にループして処理を続けるということは、SIMのCPUパワーを常に使いっぱなしということです。
こんなスクリプトを書いてしまったら、スクリプトをリセットする以外には止める方法がなくなりますので注意しなければなりません。
立ちはだかる敵を配置するのは結構ですが、必ず倒す手段を用意しておくべきでしょう。

さて、もう一方のdo-whileも見てみましょう。

【do-whileの構文】
do {
  ここに書いたコードを実行し、論理演算式がTRUEの場合はdoに戻って繰り返す
} while (論理演算式);

これは、以下のようなパターンのときに使います。

do {
  厳しい修行の日々
} while (卒業試練に不合格);

do-while文の場合、最低一回は中身が実行されます。
上の例ですと、まず最初に必ず「修行の日々」を過ごすことになります。

修行の日々が終わると、師匠が出てきて最後の卒業試練が行われます。
修行が足らず、この試練に失敗すると、
「修行が足らぬわ!」
とか言われて再び「激しい修行の日々」へとループします。

見事に師匠を倒すと、
「もはや教えることは何もない・・・ぐふっ」
とか言ってループが終了するわけです。

while文との違いは、do-while文の中身が最初に一回は必ず実行されるという点です。
いきなり「卒業の試練」を受けることは許されません。お話が盛り上がりませんから。
あくまでも最初は「修行の日々」が無ければならないのです。

while文にしろ、do-while文にしろ、注意すべきなのは無限ループです。
立ちはだかる敵を倒す方法や、こにくたらしい師匠をギャフンと言わせる方法は必ず用意しておきましょう。

以下はwhileの具体例です。

vector pos = llGetPos();
while(pos.z < 500.0){
  pos.z += 1.0;
  llSetPos(pos);
} 

オブジェクトのZ位置が500.0以上になるまで、1mずつオブジェクトを動かす処理になります。
llGetPosはオブジェクトの位置を取得する関数、llSetPos?は位置を設定する関数です。

for


for文もwhile同様ループを行うフロー制御文なのですが、ループ時に変数の増加等の処理を行うことができる点が異なります。

構文から見てみます。

for(初期化; 論理演算式; 更新処理) {
  論理演算式がTRUEの間、ここに書いた処理が繰り返し実行される
} 

「初期化」と「更新処理」の部分が目新しい要素ですね。

「初期化」の部分はループに入る直前に一度だけ実行されます。
「更新処理」のほうはループの最後、次のループに入るかどうか判定が行われる直前に実行されます。

言葉で書くとよくわからないので、for文をwhile文に書き換えてみましょう。

初期化処理;
while(論理演算式){
  論理演算式がTRUEの間、ここに書いた処理が繰り返し実行される
  更新処理;
} 

このwhile文はfor文と同等の処理です。
「初期化処理」と「更新処理」がループのどの位置にあるのかをよく見てください。
「初期化」はループの直前、「更新処理」は次のループに入る判定の直前ですね。

このパターンのループがどういうときに使われるかと言うと、例えばリスト型の変数の全要素を表示したいような場合、

list colors = ["red","green","blue"];
integer i = 0; // 初期化処理
while(i < llGetListLength(colors)){
  llSay(0, llList2String(colors, i));
  i ++; // 更新処理
} 

リスト型変数colorsには、3つの要素があります。
whileループに入る前に整数型の変数iを定義し、値を0にします。

そしてループに入りますが、「llGetListLength(colors)」というのはリストの項目数を返す関数ですので、3です。
iは0ですので、(0 < 3)はTRUEです。

llList2String(colors, i)」はリストの中身を文字列型で取り出す関数です。
iが0なので、一番最初の要素"red"が取り出され、チャット欄に表示されます。

ループの最後の「i ++」は先日やった通り、「iの値を1増やす」計算です。
これでiが1になりました。

whileの条件判定に戻り、(1 < 3)はまだTRUEです。
再びループの中身が実行され、今度は"green"がチャット欄に表示されます。
i ++でiが2となり、再び条件判定・・・(2 < 3)なのでループ内が処理され、今度は"blue"が表示されます。
ループの最後でi ++されてiは3になります。

すると今度は(3 < 3)がFALSEとなるので、ループは終了します。

以上、"red","green","blue"のリストの中身が順番に処理されました。
このようなパターンのときに、forループは使われます。

上記のwhile文で書いたループをfor文に書き換えると以下のようになります。

list colors = ["red","green","blue"];
integer i;
for(i = 0; i < llGetListLength(colors); i++){
  llSay(0, llList2String(colors, i));
} 

1行減ったので、ちょっとだけスッキリした気分になります。
ですがwhile文で書いたときと、処理の内容に違いはありません。
好みに応じてwhileとforのどちらかを使えば良いでしょう。

return


リターンはユーザー関数やイベントを中断するときに使います。
また、ユーザー関数で値を返すときにも使われます。

例えばこんな感じで使います。

touch start(integer total_number) {
  if ( llDetectedKey(0) != llGetOwner() ){
    return;
  }
  // オーナーだけに許される処理をここに書く
} 

この例だと別にリターンを使わなくちゃいけないわけではないのですが・・・。
タッチした人のUUIDとオーナーのUUIDを比較し、一致しない場合(=オーナー以外がタッチした場合)はリターンでイベントを終了しています。
タッチしたのがオーナーだった場合はリターンせず、その先に書かれた処理が実行されます。

値を返すユーザー関数を作った場合は以下のようになります。

list keys = ["key1", "key2", "key3"];
list valuse = [ 100, 50, -25];

integer get_value(string keyword) {
  integer i = llListFindList(keys, [keyword]);
  if (i != -1 ) {
    return llList2Integer(values, i);
  }
  return 0;
} 

このユーザー関数get_valueは、引数keywordで指定された文字列をリストkeysの中から探します。
そして見つかった場合はvaluesの同じ位置にある整数値を返します。
見つからなかった場合は0を返します。

この使い方については「関数?」の説明をするときに改めて書きます。

jump


ジャンプはコードの中の指定位置に飛ぶフロー制御です。
あまり使いどころがありませんが・・・。

 while(orenome == "black"){
  if (llFloor(llFrand(10.0)) == 0){
    jump loopout;
  }
 } 
 @loopout;

例えばwhileループからの脱出などに使います。
上記の場合、一見無限ループになっていますが、ランダムなタイミングで@loopoutの位置にジャンプするようになっています。

なお、ジャンプはどこにでも飛べるわけではありません。
ジャンプ先に選べるのは同じユーザー関数内か、同じイベント内だけです。

state


ステートはlslスクリプトのステートを切り替えます。

default{
  state_entry(){
    llSay(0, "default state.");
  }
  
  touch_start(integer detected){
    state next;
  }
} 

state next{
  state_entry(){
    llSay(0, "next state.");
  }
  
  touch_start(integer detected){
    state default;
  }
} 

タッチするたびに、defaultステートとnextステートを行ったり来たりする例です。