4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-05-31

目次

1. BlenderでVroidのモデルを編集しよう
2. Oculus Quest 2の準備をしよう
3. UnityにVRMモデルを入れてみよう
4. VeryAnimationで遊んでみよう
5. DynamicBoneでいろいろ遊んでみよう
6. UnityのAnimationでいちゃいちゃしよう

デモとパッケージ

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

まず、目標としては

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

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

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

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

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

そして、AssetにSAColliderBuilderをimport します。

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

スクリーンショット 2021-05-29 214153.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

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

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

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

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

スクリーンショット 2021-05-29 221108.png

Oculus Hand にDynamicBoneColliderの設置

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

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

スクリーンショット 2021-05-29 221916.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

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の力をお借りして設定していきます。

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

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

スクリーンショット 2021-05-31 145031.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

スクリーンショット 2021-05-31 150538.png

スクリーンショット 2021-05-31 150556.png

スクリーンショット 2021-05-31 150619.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

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

スクリーンショット 2021-05-31 161643.png

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

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

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

使用したVRMモデルとScriptについてのGit repository

次回に続きます。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?