衝突時の火花
衝突した時に火花を作る
リジッドボディ同士がぶつかった時に、火花が散るというアニメーションを作ってみよう。
作り方
- 衝突するオブジェクトをアクティブリジッドボディにする。
- 衝突されるオブジェクトをパッシブブリジッドボディにする。
- 衝突するオブジェクトをフィールドなどを使って、
衝突されるオブジェクトにぶつかるようにする。
- 衝突するオブジェクトにぶつかった時に火花をだすエクスプレッションを設定する。
衝突されるリジッドボディの contactCount アトリビュートが 1 以上になったら 2 つのリジッドボディが衝突しているので、
その時に
rigidBody -q -contactPosition
によって衝突した点の座標を求めればよい。
その点に emit コマンドか、
または、エミッターによって作って新しいパーティクルを発生させる。
- rigidSolver ノードの contactData アトリビュートを 1 に設定する。
この設定がされていないと衝突したかどうかがわからない。
火花のスクリプト
最初のスクリプトの火花は小さくて見にくいかもしれない。
それはパーティクルのアトリビュートを適切に設定していないからである。
それらのアトリビュートの設定は練習問題で行なう。
また、衝突した後、火花がずっと出続ける問題も練習問題で解決しよう。
なお、今回のスクリプトはプロシージャが 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(); と打ち込んで実行する。
- プレイバックしてアニメーションを実行する。
(注意)
再度プレイバックする場合は衝突によって作られたパーティクルとエミッターを消去しておいた方が良いかもしれない。
スクリプトの解説
-
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)")
- エクスプレッションの式を設定する。
式全体が ( ) で囲まれているので、まず ( ) の中の文字列の処理を行ない、
式を表す一つの文字列(" から " まで)になる。
そこで ( ) 内のエクスプレッションの式は以下のように組み立てられる。
- 最初の状態。
"makeEmitter1(\"" + $rb + "\", contactCount)"
- $rb がリジッドボディの名前(例えば、rigidBody2)に置き換わる。
"makeEmitter1(\"" + rigidBody2 + "\", contactCount)"
- 文字列が結合されて、一つの文字列になる。
"makeEmitter1(\"rigidBody2\", contactCount)"
ここで、" の前に \ があるのは式を表す文字列が、まだ終りではないことを示すためのものである。
また、contactCount はリジッドボディのアトリビュートで他のリジッドボディが衝突した点の個数を表す。
結局、このエクスプレッションでは makeEmitter1 という名前のプロシージャを、2 つの引数(リジッドボディの名前と衝突の階数)をつけて呼び出すことになる。
-
setAttr rigidSolver.contactData 1;
- rigidSolver の contactData アトリビュートを 1 にする。
これによって各リジッドボディが衝突したかどうかが、わかるようになる。
ここで rigidSolver はリジッドボディソルバとよばれるもので、
リジッドボディのアニメーションをコントロールするノードである。
-
global proc makeEmitter1(string $rigidbody, int $contval)
- makeEmitter1 という名前のプロシージャを定義する。
上のポリゴンの平面(polyPlane)に定義されたエクスプレッションから呼ばれる。
引数の説明は以下の通り。
- 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