チュートリアル / Mayaにおける魅惑のプロップリグ~モジュラーリギングシステムの基本と応用~
第5回:リグモジュールの開発とセットアップ その4〜宝箱の中身のアニメーション〜

  • GAZEN
  • Maya
  • アニメ
  • キャラクター・リグ
  • ゲーム
  • コラム
  • スクリプト・API
  • チュートリアル
  • 学生・初心者
  • 映画・TV

今回は前回作成した宝箱をアップデートしたいと思います。
宝箱なので何か中身を追加します。

宝箱の中身といえば・・・
・宝
・化け物
・空っぽ
大体この3択でしょうか。

空っぽは現状で問題ないので、宝と化け物をモデルとして追加してみました。

宝と化け物をモデルとして追加
※判り易いように蓋を開けています。

宝 : コインの山
化け物 : 牙と歯茎
のモデルをそれぞれ追加しました。
ではこのモデルを基にセットアップを行っていきます。

中身切り替えスイッチを作る

宝箱の中身を 空・宝・歯 と切り替えたいと思います。
その為の制御方法を色々考えてみます。

1. 歯と宝それぞれの可視性を制御するアトリビュート(bool)を設置する。
シンプルな方法。
想定していない組み合わせで表示されてしまう可能性がある。

2. 歯と宝のモデルのvisibilityを開放し、直接制御する。
アニメーターさんから割と多いリクエスト。
モデルのアップデートでオブジェクト名が変わってしまった場合、見た目が変わってしまうのでお勧めしない。

3. 中身を示すアトリビュート(enum) を追加し、歯と宝の可視性を制御する。
想定の組み合わせを制御できる。
複雑なセットアップになる。

4. displayLayerを同梱し、そちらで制御する。
キーフレームを打つ場合に手間がかかる。

1は簡単に実装できそうなのでこれを最低限の保険とし、3で進めてみます。

試作

空・宝・化け物・両方 というモードがあるとして、各モードでの表示組み合わせはこのようになります。

空・宝・化け物・両方 というモードがあるとして、各モードでの表示組み合わせ

enumアトリビュートの数値に対して、対応する値を返す という仕組みが必要です。
この場合choiceノードが使えそうな気がします。
choiceノードは inputに入力された複数の値から、selectorで指定されたindexの値を出力してくれます。

enumアトリビュートの数値に対して、対応する値を返す という仕組み

イメージとしては青枠方向のプリセットを用意するのではなく、 赤枠方向のプリセットを用意してindexで呼び出すといった感じですね。

 赤枠方向のプリセットを用意してindexで呼び出す

これをもとにして、Mayaで試作してみました。
treasureChest/mayaScenes/sampleData/treasureChest_sample_v02.ma

試作

choiceノードのinputはsetAttrしてしまえばいいのですが、試作なので判り易くするために適当なノードを作ってコネクトすることで確認しやすくしています。

実際にアトリビュートを変えてみるとこのように変化します。

宝箱のアトリビュートを変えて変化させる
宝箱のアトリビュートを変えて変化させる
宝箱のアトリビュートを変えて変化させる
宝箱のアトリビュートを変えて変化させる

実装

では試作をもとにリグモジュールへ落とし込んでいきます。

まずはスウィッチをセットアップする為に必要な情報を書き出してみます。

・アトリビュートを設置するコントローラー名
・アトリビュート名
・enum の項目名
・制御するモデル名
・制御するモデルとenum項目名との関係性

こんなところでしょうか。

・enum の項目名
これはマルチ設定にして任意の数を足せるようにしますが、
セットアップ作業中に、あーヤッパリ順序入れ替えたいなーとなった場合に調整しやすいように並び順も併せて設定できるように使用と思います。

・制御するモデル名
骨格データに入れ込むので、message ではなく stringタイプで指定します。
ただ、パーツ数が膨大だとかなり入力・修正が大変なので objectSet も受け付けるようにしておくと後々楽になりそうです。

・制御するモデルとenum項目名との関係性
これがちょっと悩ましい項目です。

表示パターンのプリセットと制御するモデル名をセットで管理するか、 enum項目名と制御するモデル名をセットで管理するかが考えられます。

表示パターンのプリセットと制御するモデル名をセットだと、このようにプリセット間で相反する内容を設定出来てしまうので避けた方が良さそうです。

制御するモデルとenum項目名との関係性

