Help us understand the problem. What is going on with this article?

MMD4Mecanim剛体基礎知識

More than 5 years have passed since last update.

この記事はOculus Rift Advent Calendar 2014 の6日目の記事になります。

昨年に引き続き「これOculusRift関係無くね?」という内容ではございますが、OculusRiftクラスタの人たちに活用して欲しい内容ということでここはひとつ。

title_img.png
MMD4Mecanim物理演算基礎知識

MMD4MecanimでミクさんをUnityで表示しても、Unityの剛体はすり抜けてしまう。ミクさんの髪にさわりたいのにどうすればいいのか分からないという人向けの記事です。結論から言うと適切にコンポーネントやCollisionを設定すれば可能です。手順が多いので難しく感じるかもしれませんが、仕組みを理解してしまえば応用の利く機能ですので、是非とも扱えるようになってOculusRiftアプリに活用してみてください。

MMD4Mecanimの剛体を理解する

MMD4MecanimはMMDの剛体の動きをUnity上で再現するために、Unityの持っている物理エンジンを使用せず、MMD本体と同じBulletという物理エンジンを内部的に動作させています。これはUnityの持つColliderコンポーネントとRigidbodyコンポーネントに相当するものを独自に持っているということになります。デフォルト状態ではBulletの剛体とUnityの剛体は別空間で計算されるため、お互いが干渉することはありません。しかし、MMD4MecanimにはUnityの剛体をBullet空間に送り込むためのコンポーネントが用意されており、これをうまく利用することでUnityの剛体をMMDモデルの剛体に干渉させたり、(擬似的に)MMDモデルの剛体をUnityの剛体に干渉させることができます。
ドヤ顔でUnityのCUbeを押しけるミクさん

(MMD4Mecanimは20140915以降のバージョンを使用してください)

注)MMD4Mecanimを使用してMMDモデルをUnityで利用する際は、配布モデルの利用規約をよく確認してください。MMD/MMM以外での利用を制限しているMMDモデルもございますのでご注意を。また、クリプトン・フューチャー・メディア社さんの管理するキャラクターを使用する際は、ピアプロ・キャラクター・ライセンス(PCL)の範囲内で利用する必要があります。

ミクさんの髪をさわりたい(Unity空間⇒Bullet空間へ)

MMD4MecanimにはUnityの剛体をBullet空間に送り込むコンポーネントが用意されています。そのコンポーネントを使えば、Unityの剛体でMMDモデルに干渉することができます。ただし、MMDモデルの剛体に触れることはできるものの、どの剛体に触れたかを知る機能がありません。このままではいくらミクさんの髪に触れても、ミクさんの方は髪に触れたことを判断することができません。
Unity剛体でミクさんの髪を押しのける

Unityの剛体を理解する

先ほど「ミクさんの方は髪に触れたことを判断することができません」と言いましたが大丈夫、方法はあります。それをこれから説明することになりますが、Bullet空間とUnity空間での剛体の相互干渉ができるようになると 「ミクさんの髪をさわさわと動かす⇒髪にさわられたことを判定してミクさんが反応する」 ことができるようになります。

ミクさんの髪にさわられたい(Bullet空間⇒Unity空間へ)

MMD4MecanimにはUnityの剛体をBullet空間に送り込むコンポーネントは用意されているのですが、逆方向に機能するコンポーネントは用意されていません。しかしながら、MMDモデルのボーンに対してUnityの剛体をちまちまと追加することで、擬似的にBullet空間の剛体の位置(形状)をUnity空間に持ってくることができます。(後で説明しますが、今はボタン一発で設定できるようなっており、ちまちまと剛体を手作業で追加する必要はありません)
ミクさんにUnity剛体を追加
擬似的にせよBulletの剛体をUnityの剛体として扱えるようになることで、Unityの持つ接触判定の機能を利用することができるようになります。この機能を利用して接触時のあれやこれやの反応をモデルに対して仕込んでいくことが可能になります。

Unityの剛体同士が接触したことが知りたい

Unityには剛体同士が接触したときに通知するための機能が用意されています。通知を受け取るためには、接触するオブジェクトのどちらか(今回は手側のオブジェクト)にUnityの ColliderコンポーネントとRigidbodyコンポーネントの両方が追加 されている必要があります。

