Oculus Quest 2でいちゃいちゃしてみよう！Part5

# 目次

[1. BlenderでVroidのモデルを編集しよう](https://qiita.com/KayaTakashiro/items/65838261b896117915dd)
[2. Oculus Quest 2の準備をしよう](https://qiita.com/KayaTakashiro/items/15c58867c84e24a96dd3)
[3. UnityにVRMモデルを入れてみよう](https://qiita.com/KayaTakashiro/items/ccd532fe20fdc80be0f8)
[4. VeryAnimationで遊んでみよう](https://qiita.com/KayaTakashiro/items/307c91c8a40df4eff115)
[5. DynamicBoneでいろいろ遊んでみよう](https://qiita.com/KayaTakashiro/items/f39dc45a686aca6f3d12)
[6. UnityのAnimationでいちゃいちゃしよう](https://qiita.com/KayaTakashiro/items/f25e950c268d9a239576)

## デモとパッケージ

https://youtu.be/-Ao8o2OWVVU

https://youtu.be/a25QZY0NA9I

https://github.com/blackpopo/MakingOutWith2DGirls

## 5. DynamicBoneでいろいろ遊んでみよう

まず、目標としては

- おっぱいにふれる
- 髪の毛を触る
- スカートをめくる

の3つをやっていきます。

###6で使用するColliderの設置

これらのColliderは後でいちゃいちゃするのに使います。なので、6が必要ない方は次のステップに進んでください。

まず、初めにsecondary にあるVRM Spring Boneのチェックマークを外し、すべて無効にします。

そして、Assetに[SAColliderBuilder](https://assetstore.unity.com/packages/tools/sacolliderbuilder-15058?locale=ja-JP)をimport します。

次にVRMモデルの一番の親のところ(Animatorがあるところ)にSA Bone Collider Builderを設置して、画像のようにパラメータを設定し、Processを押します。

![スクリーンショット 2021-05-29 214153.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/4e8665c3-4803-5672-011f-d95a932bcef8.png)

ちょっと、時間がかかるので待ってください。

次に、SA Collider ClearをRootのオブジェクトにアタッチして、右クリックからClean All Childrenを実行してください。


```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SAColliderCleaner : MonoBehaviour
{
    [ContextMenu("Clean All Children")]
    void CleanAllChildren()
    {
        var allChildren = GetAll(gameObject.gameObject);
        foreach (var obj in allChildren)
        {
            Rigidbody rb = obj.GetComponent<Rigidbody>();
            if (rb)
            {
                DestroyImmediate(obj);
            }
        }
    }

    // Start is called before the first frame update
    private List<GameObject> GetAll(GameObject gameObject)
    {
        List<GameObject> allChildren = new List<GameObject>();
        GetChildren(gameObject, ref allChildren);
        return allChildren;
    }

//子要素を取得してリストに追加
    private void GetChildren(GameObject obj, ref List<GameObject> allChildren)
    {
        Transform children = obj.GetComponentInChildren<Transform>();
        //子要素がいなければ終了
        if (children.childCount == 0)
        {
            return;
        }

        foreach (Transform ob in children)
        {
            allChildren.Add(ob.gameObject);
            GetChildren(ob.gameObject, ref allChildren);
        }
    }
}

```

次にJ_Bip_C_Hips, J_Bip_C_UpperChest, J_Bip_L_UpperArm, J_Bip_R_LowerArmのSABoneColliderから、Processを実行しColliderを設置します。

各Colliderの大きさを調節して、できるだけ写真のようにメッシュに沿うようにしてください。

![スクリーンショット 2021-05-29 220149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/d23c8d25-da11-ee69-4763-a8d1576be654.png)

### 胸に髪の毛が入らないようにするためのDynamicBoneColliderの設置

J_Bip_C_UpperChestに空のオブジェクトをつくり、名前をDBC_upperChestにしてください。

DynamicBoneColliderをDBC_upperChestにAddComponentして、Coll.J_Bip_C_UpperChestを参考にして、大きさを調節します。

サイドヘアーがめり込まないようにするためなので、写真のように少しずらします。

![スクリーンショット 2021-05-29 221108.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/441bd3ce-1806-652c-336e-369078894669.png)

### Oculus Hand にDynamicBoneColliderの設置

まず、OculusHand_LとOculusHand_RのそれぞれにSABoneColliderBulderをAddComponentします。

写真のようにパラメータを設定し、Processを実行します。

![スクリーンショット 2021-05-29 221916.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/03a57924-ff42-6800-3fae-d47cb6d6cf29.png)

OculusHand_L, OculusHand_Rのそれぞれに以下のDynamicBoneColliderSetterをAddComponentします。

#### 関数の中身について

* CopyColliderInChildren -> 通常のColliderに合わせて、DynamicBoneColliderを仕込むやつ
* RemoveAllColliders -> CopyColliderInChildrenで仕込んだ、DynamicBoneColliderの消去
* SetTargetColliders -> アタッチされたコンポーネント中に存在するDynamicBone中に(ここに登録したDynamicBoneColliderにより、DynamicBoneが動かされる)、Target以下の子要素のDynamicBoneColliderに登録するやつ
* RemoveTargetColliders -> SetTargetCollidersで登録したDynamicBoneColliderを消去するやつ


```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DynamicBoneColliderSetter : MonoBehaviour
{
    // [SerializeField] public GameObject baseObject;
    private CapsuleCollider fromCollider;
    private GameObject baseObject;
    [SerializeField] private GameObject target;

    [ContextMenu("Copy DynamicBoneColliders in Chilren refered to Unity Colliders")]
    public void CopyColliderInChildren()
    {
        var allChildren = GetAll(target);
            foreach (var obj in allChildren)
            {
                fromCollider = obj.GetComponent<CapsuleCollider>();
                if (fromCollider ){
                    obj.AddComponent<DynamicBoneCollider>();
                    DynamicBoneCollider toCollider = obj.GetComponent<DynamicBoneCollider>();
                    if (fromCollider.direction == 0) 
                    {
                        toCollider.m_Direction = DynamicBoneColliderBase.Direction.X;
                    }
                    else if (fromCollider.direction == 1)
                    {
                        toCollider.m_Direction = DynamicBoneColliderBase.Direction.Y;
                    }
                    else
                    {
                        toCollider.m_Direction = DynamicBoneColliderBase.Direction.Z;
                    }

                    toCollider.m_Center.x = fromCollider.center.x;
                    toCollider.m_Center.y = fromCollider.center.y;
                    toCollider.m_Center.z = fromCollider.center.z;

                    toCollider.m_Height = fromCollider.height;
                    toCollider.m_Radius = fromCollider.radius;
                }
            }
        Debug.Log("Set all DynamicBoneColliders referenced Unity Colliders.");
    }

    [ContextMenu("Remove Colliders in Children set by Copy DynamicBoneColliders")]
    public void RemoveAllColliders()
    {
        var allChildren = GetAll(target);
        foreach (var obj in allChildren)
        {
            var toCollider = obj.GetComponent<DynamicBoneCollider>();
            if (toCollider)
            {
                DestroyImmediate(toCollider);
            }

        }
        Debug.Log("Remove all DynamicBoneColliders Set by CopyColliderInChildren.");
    }

    [ContextMenu("Set DynamicBoneColliders as Colliders in DynamicBone attatched in this component.")]
    public void SetTargetColliders()
    {
        var allTargetColliders = new List<DynamicBoneCollider>();
        var allChildren = GetAll(target);
        var db = GetComponent<DynamicBone>();
        foreach (var obj in allChildren)
        {
            DynamicBoneCollider dbc = obj.GetComponent<DynamicBoneCollider>();
            if (dbc)
            {
                allTargetColliders.Add(dbc);
            }
        }
        foreach (var col in allTargetColliders)
        {
            db.m_Colliders.Add(col);
        }
        Debug.Log("Set DynamicBoneColliders as Colliders in DynamicBone Attached in this component.");
    }

    [ContextMenu("Remove Target Colliders set by SetTargetColliders")]
    public void RemoveTargetColliders()
    {
        var db = GetComponent<DynamicBone>();
        db.m_Colliders.Clear();
                Debug.Log("Remove DynamicBoneColliders in DynamicBone Attached in this component.");
    }
    private List<GameObject>  GetAll (GameObject gameObject)
    {
        List<GameObject> allChildren = new List<GameObject> ();
        GetChildren (gameObject, ref allChildren);
        return allChildren;
    }

//子要素を取得してリストに追加
    private void GetChildren (GameObject obj, ref List<GameObject> allChildren)
    {
        Transform children = obj.GetComponentInChildren<Transform> ();
        //子要素がいなければ終了
        if (children.childCount == 0) {
            return;
        }
        foreach (Transform ob in children) {
            allChildren.Add (ob.gameObject);
            GetChildren (ob.gameObject, ref allChildren);
        }
    }
}

```

DynamicBoneColliderSetterのhandにはそれぞれOculusHand_L, OculusHand_RをDrag&Dropして、右クリックから、Copy Colliders in Chilrenを実行します。

これで、手の形に添ってDynamicBoneColliderとColliderが設置できました。

### おっぱいに触ってみよう！

いよいよ、おっぱいを揺らしてみたいと思います。

まず、J_Sec_L_Bust1とJ_Sec_R_Bust1にDynamicBoneをAddComponentして、以下の写真のようにパラメータを設定します。

![スクリーンショット 2021-05-29 223454.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/8c4341a1-eda1-3d06-0876-04f16e0dadf6.png)

RootとRadiusに関してはご自身のモデルにより、適宜調整してください。

次に先ほどのDynamicBoneColliderSetterをJ_Bip_L_Bust1とJ_Bip_L_Bust2にAddComponentして、HandにTrackingSpaceを入れ、右クリックからSet Hand Collidersを実行します。これにより、TrakingSpaceの下にあるすべてのDynamicBoneに対して、胸がColliderとして設置されます。

両胸のDynamicBoneにOculusHandのDynamicBoneColliderを設定したら、おっぱいに触れるようになっているので、アプリをBuildして、ぜひ触ってみてください。

### 髪の毛に触ってみよう

次に髪の毛を触れるようにしていきたいと思います。

髪の毛のDynamicBoneは[LongHairSupporter](https://sites.google.com/view/longhairsup)の力をお借りして設定していきます。

LongHairSupporterとありますが、短髪の場合でも、髪の毛を管理するのにとても便利なので使っていただけると、楽できます。

まず、写真のようにAvatar Major Ojbects とAvatar Hair Objects のHairs Parentを設定します。

![スクリーンショット 2021-05-31 145031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/e2190603-9164-77e5-038b-d8c20ba1b31d.png)

つぎに各髪の毛についてHairTypeを設定します。

- キャラクターの髪の毛が長髪の場合

アホ毛・触角 -> Others
前髪 -> Bangs
横(耳より前)の短い髪　-> Bangs
横(耳より前)の長い髪 -> Side Hair
後ろ髪 -> Back Hair

- キャラクターの髪の毛が短髪の場合

アホ毛・触角 -> Others
そのほかの髪 -> Bangs

以上のように設定してください。

そうしましたら、LongHairSupporter > Option > Dynamic Bone Reset のチェックをつけて、一番下の**DynamicBone&Collider生成**を押してください。

まず、J_Bip_C_Head > DBC_head > DBC_head_actualが作成され、ここにDynamicBoneColliderができているので、このDynamic Bone Collider　のRadiusを**1.0**にします。

つぎにLongHairSupporterのPrefabの子を使って、各髪の毛の編集をしていきます。

DynamicBoneModel_back, DynamicBoneModel_side, DynamicBoneModel_bangs, DynamicBoneModel_othersのDynamicBoneをそれぞれ以下の写真のように設定してください。

![スクリーンショット 2021-05-31 150513.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/0b4b7ba2-3e2c-8e32-aacb-cc8dceb2d53e.png)

![スクリーンショット 2021-05-31 150538.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/ec839e5b-a06d-0770-883f-5979ea1a0f1f.png)

![スクリーンショット 2021-05-31 150556.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/9fa06d56-5b30-828a-7e5f-4e0b1a06d48b.png)

![スクリーンショット 2021-05-31 150619.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/bbbf844b-eca4-f8f6-3f07-7a8bebc7426a.png)

つぎに、*Oculus Hand にDynamicBoneColliderの設置* で使用した、DynamicBoneColliderSetter.csをDynamicBoneModel_back, DynamicBoneModel_side, DynamicBoneModel_bangs, DynamicBoneModel_othersにそれぞれアタッチしてください。

DynamicBoneColliderSetterのHandには**TrackingSpace**をDrag&Dropして、右クリックから、SetHandCollidersを実行します。

すると、DynamicBoneに両手のColliderがすべて設置されるので、そうしましたらParameter Copy H の**DynamicBone設定コピー&ペースト**を実行します。

これをDynamicBoneModel_back, DynamicBoneModel_side, DynamicBoneModel_bangs, DynamicBoneModel_othersについて、行います。

以上で、髪の毛の設定については完了です。

以下に注意点を上げていきます。

- DynamicBoneModel_sideのColliderについて

DynamicBoneModel_sideのColliderには両手のColliderの他にも、DBC_UpperChestを用います。

もし、**DynamicBone設定コピー&ペースト**をした後に入れる場合は、必ずDynamicBoneのCollidersを**0**にしてから、CollidersにDBC_UpperChestをいれて、**DynamicBone設定コピー&ペースト**を押してください。

- DynamicBoneModelのパラメータ変更について

公式にあるとおりに**DynamicBone設定コピー&ペースト**、または**停止後ペースト**をおしていただければ大丈夫です。

ですが、かならずCollidersを0にしてから実行してください。髪の毛にCollidersが重複して入ります。

- Collidersの消し忘れをしそうな方

Assets > LonghairSupporter > Script > Editor > ParameterCopyHEditor.csのCopy()関数の一番下に以下のスクリプトを追加してください。

```    
　　private void Copy()
    {
        foreach (LonghairSupporter.HairsGroup group in longhairSupporter.hairsGroup)
        {
            ///
        }
        longhairSupporter.dynamicBoneModels[(int) parameterCopyH.hairType].m_Colliders.Clear();  //取得したDynamicBoneのColliderを初期化 <= この行！
    }
 
```

- Colliderを重複して入れてしまった方

以下のDynamicBoneClearner.csをJ_Bip_C_Headに追加し、右クリックからClean Dynamic Boneを実行してください。その後、LonghairSuporterの**DynamicBone&Collider生成**から、やり直します。この時は調整したパラメータはそのままなので、Colliderの追加だけ新規に行ってください。

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DynamicBoneCleaner : MonoBehaviour
{
    private DynamicBone db;

    [ContextMenu("Clean Dynamic Bone")]
    void RemoveDynamicBone()
    {
        var allChildren = GetAll(gameObject);
        foreach (var obj in  allChildren)
        {
            var db = obj.GetComponent<DynamicBone>();
            if (db)
            {
                DestroyImmediate(db);
            }
        }
    }

    private List<GameObject>  GetAll (GameObject gameObject)
    {
        List<GameObject> allChildren = new List<GameObject> ();
        allChildren.Add(gameObject);
        GetChildren (gameObject, ref allChildren);
        return allChildren;
    }

//子要素を取得してリストに追加
    private void GetChildren (GameObject obj, ref List<GameObject> allChildren)
    {
        Transform children = obj.GetComponentInChildren<Transform> ();
        //子要素がいなければ終了
        if (children.childCount == 0) {
            return;
        }
        foreach (Transform ob in children) {
            allChildren.Add (ob.gameObject);
            GetChildren (ob.gameObject, ref allChildren);
        }
    }
}

```

- 誤った髪の毛をHairTypeに設定してしまった

Side HairのところをBangにしてしまうなどのミスが発生してしまった方用です。

LonghairSupporterについては Hair Hung を行うために、髪の毛に新しくオブジェクトが追加されます。

これらがOculus Quest 2では変に実行され、その髪の毛がスーパーサイヤ人化（笑）することがあります。

そうなってしまった場合に、対処する方法です。

LonghairSupporterには追加したObjectを消す機能はないので、追加します。

Projectから Assets > LOnghairSupoperter > Script > Editor の　LonghairSupporterEditor.csを以下の用に書き換えます。

```
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(LonghairSupporter))]//拡張するクラスを指定
public class LonghairSupporterEditor : Editor
{
    private LonghairSupporter longhairSupporter;

    /// <summary>
    /// InspectorのGUIを更新
    /// </summary>
    public override void OnInspectorGUI()
    {
        //元のInspector部分を表示
        base.OnInspectorGUI();

        //targetを変換して対象を取得
        longhairSupporter = target as LonghairSupporter;

        if (GUILayout.Button("DynamicBone＆Collider生成"))
        {
            DynamicBoneCreate();
        }

        if (GUILayout.Button("LonghairSupporter 初期化"))
        {
            DynamicBoneRemove();
        }
    }

    public void DynamicBoneCreate()
    {
      ///
    }

    public void DynamicBoneRemove()
    {
        var head = longhairSupporter.head;
        var allChildren = GetAll(head.gameObject);
        foreach (var obj in allChildren)
        {
            if (obj.transform.parent.gameObject.name.EndsWith("_branch2"))
            {
                obj.transform.parent = obj.transform.parent.gameObject.transform.parent.gameObject.transform.parent.gameObject.transform.parent.gameObject.transform; 
            }
        }

        for (int i =  head.transform.childCount-1; i >=0 ; --i)
        {
            var child = head.transform.GetChild(i).gameObject;
            if (child.name.EndsWith("_root") || child.name == "DBC_head")
            {
                DestroyImmediate(child);
            }
        }
        Debug.Log("DynamicBone&Collider消去完了");
    }

    private void ParameterCheck()
    {
      ///
    }

    private void ClearObjects()
    {
      ///
    }

    private void DynamicBonePrepare()
    {
      ///
    }

    private void SetDynamicBoneCollider()
    {
      ///
    }

    private void SetHairHang()
    {
       ///
    }
    
    private void AfterCheck()
    {
      ///
    }

    private List<Transform> GetAffectedObjects(Transform root, List<Transform> exclusions)
    {
      ///
    }

    private void Alert(string message, bool error = true)
    {
       ///
    }
    private List<GameObject>  GetAll (GameObject gameObject)
    {
        List<GameObject> allChildren = new List<GameObject> ();
        allChildren.Add(gameObject);
        GetChildren (gameObject, ref allChildren);
        return allChildren;
    }

//子要素を取得してリストに追加
    private void GetChildren (GameObject obj, ref List<GameObject> allChildren)
    {
        Transform children = obj.GetComponentInChildren<Transform> ();
        //子要素がいなければ終了
        if (children.childCount == 0) {
            return;
        }
        foreach (Transform ob in children) {
            allChildren.Add (ob.gameObject);
            GetChildren (ob.gameObject, ref allChildren);
        }
    }

```

すると、LonghairSupporterのInspectorに**LonghairSupporter初期化**というボタンが出てくるので、これをクリックします。

次に、DynamicBoneClearner.csをJ_Bip_C_Headに追加し、右クリックからClean Dynamic Boneを実行してください。

これで、LonghairSupporterによるDynamicBoneの設定前の状態に戻すことができます。

誤った髪の毛のHairTypeを設定しなおして、再度**DynamicBone&Collider生成**を行ってください。


### スカートに触れてみよう！

最後にスカートに触れてみたいと思います。

まず、J_BipC_Hipsに空のオブジェクトをつくり、名前をJ_Bip_Skirtにします。

そのあとで、J_Bip_L_Upper_LegとJ_Bip_R_Lower_Legからオブジェクトを移動させて、写真のような構造にします。

![スクリーンショット 2021-05-31 161422.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/a25e8085-1792-3fa7-e3a0-9ef40f4332d8.png)

そうしましたら、J_Bip_SkirtにDynamicBoneをAddComponentから追加して写真のようにパラメータを設定します。

![スクリーンショット 2021-05-31 161643.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/afde535d-bda5-d1d2-4ffe-aafdc704c98b.png)

つぎに、J_Bip_SkirtにDynamicBoneColliderSetter.csを追加して、Handには**TrackinigSpace**を設定し、右クリックから Set Hand Collidersを実行します。

これでスカートにColliderが設定され、スカートをめくれるようになりました((+_+))

ちょっとしたコツなのですが、キャラクターの手にDynamicBoneColliderを設定し、J_Bip_SkirtのCollidersに追加すると、リアル感が増します。

[使用したVRMモデルとScriptについてのGit repository](https://github.com/blackpopo/MakingOutWith2DGirls)

[次回に続きます。](https://qiita.com/KayaTakashiro/items/f25e950c268d9a239576)