アニメさせよう(スクリプト初級第十三回)

Miz

2007年04月19日 12:15


十三回もやってんのかって感じのスクリプト講座です(^^;
振り返ってみてみると、脱初級者までの道のりは、もうそれほど遠くはありませんね。
「ステート」「イベント」「処理」の三段構成を押さえ、最も多様性のあるイベントlistenも扱ったし、便利なダイアログも出てきたし。
パーミッションが少々モヤモヤ感がありますが、今回のアニメーションスクリプトでもパーミッションと格闘することになりますので、より理解が進むかと思います。

というわけで、今回は特にパーミッションに焦点をあてながら、アニメーションを扱うスクリプトについて見ていくことにしましょう。
アニメーションのデータ

スクリプトからアニメーションを扱うには、パーミッションにさえ気をつけていれば難しいことはほとんどありません。
ここで言うアニメーションとは、アバターの動きのことです。
ダンスとか、パンチ・キックなどのアクション、あるいはモデルポーズみたいに固まったままのものもあります。

スクリプトで制御するのは「どのアニメーションを実行するか」だけです。
そのアニメーションが実際に「どのような動きをするか」については、スクリプトからは判断ができません。

「どのような動きをするか」はアニメーション・データ次第です。
SLではアニメーションのデータを自作することができますが、詳しくはSL Wiki Japanのアニメーション作成の記事が参考になります。

外部のアニメーション作成ツールを使ってデータを作成し、SLにデータをアップロードします。
SLのアニメーションを作れるツールはいくつかありますので、自分の使いやすいものを選べば良いでしょう。
ちなみに私はQAvimatorというシンプルなツールを良く使います。
というか、高機能なものは使い方を理解するだけの頭がございません(^^;


QAvimatorの画面イメージ

自分でアニメーションデータを作ることができなくても、もとからSLに用意されているデータや、フリーで出回っているものを使うことができます。
非常に良くできたものもありますので、興味のある方は探してみてください。

なお、今回のスクリプトでは「ダンスアニメーション」を使います。
手元に使えそうなアニメーションデータが無いという方は、私の店にフリーアニメーションが置いてありますのでご自由にお持ち下さい(^^
「free animation pack」と書かれた音符マークの緑色の箱です。

ちゃっかり宣伝:Silent Stream
/adder/104/210/0/


アニメーション開始!

さっそくアニメーションを実行する関数から見てみます。

  llStartAnimation(string anim);

今まで出てきた関数の中でも、かなり簡単そうに見えます!w
引数には実行するアニメーションの名前を指定します。
もともとSLに組み込まれているアニメーション以外を使う場合は、オブジェクトのコンテンツの中にアニメーションが入っていなければなりません。

ちなみに、アニメーションを止めるときは、

  llStopAnimation(string anim);

もはや説明する必要もないでしょうが、引数で指定したアニメーションを止める関数です。
指定したアニメーションが実行中ではない場合には何の意味もありません。

このように、関数自体は非常に簡単そうに見えます。
しかし、この二つの関数をジッと見ていると、ふとした疑問が出てきませんか?

llStartAnimation(string anim)・・・。

animで指定したアニメーションを実行する・・・。

指定したアニメーションをさせる・・。

アニメーションさせる・・・。

・・・・・。

誰を?w

そう、この関数には「誰を」アニメーションさせるかの指定がないのです。

目的語を必要としない動詞であれば、意味は問題なく通じます。
例えば、
「あのさー、俺さー、昨日、泣いちゃったんだよ」
これは意味がわかります。
何故泣いたのかは不明ですが、文章としては完結します。

しかし、
「あのさー、俺さー、昨日、やっちゃったんだよ」
これは謎ですw
「何をしたんだ・・・?」
そう聞かずにはおれない文章です。
一体何を・・・。
ついにヤツの頭に銃をつきつけて引き金を引いてしまったではないかと、ボヘミイアン・ラプソディ並みに心配させる文章です。

同様にスクリプトllStartAnimation(string anim)も、
「さあ、animのようにアニメーションさせるのだ」
そう命令しているわけですが、命令されたほうは、
「だ、誰をですか・・・?」
そう聞き返さずにはいられない状況としか思えません。

しかしながら、スクリプトは迷うことなくアニメーションを実行するのです。
それは別のところで「誰を」アニメーションさせるか明示しているからなのですが、そこで出てくるのが例のパーミッションです。

踊るのは誰か

実は、アニメーション対象は「直前にパーミッションを取得したヤツ」と決まっています。
こんなん言われなきゃわかりません(^^;

私がlslをいじり始めたての頃、この辺りのことがわからないままにアニメーションのスクリプトを組んだものです。
すると友達がオブジェクトにタッチしているにも関わらず、踊りだすのは私という、マリオネットなスクリプトが完成しました(^^;

誰をアニメーションさせるのかわかった後には笑い話ですが、わかるまでは「?」でいっぱいな状態でしたね・・・。

つまりアニメーションを実行するには、以下のステップが必要になります。

(1)パーミッションの取得
    アニメーションさせたい相手に関して、アニメーションのパーミッションを取得します。
(2)アニメーションの実行
    パーミッションを取得した相手に対してアニメーションを実行する

このステップを無視すると、アニメーションが動作しないだけでなく、予期せぬ人が(主に自分w)いきなり動き出すという事態になります。

従って、タッチしたときにアニメーションするような、ダンスボールみたいな仕組みを作るには、
「タッチイベント」で「アニメーションの実行」
これだけだとうまく動きません。

(1)「タッチイベント」で「パーミッションを要求する」
(2)「パーミッションイベント」で「アニメーションの実行」

このようなステップになります。
このへんがアニメーションスクリプトの面倒なところです。

アニメーション実行までの流れ

パーミッションの扱い方は、ベンダーのときと同じ流れです。
復習になりますが、わかりにくいところですので再び詳細に追いかけてみましょう。

まずパーミッションを取得するための関数はllRequestPermissions()関数です。

  llRequestPermissions(key agent, integer perm)

key型引数agentには、パーミッションを取得する相手のUUIDを指定します。
permは取得するパーミッションの種類です。
アニメーションのパーミッション定数はPERMISSION_TRIGGER_ANIMATIONという名前になっていますので、今回はこの値を使います。

「タッチしたとき」に「パーミッションを要求する」には、

  touch_start(integer detected){
    llRequestPermissions(llDetectedKey(0), PERMISSION_TRIGGER_ANIMATION);
  }

このようになります。
これでタッチした人に対してパーミッション要求のダイアログが表示されます。

パーミッションのダイアログに対して返答がなされると、run_time_permissionsイベントが起こります。
ベンダースクリプトのお金のパーミッションのときと同様ですね。

  run_time_permissions(integer perm)

このイベントは、パーミッションが承認されても、却下されても、どちらでも発生しますので、承認されたかどうかの確認はイベント内で行わなければなりません。

  run_time_permissions(integer perm) {
    if (perm & PERMISSION_TRIGGER_ANIMATION){
      // PERMISSION_TRIGGER_ANIMATIONパーミッションが許可された
    } else {
      // PERMISSION_TRIGGER_ANIMATIONパーミッションが許可されなかった
    }
  }

パーミッションが承認されればアニメーションの実行が可能ですので、

  run_time_permissions(integer perm) {
    if (perm & PERMISSION_TRIGGER_ANIMATION){
      llStartAnimation("boogie");
    }
  }

こんなふうにしてやれば動き出します。
パーミッションが却下された場合は何もしなくてよいので、if文の後半は削りました。

しかし、これだけではアニメーションのスタートは実現できますが、停止ができません。
一度踊りだしたらもう止まらないスクリプトというのも面白いとは思いますがw

停止するために

タッチすると踊りだし、もう一度タッチすると止まるような仕組みを考えてみましょう。
パーミッションの要求部分については共通で使えます。

(1)「タッチしたとき」・・・「パーミッションを要求する」
(2)「パーミッションが承認されたとき」
          「まだアニメーションしていない場合」・・・「アニメーション実行」
          「すでにアニメーションしている場合」・・・「アニメーション停止」

これが出来れば、タッチするたびに「アニメーション実行」「アニメーション停止」が切り替わりますね。

「すでにアニメーションしているかどうか」を調べるには、llGetAnimationList()関数が使えます。
llGetAnimationList()関数は、指定したアバターが実行しているアニメーションのリストを取得する関数です。

  list llGetAnimationList(key id)

idにはアバターのUUIDを指定します。
返ってくるlistは、実行中のアニメーションのUUID一覧です。

なぜlist型で返ってくるかというと、実は同時に実行できるアニメーションの数は一つではないからです。
アニメーションには5段階の優先順位があって、例えばwalk(歩くアニメーション)は優先順位0です。
対してsit(座るアニメーション)は優先順位4になっており、walkとsitを同時に実行すると、sitのほうが優先されて動きます。
また、アニメーションの中には全身を動かさないようなものもあります。
表情のアニメなどは特にそうですが、laugh(笑うアニメーション)とwalk(歩くアニメーション)を同時に実行すると、表情は笑顔で身体は歩いている状態にすることができます。
そのように複数のアニメーションが実行可能なため、llGetAnimationList()関数は実行中のアニメーションをlistで返すのです。

従って、特定のアニメーションに関して「すでにアニメーションしているかどうか」を調べるには、listの中にそのアニメーションが含まれているかどうかを調べなければなりません。

ここでもう一点注意しなければならないことは、llGetAnimationList()関数で返ってくるリストはアニメーションのUUIDです。
"boogie"という名前のアニメーションが実行されているかどうかを調べるには、まずUUIDと名前の変換をしてやらなければいけません。

名前をUUIDに変換するには、llGetInventoryKey()関数が使えます。
llGetInventoryKey()関数はオブジェクトのコンテンツに入っているもののUUIDを取得する関数で、引数には調べたいものの名前を指定します。

  key llGetInventoryKey(string name)

例えば"boogie"という名前のアニメーションのUUIDを調べたい場合は、
  key chk = llGetInventoryKey("boogie");
このようにすれば変数chkに"boogie"のUUIDが格納されます。

「すでにアニメーションしているかどうか」を調べて、アニメーションしていたら停止、していなかったら実行するようにするには、以下のようなコードになります。
以下の例ではアニメーションの名前は"boogie"、アニメーション対象のUUIDは変数agentとしています。

  key chk = llGetInventoryKey("boogie");
  list anms = llGetAnimationList(agent);
  integer i;
  for (i = 0; i < llGetListLength(anms); i++){
    if (chk == llList2Key(anms, i)) {
      // アニメ"boogie"は実行中
      llStopAnimation("boogie");
      return;
    }
  }
  // アニメ"boogie"は実行中のアニメリストの中に無かった=まだ実行していない
  llStartAnimation("boogie");

これでだいぶできてきました。
クリアすべき点はあと1点のみです。

上の例ではアニメーション対象のUUIDをagentという変数で示していますが、このUUIDはどのように取得すればいいのでしょうか。
アニメーション対象は「直前にパーミッションを取得した相手」です。
これを調べるための関数はllGetPermissionsKey()と言います。

  key llGetPermissionsKey();

引数はありません。無条件に「現在パーミッションを取得しているアバターのUUID」が返ります。
誰のパーミッションも取得していない場合はNULL_KEY(存在なし)が返ってきます。

アニメーションスクリプト

※一番最初に掲載した際にコードにミスがありました。以下は修正後のコードです。
string animation_name="boogie";
key agent = NULL_KEY;

default {
  touch_start(integer detected){
    if (agent == NULL_KEY) {
      agent = llDetectedKey(0);
      llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION);
    }
  }

  run_time_permissions(integer perm) {
    key perm_key = llGetPermissionsKey();
    if (perm_key == agent) {
      if (perm & PERMISSION_TRIGGER_ANIMATION){
        key chk = llGetInventoryKey(animation_name);
        list anms = llGetAnimationList(agent);
        integer i;
        for (i = 0; i < llGetListLength(anms); i++){
          if (chk == llList2Key(anms, i)) {
            llStopAnimation(animation_name);
            agent = NULL_KEY;
            return;
          }
        }
        llStartAnimation(animation_name);
      }
      agent = NULL_KEY;
    }
  }
}


コードは短いですが、アニメーションの仕組みが理解できていないと読み取れないスクリプトです。
実行するアニメーションはstring型の変数animation_nameとしてスクリプトの先頭に定義しました。
ここでは"boogie"というアニメーションを使っていますが、ここを変更すると様々なアニメーションに対応可能です。

「パーミッションのリクエスト」と「パーミッションイベント」の間の整合性を確保するために、少々小細工を弄しています。
どういうことかと言うと、「パーミッションのリクエスト」をしたときの相手と、「パーミッションイベント」の起きたときの相手が、必ずしも同じではない場合があり得るということです。
以下のような場合を考えてみて下さい。

(1)私(Miz)がオブジェクトにタッチする。
(2)Mizに対してパーミッションダイアログが表示される
(3)Mizはそれを放置(ぉぃ
(4)その間に私の友人のYasichiくんがオブジェクトにタッチ
(5)Yasichiくんに対してパーミッションダイアログを表示
(6)Yasichiくんは即座にパーミッションを受け入れる
(7)パーミッションイベントが起きる。このときパーミッションの相手はYasichiくん
(8)Yasichiくん、踊りだす
(9)楽しそうなYasichiくんを見て、Mizがようやくパーミッション受け入れのボタンを押す
(10)パーミッションイベントが起きる。しかしこのとき、パーミッションの相手はYasichiくん
(11)スクリプトはYasichiくんの実行中アニメーションを調べ、すでに踊っているのでアニメーションを停止
(12)何が起こったのかわからないMizとYasichiくんは険悪な状態に(ぇ

(10)の時点で、「直前のパーミッション要求」は(5)の処理ですので、相手はYasichiくんです。
こんなふうに、パーミッション要求と返答が入り混じってしまうと、たちまちおかしな動作になります。

ですので小細工してパーミッション要求と返答が入り混じらないようにしています。
key型変数agentを用意しているのはそのためです。
agentにはパーミッション要求した相手のUUIDを保管するようにしておき、誰にも要求を出していないときにはNULL_KEYを入れておきます。

タッチイベントが起きたとき、agentがNULL_KEYであれば誰にも要求を出していないので、要求を出す相手をagentに設定し、パーミッションリクエストを行います。
run_time_permissionsイベントが起きたときには、まず要求を出している相手のUUIDを調べ、それをagentと比較します。
これが一致していれば、正しい順序でパーミッション要求と返答が来ている事になりますので、アニメーションの処理を行います。
アニメーションの処理が終わったら、agentにNULL_KEYをセットし、次のタッチイベントに備えます。

今回のポイント

・アニメーションの開始:
  llStartAnimation(string anim);
animは開始するアニメーション名。
対象は直前にパーミッションを取得した相手。

・アニメーションの終了:
  llStopAnimation(string anim);
animは停止するアニメーション名。
対象は直前にパーミッションを取得した相手。

・実行中のアニメーション取得:
  list llGetAnimationList(key id)

・オブジェクトコンテンツの中にあるもののUUID取得:
  key llGetInventoryKey(string name)

・直前にパーミッション要求を出した相手のUUID:
  key llGetPermissionsKey();

繰り返しになりますが、アニメーションで面倒なのはパーミッションの処理だけです。
パーミッションの扱い方法を理解し、現在誰に対してのパーミッションを取得するのかを意識すれば、アニメーションを自由に操れます。

例えば、しばしばクラブなどで見かけるダンスボールですが、不特定多数の人がタッチして踊れるようになっており、しかも一定時間でダンスアニメーションを切り替えるものもあります。
あれを実現しようとすると、結構複雑なパーミッション管理を行う必要が出てきます。
というか、正攻法で作るのはおそらく困難を極めますw
もしも作れたら、初級者卒業はもちろん、かなりのスクリプト技術があると言ってもいいでしょう。
中級者でも悩む人いるんじゃないかな?(^^;

ダンスボールについては、正攻法以外の裏技もありますけどねw
裏技ならば、今回のスクリプトを改造して対応可能です(いずれ機会があればダンスボールの作り方は解説します)。

さて。
アニメーションスクリプトの基礎が出来たところで、次回は応用をしてみましょう。
以前作った椅子スクリプトがありますが、あれにアニメーションの処理を追加し、デフォルト以外のポーズで座れる椅子を作ってみる、なんてのを考えています。

では、また次回。
初級スクリプト