Unityの接触判定関数 詳細
void OnCollisionEnter( Collision collision ) colliderやrigidbodyオブジェクトが他のcolliderやrigidbodyオブジェクトに触れたときに呼び出される。
void OnCollisionStay( Collision collisionInfo ) colliderやrigidbodyオブジェクトが他のcolliderやrigidbodyオブジェクトと衝突している最中に呼び出され続けます。
void OnCollisionExit( Collision collisionInfo ) colliderやrigidbodyオブジェクトが他のcolliderやrigidbodyオブジェクトに離れたときに呼び出される。
void OnTriggerEnter( Collider collider ) IsTriggerオプションがONのときの通知関数
void OnTriggerStay( Collider collider )
void OnTriggerExit( Collider collider )

ミクさんの髪で例えると、Unity上で用意した剛体がミクさんの髪に触れた瞬間に一回だけOnCollisionEnterが呼ばれ、ミクさんの髪に触れている間は毎フレームOnCollisionStayが、ミクさんの髪から剛体が離れた瞬間に一回だけOnCollisionExitが呼ばれます。
髪に触れた瞬間であるOnCollisionEnterに照れる表情に変更するスクリプトを記述し、髪から剛体が離れた瞬間であるOnCollisionExitに表情を元に戻すスクリプトを記述することで「髪に触れられると照れる⇒髪から手が離れると表情が元に戻る」という処理を行うことが考えられます。

実際は剛体が跳ね返りますので「触れる⇒離れる」細かく繰り返すことになったりしますので、そのあたりを考慮した処理を作成する必要があります。以下に衝突を判定するサンプルスクリプトを載せておきますので、Unity上でどのように動作するか試してみるのもよいかと思います。

TouchTest.cs
using UnityEngine;
using System.Collections;

public class TouchTest : MonoBehaviour
{
    //-----------------------------
    // OnCollision
    //-----------------------------
    void OnCollisionEnter( Collision collision )
    {
        Debug.Log( string.Format( "{0}.OnCollisionEnter {1}", name, collision.collider.name ) );
    }

    void OnCollisionStay( Collision collision )
    {
        Debug.Log( string.Format( "{0}.OnCollisionStay {1}", name, collision.collider.name ) );
    }

    void OnCollisionExit( Collision collision )
    {
        Debug.Log( string.Format( "{0}.OnCollisionExit {1}", name, collision.collider.name ) );
    }

    //-----------------------------
    // OnTrigger
    //-----------------------------
    void OnTriggerEnter( Collider collider )
    {
      Debug.Log( string.Format( "{0}.OnTriggerEnter {1}", name, collider.name ) );
    }

    void OnTriggerStay( Collider collider )
    {
        Debug.Log( string.Format( "{0}.OnTriggerStay {1}", name, collider.name ) );
    }

    void OnTriggerExit( Collider collider )
    {
        Debug.Log( string.Format( "{0}.OnTriggerExit {1}", name, collider.name ) );
    }
}

実践:髪に触れられたら照れるミクさんを作ってみよう

では実際に「髪に触れられたら照れるミクさん」を作ってみましょう。

Unity上で『手』を表現する剛体を作成し、Bullet空間に送り込む

モデルに触れるための手をUnity上で用意しましょう。Unityの剛体があれば機能させることができますので、Unityの剛体が扱える人ならば他のモデルなどを利用して頂いて構いません。

Unity上で何個かCubeを作成して適当に手を作ります。(面倒ならCube一個とかでいいです)
CubeHand

Colliderコンポーネントを持つオブジェクトに対して、MMD4Mecanim Rigid Bodyコンポーネントを追加します。Hierarchyビューでオブジェクトを複数選択した状態でAdd Componentを行うと一括でコンポーネントの追加が行えます。
コンポーネント一括追加
MMD4Mecanim Rigid Bodyコンポーネントを追加してください。これでUnity側の剛体設定は大丈夫です。次にモデル側の設定を行います。

モデルのPhysicsパラメータを設定する