enum項目名 と 制御するモデル名をセットだとこのようになります。
だた、この情報をもとにリグモジュール内でプリセットを生成する必要があるので、その辺がちょっと面倒になります。

制御するモデルとenum項目名との関係性

諸々まとめると入力項目はこのようになりました。

_ATTRS = [
                "ctrlNode",     #アトリビュートを設置するコントローラー名
                "attrName",     #アトリビュート名
                "keyable",      #アトリビュートをkeyableにするか否か

                "expandTargets",  #登録されたノードを最下層まで展開し、個別にするか否か
                "enumSettings",   #enum項目の詳細

                #enumIndex        enumの順番 
                #enumName         enum項目名
                #targets          このenum項目選択時に表示されるobject名。複数の場合は , で繋ぐ。 objectSetでも可
]

_ATTR_DICT = {
                "ctrlNode":             {"type":"string",  "multi":False,  "default":""},
                "attrName":             {"type":"string",  "multi":False,  "default":""},
                "keyable":              {"type":"bool","default":True},
                "expandTargets":        {"type":"bool","default":True},

                "enumSettings":         {"type":"compound","ch":["enumIndex","enumName","targets"],"multi":True},
                    "enumIndex":        {"type":"long","default":0,"max":9999,"min":0,"multi":False},
                    "enumName":         {"type":"string","default":"","multi":False},
                    "targets":          {"type":"string","default":"","multi":False},
}

enum情報からのプリセットの生成はどうにもスマートな方法が思いつかなかったので、
target 総あたりで組み合わせを取得し、その組み合わせをkeyにしたdictを作成してセットアップに回しています。

作成したモジュールはこちらに格納してあります。
SSRig/rigModules/enumVisSwitch.py

組み込み

早速組み込みたいところですが、その前にまずはモデルの更新を反映させようと思います。
前回からの続きなので、packageのヴァージョンを 3 から 4へ上げ、モデルをtreasureChest_model_v02.maにします。

SSRigSetupDataEditor

まずはこの状態で一度 for test を実行して様子を見てみます。
追加モデルはバインドされていないのでこうなりますね。

バインドしてない状態

上の歯を topLid_jnt
下の歯とコインを body_jnt にそれぞれバインドしてウェイトを書き出します。
そして再度 for test でビルドし、確認します。

上の歯を topLid_jnt、下の歯とコインを body_jnt にそれぞれバインドしてウェイトを書き出す

問題なさそうなので、treasureChest_skeleton_v03.ma に 今回作成した enumVisSwitch モジュールを追加します。
モジュールの設定はこのようにしました。

treasureChest_skeleton_v03.ma に 今回作成した enumVisSwitch モジュールを追加

これを treasureChest_skeleton_v04.ma として保存し、セットアップデータに登録してビルドしてみます。

treasureChest_skeleton_v04.ma として保存し、セットアップデータに登録してビルドする

root_gp にアトリビュートが追加されました。
expandTargets をTrueにしてあるので、グループではなく子のジオメトリが直接制御されています。

root_gp にアトリビュートが追加される

アトリビュートを変えて挙動を確認します。

アトリビュートを変えて挙動を確認
アトリビュートを変えて挙動を確認
アトリビュートを変えて挙動を確認
アトリビュートを変えて挙動を確認

問題なさそうなので for shipping でビルドし、treasureChest_asset_v02.ma として保存します。

前回作成したアニメーションシーン(treasureChest_testAnimation_v01.ma)のリファレンスを切り替えて動きを見てみます。
中身は適当に設定したので、再生する前にどれになっているか予想してみてください。

正解はtoothでした!

歯の動きを追加する

ん~ もう少し要素を追加したいですね。
折角歯がいっぱいあるので、これらを動かせるようにしようと思います。

動きのイメージとしては・・・・ランダムにわしゃわしゃ動いてる感じにしたいかなと

個数が多いので個別に動かすのではなく、パラメーターで一括制御したいですね。

案出し

実際にランダムに動かすと動きがおかしなことになってしまうので、正しくは 1本1本はループの動きをしているが、その動きは同期していない という感じでしょうか。

まずは1本単位ので動きを考えます。

基本的には 前後・横方向の行ったり来たりを組み合わせた動きが良さそうです。

歯の1本単位ので動き

さて、この動きを実現するには・・・・
1. drivenKeyを使用する
2. expression で sin / cos を使用する
3. sin / cos 的な挙動をするギミックを作成する
あたりが思い浮かびました。

