Houdini第1回 「木箱破壊フロー」
こんにちは、エフェクトデザイナーの丸野、クライアントエンジニアの中田です。今回は『PRINCIPLES』の木箱のランタイム破壊の制作フローをご紹介します。
ランタイム破壊とは?
ゲーム再生時にプレイヤーなどの行動によって起こった変化に対して、Unity内でほぼ同時に計算を行い、結果を再現する事です。
破壊モデルの作成
破壊モデル作成フローを丸野から紹介させて頂きます。
木箱破壊モデルはHoudiniを使用して制作。大きく分けて3つの工程があります。
①断面の処理
破壊断面が真っすぐなのを回避する為に「断面の処理」を行います。
「Normal方向に変形しない」とは?
②破壊モデル作成
破壊モデルをループ処理で行います。
③UV展開・データ整理
UV展開、データの整理を行います。
Unityで破壊
ここからはUnityで破壊の動きをさせる設定を中田より紹介していきます。
①Prefabの作成
先ほどHoudiniで作成した破壊モデル(BoxAfter)と、破壊する前のモデル(BoxBefore)をFBX形式などで受け取り、Unityのプロジェクトに入れます。
これらを1つのPrefabにしたいのでHierarchyで組み立てていきます。
Create EmptyでGameObjectを作り、その下に受け取ったモデルを入れます。
何故破壊する前のモデルも一緒にするかというと、描画負荷を抑えるためです。Hierarchyで見ても分かるようにBoxAfterは破片の数だけメッシュがあり、全ての破壊モデルの破片を常に描画していると、それなりの負荷になります。
そこで、破壊するまではBoxBeforeというメッシュが1つにまとまっているモデルを描画しておき、破壊の瞬間に2つの表示を切り替えることで負荷を最小限に抑えます。
②コンポーネントの設定
続けて必要なコンポーネントを付けていきます。
まずはメッシュに衝突判定に必要なMeshColliderと、物理的な挙動をしてもらうためのRigidbodyというコンポーネントを追加します。
全ての破片に設定する必要があるので、コロプラでは一括で設定するスクリプトを作っています。
public void Setup(GameObject root)
{
var renderers = root.GetComponentsInChildren<Renderer>();
foreach (var renderer in renderers)
{
var rigidbody = renderer.gameObject.AddComponent<Rigidbody>();
rigidbody.isKinematic = true;
renderer.gameObject.AddComponent<MeshCollider>();
}
}
コンポーネントを設定すると共にisKinematicフラグをtrueにして、壊す時以外は物理的な挙動をしないように制限しておきます。
③質量の設定
RigidbodyにはMassという質量の設定項目があります。
デフォルトでは1(kg)が設定されており、このままでもそれっぽい動きはしてくれるのですが、破壊モデルのサイズがすごく大きかったり、または材質が軽そうな素材だったりすると違和感が出てしまいます。
ですが、破片は数が多い上に1つ1つ大きさが異なるので設定するのが結構大変です。
そこでRigidbodyのSetDensityを使います。
float density = 200.0f;
var rigidbody = gameObject.GetComponent<Rigidbody>();
rigidbody.SetDensity(density);
densityとは密度のことで、SetDensityに渡すことによって密度とメッシュの大きさを元に質量を計算し、RigidbodyのMassに設定してくれます。ここで使う密度の単位は定義されていないのですが、PRINCIPLESの木箱の場合は200を設定しています。
これも全ての破片に設定したいのでスクリプトで一括で設定します。
public float Density;
public void SetDensityAll(GameObject root)
{
var rigidbodys = root.GetComponentsInChildren();
foreach (var rigidbody in rigidbodys)
{
rigidbody.SetDensity(Density);
}
}
④力を加える
一通り設定すれば、あとはRigidbodyを持ったメッシュをぶつけるだけで破壊モデルが崩れてくれます。
これだけで小一時間遊べます。
ですが、物を飛ばしてぶつける場合は吹き飛ばし方の調整が難しく、PRINCIPLESではRigidbodyのAddExplosionForceを使って吹き飛ばしています。
float force = 5.0f;
Vector3 position = new Vector3(0.0f, 1.0f, 0.0f);
float radius = 3.0f;
var rigidbody = gameObject.GetComponent<Rigidbody>();
rigidbody.AddExplosionForce(force, position, radius);
同じく力を加える機能としてAddForceもありますが、AddExplosionForceは特に爆発による力のかかり方を再現してくれます。上のサンプルコードの場合は(0.0, 1.0, 0.0)の座標で爆発が起こり、そこから半径3.0の範囲で5.0の力を加えて吹き飛ばしてくれます。
使い方ですが、例えばPRINCIPLESではエフェクトに見えないColliderを付けて、ColliderのコールバックであるOnTriggerEnter内で呼び出しています。
private void OnTriggerEnter(Collider collider)
{
var rigidbody = collider.gameObject.GetComponent<Rigidbody>();
if (r == null)
{
return;
}
rigidbody.AddExplosionForce(force, position, radius);
}
上記サンプルでは簡潔に書きましたが、BoxBeforeとBoxAfterの切り替えなどもここで行うのが良いかと思います。
これでエフェクトで木箱を吹き飛ばすことができるようになりました。
まとめ
木箱のランタイム破壊の検証をエフェクトデザイナーとクライアントエンジニアが共同で行いました。
コロプラではセクション間の連携を大切にしています。引き続き新たな検証に取り組んでいますので、今後紹介させていただければと思います。
採用情報
コロプラでは3Dアーティストを募集しています。最高のゲーム体験をユーザーさまに届けるために必要な表現提案、仕様策定など、あらゆる箇所に積極的に関わり、グラフィック表現のレベルを一段上に引き上げていただける方をお待ちしております。