MMD4Mecanimでコンバートしたモデルはデフォルト状態では、モデル内部に持っている剛体のみで計算するようになっています。MMD4Mecanim Rigid Bodyコンポーネントを持った剛体であっても外部の剛体を無視してしまいますので、外部からの剛体を受け付けるように設定します。

05_Physics設定.png
シーンに配置したモデルのMMD4Mecanim Modelコンポーネントのパラメータを以下のように設定してください。

Physicsパラメータ
Physics Engine Bullet Physics
Joint Local World OFF(チェックを外す)

設定できたら再生してみましょう。
さわぁ
Unityの剛体でBulletの剛体に干渉することができるようになったかと思います。

モデルのボーンに対してUnityの剛体を追加する

次にどこに触れたかの判定を行うためのUnityの剛体をモデルに追加します。

以前は手作業やスクリプトでモデルのボーンに対して剛体を付けていたのですが、現在のMMD4Mecanimのバージョンでは、ボタン一発でボーンに関連付けられたBulletの剛体をUnityの剛体に変換してくれる機能があります。この機能を使ってUnityの剛体を追加してみましょう。
Unity剛体をミクさんに追加
シーンに配置したモデルを選択し、Generate CollidersのProcessボタンを押すと、自動でUnityの剛体を追加してくれます。
剛体追加後
緑色のオブジェクトが追加されたUnityの剛体(Colliderコンポーネント)になります。次にこの剛体と先ほど作成した『手』の剛体の接触を判別するスクリプトを作成します。

剛体同士の接触判定を取ってモデルに通知する

モデルとの接触の通知を手側のオブジェクトが受け取れるようにするには、手側のオブジェクトにRigidbodyコンポーネントを追加する必要があります。接触の通知を機能させるにはUnityのColliderコンポーネントとRigidbodyコンポーネントの両方が必要になる、ということです。

手のオブジェクトを複数選択して一括でRigidbodyコンポーネントを追加しましょう。
一括コンポーネント追加

UnityのRigidbodyのデフォルト値では重力計算が働いており、このままでは再生後、下に落ちてしまったりして具合が悪いのでパラメータを修正します。Rigidbodyはパラメータ修正時も複数選択で一括変更ができます。

手のRigidbody設定

Rigidbodyパラメータ
Use Gravity OFF(チェックを外す)
Is Kinematic ON(チェックを入れる)

設定が間違っていると再生した時に手が下に落ちたり、モデルに当たって弾かれたりします。うまく動作しなかったときはRigidbodyのパラメータを確認してください。

次に剛体の接触を手のオブジェクトからモデルに通知するためのスクリプトを追加します。以下のスクリプトを手側の剛体を持つオブジェクト全てに追加してください。スクリプトの追加もRigidbody同様、複数選択で一括に行うことができます。

TouchModel.cs
using UnityEngine;
using System.Collections;

public class TouchModel : MonoBehaviour
{
  // 指定の剛体との接触であればモデルにメッセージを通知する.
  protected void checkTouch( Collider collider, string name, string message )
  {
    if( collider.name.IndexOf( name ) >= 0 ){
      var model = collider.GetComponentInParent<MMD4MecanimModel>();
      if( model ){
        var option = SendMessageOptions.DontRequireReceiver;
        model.SendMessage( message, this.collider, option );
      }
    }
  }

  // 剛体と接触中.
  void OnTriggerStay( Collider collider )
  {
    // 監視対象の剛体に触れたかを判定.
    // ここの文字列はモデルによって違いますので適時修正してください.

    // 髪.
    checkTouch( collider, "FrontHair", "setTouch_HiarFront" );
    checkTouch( collider, "RightHair", "setTouch_HiarRight" );
    checkTouch( collider, "LeftHair",  "setTouch_HiarLeft" );

    // ネクタイ.
    checkTouch( collider, "Necktie", "setTouch_Necktie" );
  }
}

ここでは剛体の名前にどんな文字列が含まれているかを判別してモデルに通知しています。前髪、ツインテール右、左、ネクタイを判別して、個別のアクションが設定できるようなスクリプトにしてみました。別のモデルで作成するときはここの文字列を変更することで対応、反応部位の追加を行うことができます。