drivenKeyは割と簡単そうな気はしますが、動きの幅とかタイミング調整機能をつけるとなった場合には少々面倒そうです。
そうなると expression が柔軟に対応できそうですね。

ちなみに3に関しては、以前何種類か作ったことがありまして、
・nurbsCurve をsinデフォーマーで振動させ、cvの位置情報を取得する
とか
・円形に配置したロケーターを円の中心で回転させて、平面上の位置情報を取得する
など色々試行錯誤してみましたが、sin / sind / asin などが使えるexpressionの方が圧倒的にやり易いなぁという気持ちになりました。

試作

expression のsin/cosで制御するので それらの波の動きについて考えてみます。

sinの波はこんな感じで1ループしますね。

sinの波

横軸方向に時間が進むとして、縦軸方向の数値をjointに流せばループした動きが作れそうです。

横軸方向に時間が進むとして、縦軸方向の数値をjointに流せばループした動きが作れそう

また、別のjointに関しては位相をずらすことで、同じループの動きをしつつ同一の時間で見ると別のポーズにすることが出来そうです。

別のjointに関しては位相をずらすことで、同じループの動きをしつつ同一の時間で見ると別のポーズにすることが出来そう

これをMayaのタイムラインに沿って動かしたいので、1ループ分のフレーム数仮に24と定義しておきます。

expressionにやらせたい処理を文章にすると

時間(フレーム数)にあわせて1~24をループする値を作成し、
その値を sin または sind に与え、最終的にjointへコネクトする。

という流れになります。
今回は sindを使用してみます。

expression としてはこのように記述しました。
そのままの数字だと恐らく小さすぎるので、とりあえず x50 ほど大きくしておきます。

int $loopIn = 24;
joint1.rotateZ = sind(((frame % $loopIn) / $loopIn)* 360) * 50 ;

これで動かしてみるとこのように動きました。

これを参考にもう少し調整できるパラメーターを加え、位相のずらしも考慮してみます。

int $loopIn = 24;
int $offset = -1;
float $mag = 50;
int $delay = 15;

botTeeth01_L_jnt.rotateZ = sind((((frame + $offset + ($delay*0)) % $loopIn) / $loopIn)* 360) * $mag;
botTeeth02_L_jnt.rotateZ = sind((((frame + $offset + ($delay*1)) % $loopIn) / $loopIn)* 360) * $mag;
botTeeth03_L_jnt.rotateZ = sind((((frame + $offset + ($delay*2)) % $loopIn) / $loopIn)* 360) * $mag;
botTeeth04_L_jnt.rotateZ = sind((((frame + $offset + ($delay*3)) % $loopIn) / $loopIn)* 360) * $mag;
botTeeth05_L_jnt.rotateZ = sind((((frame + $offset + ($delay*4)) % $loopIn) / $loopIn)* 360) * $mag;

これを実際に動かしてみると、かなり落ち着きのないシーンが出来上がりました。
車のエンジンのような動きをしてますね。

参考データがこちらに格納してあります。
treasureChest/mayaScenes/sampleData/treasureChest_sample_v03.ma

実装

試作をもとにリグモジュールを作成していきます。

この手のモジュールは制御するノードが多くなりがちで、かつ左右対称だったりもするので
・targetsに指定されたノードを名前順にソートする
・反対側のノードもターゲットとして加える
という処理を追加したいと思います。

今回は
1. 左右 昇順の方向へ

リグモジュールの作成:左右 昇順の方向へ

他には
2. 右 降順 から 左 昇順へ

リグモジュールの作成: 右 降順 から 左 昇順へ

3. 左 降順 から 右 昇順へ

リグモジュールの作成: 左 降順 から 右 昇順へ

というケースが考えられます。

1は並列に動き、2,3は直列でつながる といった感じでしょうか。
2,3は それぞれでの 昇順・降順のソートに加え、 右・左 どちらが先に来るかを選択出来れば対応できそうです。

諸々考慮し、パラメーターを下記の様に設定しました。


