衝突時の火花
衝突した時に火花を作る
リジッドボディ同士がぶつかった時に、
火花が散るというアニメーションを作ってみましょう。
また応用として、火花の代わりに衝突時に穴を開けたり、へこみを作ることもできるでしょう。
作り方
- 衝突するオブジェクトをアクティブリジッドボディにします。
- 衝突されるオブジェクトをパッシブブリジッドボディにします。
- 衝突するオブジェクトをフィールドなどを使って、
衝突されるオブジェクトにぶつかるようにします。
- rigidSolver ノードの contactData アトリビュートを 1 に設定しておきます。
この設定がされていないと衝突したかどうかがわかりません。
- 衝突するオブジェクトにぶつかった時に火花をだすエクスプレッションを設定します。
衝突されるリジッドボディの contactCount アトリビュートが 1 以上になったら 2 つのリジッドボディが衝突しているので、
その時に
rigidBody -q -contactPosition
によって衝突した点の座標を求めます。
その点に emit コマンドか、
または、エミッターによって新しいパーティクルを発生させます。
火花のスクリプト
最初のスクリプトの火花は小さくて見にくいかもしれません。
それはパーティクルのアトリビュートを適切に設定していないからです。
それらのアトリビュートの設定は練習問題で行ないます。
また、衝突した後、火花がずっと出続ける問題も練習問題で解決しましょう。
なお、今回のスクリプトはプロシージャが 2 つ含まれているので注意して作ってください。
- 以下の MEL スクリプトを makeSpark1.mel という名前で作ります。
global proc makeSpark1()
{
string $gra[] = `gravity -m 9.8`;
polySphere;
move 1 10 1;
rigidBody -active;
connectDynamic -f $gra[0];
polyPlane;
scale 20 1 20;
rotate 0 0 -45;
string $rb = `rigidBody -passive`;
expression -o $rb -s ("makeEmitter1(\"" + $rb + "\", contactCount)");
setAttr rigidSolver.contactData 1;
}
global proc makeEmitter1(string $rigidbody, int $contval)
{
if($contval > 0)
{
string $pp[] = `rigidBody -q -contactPosition $rigidbody`;
string $pos[];
tokenize $pp[0] $pos;
string $emi[] = `emitter -pos $pos[0] $pos[1] $pos[2]`;
string $pname[] = `particle`;
connectDynamic -em $emi[0];
setAttr ($pname[1] + ".particleRenderType") 6;
setAttr ($pname[1] + ".lifespanMode") 2;
setAttr ($pname[1] + ".lifespanRandom") 0.5;
setAttr ($pname[1] + ".lifespan") 5;
}
}
- の によって makeSpark1.mel を読み込みます。
- の下のウインドウから makeSpark1(); と打ち込んで実行します。
- プレイバックしてアニメーションを実行します。
(注意)
再度プレイバックする場合は衝突によって作られたパーティクルとエミッターを消去しておいてください。
スクリプトの解説
global proc makeSpark1()
- makeSpark1() というプロシージャーの宣言です。
-
string $gra[] = `gravity -m 9.8`;
- gravty (重力)フィールドを作ります。
重力係数(-m)は 9.8 です。
-
polySphere;
- ポリゴンの球を作ります。
-
move 1 10 1;
- ボリゴンの球を (1, 10, 1) に移動します。
-
rigidBody -active;
- ポリゴンの球をアクティブ・リジッドボディにします。
-
connectDynamic -f $gra[0];
- ポリゴンの球に gravty フィールドをコネクトして Y 軸マイナス方向に落ちてゆくようにします。
ポリゴンの球は、
この時点でセレクトされた状態なので、
コマンドで球の名前を指定する必要はありません。
-
polyPlane;
- ポリゴンの平面を作ります。
-
scale 20 1 20;
- ポリゴンの平面を X 方向 20、Z 方向 20 に拡大します。
-
rotate 0 0 -45;
- ポリゴンの平面を Z 方向に -45 度回転します。
-
string $rb = `rigidBody -passive`;
- ポリゴンの平面をパッシブ・リジッドボディにします。
作成されたリジッドボディの名前を文字列の変数 $rb に代入しておきます。
-
expression -o $rb -s ("makeEmitter1(\"" + $rb + "\", contactCount)");
- 変数 $rb に代入されているリジッドボディにエクスプレッションを設定します。
- -o $rb
- エクスプレッションを定義するオブジェクトを $rb に名前が代入されているリジッドボディに設定します。
- -s ("makeEmitter1(\"" + $rb + "\", contactCount)")
- エクスプレッションの式を設定します。
式全体が ( ) で囲まれているので、まず ( ) の中の文字列の処理を行ない、
結果が式を表す 1 つの文字列になります。
そこで ( ) 内のエクスプレッションの式は、
以下のように組み立てられてゆきます。
- 最初の状態
"makeEmitter1(\"" + $rb + "\", contactCount)"
- $rb がリジッドボディの名前(例えば、rigidBody2)に置き換わります。
"makeEmitter1(\"" + rigidBody2 + "\", contactCount)"
- 文字列が結合されて、1 つの文字列になります。
"makeEmitter1(\"rigidBody2\", contactCount)"
ここで、" の前に \ があるのは式を表す文字列が、
まだ終りではないことを示すためのものです。
また、contactCount はリジッドボディのアトリビュートで他のリジッドボディが衝突した点の個数を表しています。
結局、このエクスプレッションでは makeEmitter1 という名前のプロシージャを、
リジッドボディの名前と衝突の階数の 2 つの引数をつけて呼び出すことになります。
-
setAttr rigidSolver.contactData 1;
- rigidSolver の contactData アトリビュートを 1 に設定します。
これによって各リジッドボディが衝突したかどうかが、わかるようになります。
ここで rigidSolver はリジッドボディソルバとよばれるもので、
リジッドボディのアニメーションをコントロールするノードです。
-
global proc makeEmitter1(string $rigidbody, int $contval)
- makeEmitter1 という名前のプロシージャを定義します。
上のリジッドボディになっているポリゴンの平面に定義されたエクスプレッションから呼ばれます。
各引数の説明は以下の通りです。
- string $rigidbody
- 他のリジッドボディが衝突したかどうか調べるリジッドボディの名前
- int $contval
- リジッドボディが衝突した点の個数
-
if($contval > 0)
- リジッドボディが衝突した点の個数が 0 以上なら以下のスクリプトを実行します。
-
string $pp[] = `rigidBody -q -contactPosition $rigidbody`;
- リジッドボディが衝突した点の座標を求めて文字列の配列 $pp に代入します。
衝突した点の個数は 1 個とは限らないので配列にしておきます。
-
string $pos[];
- 文字列の配列 $pos を定義します。
-
tokenize $pp[0] $pos;
- 文字列 $pp[0] を分解して配列 $pos に代入します。
なぜこのような処理が必要かというと、$pp の中身であるX, Y, Z 座標値が一つの文字列になっているからです。
ここでの処理は以下のように進んでゆきます。
- 例えば、$pp[0] の中身が "1.5 2.3 3.6" という文字列になっているとします。
- tokenize コマンドによって、$pp[0] が分解されて $pos の中に代入されてゆきます。
$pos[0] = "1.5"
$pos[1] = "2.3"
$pos[2] = "3.6"
なお、ここでは 1 回の衝突で 1 個だけエミッターを作ることにしているので $pp[1] 以下はわざと無視しています。、
-
string $emi[] = `emitter -pos $pos[0] $pos[1] $pos[2]`;
- エミッターを ($pos[0], $pos[1], $pos[2]) の位置に作ります。
エミッターの名前は $emi に代入します。
-
string $pname[] = `particle`;
- エミッターのために空のパーティクルを作ります。
パーティクルのトランスフォームノードの名前が $pname[0] に、
シェープノードの名前が $pname[1] 代入されます。
-
connectDynamic -em $emi[0];
- 空のパーティクルにエミッター($emi)をコネクトします。
-
setAttr ($pname[1] + ".particleRenderType") 6;
- パーティクルのシェープノード $pname[1] の particleRenderType アトリビュートに 6 (Streak)を設定します。
これでパーティクルのレンダリングタイプが細長い線になります。
-
setAttr ($pname[1] + ".lifespanMode") 2;
- ライフスパンモードを 2 (Random range)に設定します。
これによって個々のパーティクルに違ったライフスパンを割り当てることができます。
ライフスパンとはパーティクルの生存時間のことです。
-
setAttr ($pname[1] + ".lifespanRandom") 0.5;
- ライフスパンの平均値(lifespan)からの分布の幅を設定します。
平均値が 5 、lifespanRandom が 0.5 であれば、
ライフスパンは 4.5 から 5.5 の間のランダムな値になります。
-
setAttr ($pname[1] + ".lifespan") 5;
- ライフスパンの平均値を 5 に設定します。
練習
- 使用例の makeSpark1.mel を参考にして、makeSpark2.mel を作り、
セレクトされたオブジェクトに対して火花の出る設定にしてみましょう。
(使用例)
- プリミティブなどで 2 つのオブジェクトを作って、
適当にトランスレート・スケール・回転などを実行しておきます。
- の によって makeSpark2.mel を読み込みます。
- 衝突するオブジェクトを一つセレクトします。
- 次に、衝突されるオブジェクト一つを追加してセレクトします。
ワークスペースであれば Shift キーを押しながらセレクト、
Outliner であれば Ctrl キーを押しながらクリックします。
- の下のウインドウから makeSpark2(); と打ち込んで実行します。
以下の図は、衝突するオブジェクトにポリゴンのシリンダー、
衝突されるオブジェクトにポリゴンの立方体を使用して実行したものです。
- makeSpark2.mel を参考にして、makeSpark3.mel を作り、
以下のアトリビュートをエミッターとパーティクルに設定して火花らしい外見にしてみましょう。
エミッター $emi[0] に設定するアトリビュート一覧
- speed
エミッターから出るパーティクルのスピード
パーティクル($pname[1])に設定するアトリビュート一覧
- maxCount
シーンの中に存在するパーティクルの個数の最大値
この個数を越えると、パーティクルが消滅して個数以下になるまで
パーティクルを放出しなくなります。
- lineWidth
Streak の太さ
- tailSize
Streak の長さ
- tailFade
Streak の尾の不透明度
1.0 で完全に不透明、0.0 で完全に透明になります。
(使用例)
実行の手順は makeSpark2 と同じです。
以下の図における各アトリビュートの値は以下の通りです。
- speed
- 5.0
- lifespan
- 1.0
- maxCount
- 50
- lineWidth
- 3
- tailSize
- 20.0
- tailFade
- 0.1
(ヒント)
lineWidth, tailSize, tailFade はダイナミックアトリビュートなので
addAttr コマンドでパーティクル $pname[1] にアトリビュートを追加してやる必要があります。
addAttr コマンドを使用する場合に必要なフラグは以下の通りです。
- -ln
- 追加するアトリビュートの名前
- -at
- 追加するアトリビュートのアトリビュートタイプ
lineWidthは "long"、 tailSize, tailFade は "float" です。
- makeSpark3.mel を参考にして、makeSpark4.mel を作り、一定のフレームが経過したらパーティクルの放出が止まるようにしてみましょう。
スクリプトの使用方法は makeSpark3.mel と同じです。
(ヒント)
現在のフレーム番号を知るには以下のコマンドを使用します。
currentTime -q
このコマンドを使用してぶつかった時のフレーム番号を調べ、
そのフレーム番号より何フレームか後にエミッターの rate アトリビュートを 0 にしてください。
そのためには、エミッターにエクスプレッションを設定して、
ぶつかった時のフレーム番号から何フレーム後かどうかをチェックして、
アトリビュート(rate)を 0 にします。
- ぶつかった時のフレーム番号を調べて、変数に代入しておく。
- その変数を利用して、エミッターにエクスプレッションを設定します。
例えば、ぶつかった時より 20 フレーム後にパーティクルを止めたければ、
以下のようにします。
if(現在のフレーム番号 > ぶつかった時のフレーム番号 + 20){ rate = 0; }
この時、文字列の結合を利用して 1. の変数の中身(フレーム番号を表す数字)をエクスプレッションに組み込んでください。
なお、エクスプレッションの中では現在のフレーム番号は frame で表されることに注意してください。
練習課題
参考
Prev
Home | Contents
Mail