モデル側の反応を追加する

モデル側の反応はUnityのMecanimに設定されている複数モーションを呼び出す処理をします。下の例ではモーションを4つAnimatorControllerに追加し、スクリプトからそれらのモーションを切り替えています。
Animator設定
changemotion_stand0.vmdが待機モーションとしてデフォルト再生設定されています。あとは触られた部位によってモーションを3パターン用意しています。今回のスクリプトではモーションを複数登録するだけで動作するようにしてありますでの、Mecanimの遷移パラメータの設定は必要ありません。

以下のスクリプトでモデルが触られた部位によってモーションを切り替えます。モデルにコンポーネントとして追加してください。

TouchAction.cs
using UnityEngine;
using System.Collections;

public class TouchAction : MonoBehaviour
{
  // モーションを元に戻すためのタイマー.
  public float timer = 0.0f;

  // 離れたときにモーションを元に戻す時間.
  public float recover_sec = 2.0f;  

  // アニメーション制御.
  protected Animator anim = null;
  protected string last_anim_name = "";

  //-------------------------------------
  // 初期化.
  //-------------------------------------
  void Start()
  {
    anim = GetComponent<Animator>();
  }

  //-------------------------------------
  // アニメーション変更.
  //-------------------------------------
  protected void setAnimation( string anim_name )
  {
    // 再生中のアニメーションは無視したい.
    if( anim_name != last_anim_name ){
      last_anim_name = anim_name;
      anim.CrossFade( anim_name, 0.1f );
    }
  }


  //-------------------------------------
  // 毎フレームの更新.
  //-------------------------------------
  void Update()
  {
    if( timer > 0.0f ){
      timer -= Time.deltaTime;
      if( timer < 0.0f ){
        timer = 0.0f;
        setAnimation( "changemotion_stand0.vmd" );
      }
    }
  }


  //==================================================
  // 剛体が触れられたときに呼ばれる関数.
  //==================================================

  //-------------------------------------
  // 髪に触れた.
  //-------------------------------------
  public void setTouch_HiarFront( Collider collider )
  {
    timer = recover_sec;
    setAnimation( "changemotion_stand1.vmd" );
  }

  public void setTouch_HiarRight( Collider collider )
  {
    // 今回はどの髪に触れても同じ動作をする.
    setTouch_HiarFront( collider );
  }

  public void setTouch_HiarLeft( Collider collider )
  {
    // 今回はどの髪に触れても同じ動作をする.
    setTouch_HiarFront( collider );
  }

  //-------------------------------------
  // ネクタイに触れた.
  //-------------------------------------
  public void setTouch_Necktie( Collider collider )
  {
    timer = recover_sec;
    setAnimation( "changemotion_stand2.vmd" );
  }
}

前項で「前髪、ツインテール右、左、ネクタイに別アクションを設定できる」という説明をしましたが、モーションがすべてのパターンに用意されていないことも想定して、適当に同じ処理を行うようなスクリプトにしてみました。例えば髪なら前左右全て同じモーションを再生するようにしてあります。ここを新しいアクションに差し替えれば、別々の反応をさせることができます。

設定はこれで完了です。これでシーンを再生すると髪やネクタイに触れたときにモーションが切り替わるようになります。

ネクタイに触れた

画像はネクタイに触れたことでAnimatorControllerのモーションが切り替わったところを写したものです。

触れられた部位を判定して反応を切り替えるというこができるようになったら、OculusRift+LeapMotionなどのデバイスを使用したアプリケーションを作ればいいんじゃないでしょうかね?

それでは皆さま、よきMMD4Mecanimライフを( ・ω・)ノシ


使用モデル>よ式初音ミク http://piapro.jp/t/QcRy
※この記事はピアプロ・キャラクター・ライセンスに基づいてクリプトン・フューチャー・メディア株式会社のキャラクター「初音ミク」を使用したものです。

mkt_
本業はドッターだと主張するフリーランスのゲームプログラマ。 家賃よりも書籍代の方が多い感じで。 Blenderを覚えるためにMMDモデルを制作してみたり、UnityでMMD/OculusRiftを動かしてみたり。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away