_ATTRS = [
                "ctrlNode",           #アトリビュートを設置するコントローラー名
                "useParamPrefix",     #アトリビュートのprefix
                "attrName",           #アトリビュート名 (空欄でも可)

                                      # ctrlNode.paramPrefix + attrName +"_mag"
                                      # ctrlNode.paramPrefix + attrName +"_loopIn"
                                      # ctrlNode.paramPrefix + attrName +"_delay"
                                      # ctrlNode.paramPrefix + attrName +"_frameOffset"

                "targets",            #制御する joint
                "sortDirection",      #targetsをソートする方向を指定

                "sinTargetAttrs",     #sin で制御するアトリビュート。 複数ある場合は , で繋ぐ  tx,ty,tz,rx,ry,rz
                "cosTargetAttrs",     #cos で制御するアトリビュート。 複数ある場合は , で繋ぐ  tx,ty,tz,rx,ry,rz
                                      #sinと同じものが登録されていた場合はsinを優先する。

                "mirrorSetupSetting"  #反対側セットアップ設定

                #addMirrorTargets     #ネーミングルールにしたがって、targets の対となるjointを反対側用targetsとして使用するか否か
                #mirrorSortDirection  #反対側用targets をソートする方向
                #extendTo             #反対側用targetsを targetsとどう組み合わせて処理するか
                                      #before   反対側用targets + targets(直列)
                                      #after    targets + 反対側用targets(直列)
                                      #parallel  並列
]

_ATTR_DICT = {
                "ctrlNode":             {"type":"string",  "multi":False,  "default":""},
                "useParamPrefix":       {"type":"enum","enumList":["none","section+prefix","section+prefix+side","prefix","prefix+side"],"default":"prefix"},

                "attrName":             {"type":"string",  "multi":False,  "default":""},

                "targets":              {"type":"message","default":True,"multi":True},
                "sortDirection":        {"type":"enum","enumList":["+","-"],   "default":"+"},

                "sinTargetAttrs":        {"type":"string","multi":False,  "default":"ry"},
                "cosTargetAttrs":        {"type":"string","multi":False,  "default":"rz"},

                "mirrorSetupSetting":       {"type":"compound","ch":["addMirrorTargets","mirrorSortDirection","extendTo"],"multi":False},
                    "addMirrorTargets":     {"type":"bool",  "multi":False,  "default":False},
                    "mirrorSortDirection":     {"type":"enum","enumList":["+","-"],   "default":"+"},
                    "extendTo":          {"type":"enum",     "enumList":["before","after","parallel"],   "default":"parallel"},

}

作成したモジュールはこちらに格納してあります。
SSRig/rigModules/frameWaveSwitch.py

テスト

では作成したリグモジュールを組み込んで行きます。

骨格データのアップデート

おそらくこれが今回最も面倒な作業になると思います。
歯を個別に動かしたいという事なので、歯個別のjointを作成していきます。
ただ歯並びは良いのでまだマシでしょうか。

上の歯は奇数本のため、中央の1本はL/Rを名前から抜いています。

骨格データのアップデート

ただ、この名前だとsortが掛かった際に、こうなってしまいます。

骨格データのアップデート

そのため、00 に変更しました。

骨格データのアップデート

リグモジュールは今回は上下別制御になるように設定しました。

リグモジュールは今回は上下別制御になるように設定

これを treasureChest_skeleton_v05.ma として保存・登録し、セットアップを実行してみます。

追加されたアトリビュートをいじってみます。
当たり前ですが、weightを修正していないので骨しか動いてません。

weightを修正していないので骨しか動かない

さてこれも数があるので面倒ですが、weightを調整して再度セットアップを実行してみます。
結果を見てみますと・・・・

あー・・・・・・・なんか気持ち悪い動きになってるので成功ですね。
歯の側面が箱と干渉してしまってるので、mag の数値はあまり上げ過ぎない方が良さそうです。

挙動確認

for shipping でセットアップを実行し、treasureChest_asset_v03.ma として保存します。
折角歯も動くようになったので、アニメーションは使いまわさず新たに作成してみます。

宝箱の貴重な捕食シーンができました。
歯を入れたので、若干キャラクターっぽさが出てきてしまいましたね。

終りに

宝箱は今回でお終いになります。

expressionはとても便利ですが、記述の仕方次第では evaluationモードがパラレルの時に落ちてしまう原因になってしまいます。
expressionでif文などを使用すれば前半の可視性の制御も出来そうですね。

一時期expressionを毛嫌いして使っていない時期がありましたが、 手法が偏ってしまうと対応しきれない局面が出てきてしまうのと、やはりexpressionは便利なので使い始めました。

手札増やすためにもそろそろbifrostも勉強していきたいなぁと思っています・・・・思っています。


製品購入に関するお問い合わせ
オートデスク メディア&エンターテインメント 製品のご購入に関してご連絡を希望される場合は、こちらからお問い合わせください。