(この記事の使用環境: Unity5.5.0f3 Personal、Windows10、SteamVR Plugin 1.2.0、VRTK3.0.1)


VR用アセット「VRTK」の Example 016_Controller_HapticRumble の解説です。剣で箱を殴る強さに応じて、コントローラーが振動します。十分な強さで殴ると、箱は壊れます。なかなか興味深い作りになっています。

■公式サイトの解説

VRTK公式サイトでこのExampleの解説文及び解説動画を見ることができます。ただし、動画に関してはVRTK旧バージョンのときに作られたものであり、VRTK3.0(以降)とは設定は異なっていることに留意してください。動きを中心に見てもらえれば良いかと思います。

解説動画



■VRTKのセットアップ方法および使用されているスクリプトの解説

適宜、以下の記事を参照願います。

■Exampleの解説

VRTK/Examples 内にある当該番号のシーンを呼び出すことで、デモを動かしてみることができます。
 
シーンには 剣(Sword)と 壊せる箱(Breakable Boxes)が配置されています。剣を持って箱を殴ります。剣で箱を殴った強さに応じてコントローラーが振動します。十分な強さで殴れば箱は壊れます。
SnapCrab_NoName_2017-1-29_10-8-43_No-001

以下、シーンに配置されているオブジェクトについての概要説明です。
  • Sword: 掴める剣。VRTK_InteractableObject を継承した Sword スクリプトがアタッチされている。
    SnapCrab_NoName_2017-1-29_9-46-2_No-00

    Sword スクリプトの解説。

    (18行目~) まずは(VRTK_InteractableObject のGrabbed メソッド をオーバーライドした)Grabbed メソッド内で、コントローラーの VRTK_ControllerEvents と VRTK_ControllerActions を取得している。(VRTK公式サイトの「CLASS METHODS」>「Grabbed/1」を参照)

    (31行目~) OnCollisionEnter() 内では、VRTK_ControllerEvents の GetVelocity().magnitude でコントローラーを振った速度を取得し、振動の強さに反映させている。(VRTK公式サイトの「CLASS METHODS」>「GetVelocity/0」を参照)

    (37行目) コントローラーの振動は VRTK_ControllerActions  のTriggerHapticPulse() で行っている(3個の引数では振動の強さ、長さ、インターバルを指定している)。(VRTK公式サイトの「CLASS METHODS」>「TriggerHapticPulse/3」を参照)
    namespace VRTK.Examples
    {
        using UnityEngine;
    
        public class Sword : VRTK_InteractableObject
        {
            private VRTK_ControllerActions controllerActions;
            private VRTK_ControllerEvents controllerEvents;
            private float impactMagnifier = 120f;
            private float collisionForce = 0f;
            private float maxCollisionForce = 4000f;
    
            public float CollisionForce()
            {
                return collisionForce;
            }
    
            public override void Grabbed(GameObject grabbingObject)
            {
                base.Grabbed(grabbingObject);
                controllerActions = grabbingObject.GetComponent<VRTK_ControllerActions>();
                controllerEvents = grabbingObject.GetComponent<VRTK_ControllerEvents>();
            }
    
            protected override void Awake()
            {
                base.Awake();
                interactableRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
            }
    
            private void OnCollisionEnter(Collision collision)
            {
                if (controllerActions && controllerEvents && IsGrabbed())
                {
                    collisionForce = controllerEvents.GetVelocity().magnitude * impactMagnifier;
                    var hapticStrength = collisionForce / maxCollisionForce;
                    controllerActions.TriggerHapticPulse(hapticStrength, 0.5f, 0.01f);
                }
                else
                {
                    collisionForce = collision.relativeVelocity.magnitude * impactMagnifier;
                }
            }
        }
    }
    

  • BreakableCube(s): 壊せる箱。剣で一定以上の強さで殴ることで壊すことができる。箱の6辺(側面+天地)はそれぞれ独立した極薄のCubeであり、親のオブジェクトによりまとめられている。親のオブジェクトには Breakable_Cube スクリプトがアタッチされている。
    SnapCrab_NoName_2017-1-29_10-25-13_No-00
    SnapCrab_NoName_2017-1-29_11-42-54_No-00

    Breakable_Cube スクリプトの解説。

    (9行目~) まずは Start() 内で、親のゲームオブジェクトのRigidbodyの collisionDetectionMode を CollisionDetectionMode.Continuous に設定している。これにより、箱は衝突を連続的に検出するようになる。

    (14行目~) OnCollisionEnter() で衝突検出し、collisionForce が正の値なら ExplodeCube() 処理を呼び出す。collisionForce の値は GetCollisionForce() を使って取得している(24行目~)。

    (39行目~) ExplodeCube() が呼び出されると、箱を構成する辺の子オブジェクトを取得し、各子オブジェクトを ExplodeFace() する。また、親のゲームオブジェクトを10秒後にDestroyする。

    (49行目~) ExplodeFace() は箱を構成する各辺に対して以下のような処理を行っている。
    ・親のゲームオブジェクトをnullにする(親離れして独立する)
    ・AddComponent() で Rigidbodyを付け加える( isKinematic は false に、useGravity は true に指定)
    ・Rigidbodyの AddExplosionForce() で爆発を模した力を加える
    ・10秒後にDestroyする
    namespace VRTK.Examples
    {
        using UnityEngine;
    
        public class Breakable_Cube : MonoBehaviour
        {
            private float breakForce = 150f;
    
            private void Start()
            {
                GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.Continuous;
            }
    
            private void OnCollisionEnter(Collision collision)
            {
                var collisionForce = GetCollisionForce(collision);
    
                if (collisionForce > 0)
                {
                    ExplodeCube(collisionForce);
                }
            }
    
            private float GetCollisionForce(Collision collision)
            {
                if ((collision.collider.name.Contains("Sword") && collision.collider.GetComponent<Sword>().CollisionForce() > breakForce))
                {
                    return collision.collider.GetComponent<Sword>().CollisionForce() * 1.2f;
                }
    
                if (collision.collider.name.Contains("Arrow"))
                {
                    return 500f;
                }
    
                return 0f;
            }
    
            private void ExplodeCube(float force)
            {
                foreach (Transform face in GetComponentsInChildren<Transform>())
                {
                    if (face.transform.name == transform.name) continue;
                    ExplodeFace(face, force);
                }
                Destroy(gameObject, 10f);
            }
    
            private void ExplodeFace(Transform face, float force)
            {
                face.transform.parent = null;
                Rigidbody rb = face.gameObject.AddComponent<Rigidbody>();
                rb.isKinematic = false;
                rb.useGravity = true;
                rb.AddExplosionForce(force, Vector3.zero, 0f);
                Destroy(face.gameObject, 10f);
            }
        }
    }
    

今回は以上です。