LoginSignup
13
8

More than 3 years have passed since last update.

VRMモデルのSpringBoneをコピーするツールを作りました

Last updated at Posted at 2019-06-19

はじめに

VRMモデルを作っていて、FBXから作り直しが発生したとき
VRM Spring Boneのつけ直しって大変じゃありませんか?
1発でコピーできるツールを作りました

vrm.gif

とりあえずツールだけほしい方

VRMモデルのSpringBoneをコピーするツール
こちらからダウンロードできます

使い方

1.メニューのVRM→Spring Bone Copyでメニューを開きます
2. 元GameObjectにSpringBoneの設定されているオブジェクトを
 コピー先GameObjectにSpringBoneの設定されていないオブジェクトを指定します
3.コピーボタンを押すことでコピーが完了します

ソースコード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Reflection;

namespace VRM
{
    public class VRMSpringBoneCopy : EditorWindow 
    {

        [MenuItem("VRM/Spring Bone Copy")]
        static void Open()
        {
            GetWindow<VRMSpringBoneCopy> ();
        }

        GameObject srcGo;

        GameObject dstGo;
        /// <summary>
        /// OnGUI is called for rendering and handling GUI events.
        /// This function can be called multiple times per frame (one call per event).
        /// </summary>
        void OnGUI()
        {
            EditorGUILayout.LabelField("元GameObject");
            this.srcGo = EditorGUILayout.ObjectField(this.srcGo, typeof(GameObject), true) as GameObject;
            EditorGUILayout.LabelField("コピー先GameObject");
            this.dstGo = EditorGUILayout.ObjectField(this.dstGo, typeof(GameObject), true) as GameObject;

            if(GUILayout.Button("コピー"))
            {
                copyComponents();
                showResult();
            }
        }

        Transform[] transformList;

        List<string> colliderTargetError = new List<string>();
        List<string> springTargetBone = new List<string>();

        List<string> springBoneError = new List<string>();

        void copyComponents()
        {
            this.colliderTargetError.Clear();
            this.springTargetBone.Clear();
            this.springBoneError.Clear();

            deleteComponents();

            this.transformList = this.dstGo.GetComponentsInChildren<Transform>();

            var colliders = this.srcGo.GetComponentsInChildren<VRMSpringBoneColliderGroup>();
            foreach(var c in colliders)
            {
                var targetTransform = this.transformList.FirstOrDefault(x => x.name == c.transform.name);
                if(targetTransform != null)
                {
                    var col = targetTransform.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
                    col.Colliders = new VRMSpringBoneColliderGroup.SphereCollider[c.Colliders.Length];
                    copyField(c, col,  "m_gizmoColor");
                    for(int i=0;i<col.Colliders.Length;i++)
                    {
                        col.Colliders[i] = new VRMSpringBoneColliderGroup.SphereCollider();
                        col.Colliders[i].Offset = c.Colliders[i].Offset;
                        col.Colliders[i].Radius = c.Colliders[i].Radius;
                    }
                }
                else
                {
                    Debug.LogWarning("Not found VRMSpringBoneColliderGroup bone->"+c.gameObject.name);
                    this.colliderTargetError.Add(c.gameObject.name);
                }
            }

            var springbone = this.srcGo.GetComponentsInChildren<VRMSpringBone>();
            foreach(var c in springbone)
            {
                var targetTransform = this.transformList.FirstOrDefault(x => x.name == c.transform.name);
                if(targetTransform != null)
                {
                    var spring = targetTransform.gameObject.AddComponent<VRMSpringBone>();
                    spring.m_comment = c.m_comment;
                    copyField(c, spring, "m_drawGizmo");
                    copyField(c, spring, "m_gizmoColor");
                    spring.m_stiffnessForce = c.m_stiffnessForce;
                    spring.m_gravityPower = c.m_gravityPower;
                    spring.m_gravityDir = c.m_gravityDir;
                    spring.m_dragForce = c.m_dragForce;
                    spring.m_center = searchTranform(c.m_center);
                    foreach(var b in c.RootBones)
                    {
                        var bb = searchTranform(b);
                        if(bb != null)
                        {
                            spring.RootBones.Add(bb);
                        }
                        else
                        {
                            this.springTargetBone.Add(b.name);
                        }
                    }
                    spring.m_hitRadius = c.m_hitRadius;
                    if(c.ColliderGroups.Length > 0)
                    {
                        spring.ColliderGroups = new VRMSpringBoneColliderGroup[c.ColliderGroups.Length];
                        for(int i=0;i<spring.ColliderGroups.Length;i++)
                        {
                            var t = searchTranform(c.ColliderGroups[i].transform);
                            if(t != null)
                            {
                                spring.ColliderGroups[i] = t.GetComponent<VRMSpringBoneColliderGroup>();
                            }
                            else
                            {
                                Debug.LogWarning("Not found VRMSpringBoneColliderGroup ->"+c.ColliderGroups[i].transform.name);
                                this.springTargetBone.Add(c.ColliderGroups[i].transform.name);
                            }
                        }
                    }

                }
                else
                {
                    Debug.LogWarning("Not found VRMSpringBone ->"+c.gameObject.name);
                    this.springBoneError.Add(c.gameObject.name);
                }
            }
        }

        void showResult()
        {
            if(this.colliderTargetError.Count == 0 &&
                this.springTargetBone.Count == 0 &&
                this.springBoneError.Count == 0)
            {
                EditorUtility.DisplayDialog("コピー完了", "正常にコピーが完了しました", "OK");
            }
            else
            {
                string message = "一部のコンポーネントのコピーができませんでした\n";
                if(this.colliderTargetError.Count > 0)
                {
                    message += "VRMSpringBoneColliderGroup のボーンが見つかりませんでした\n";
                    foreach(var e in this.colliderTargetError)
                    {
                        message += e+"\n";
                    }
                }

                if(this.springBoneError.Count > 0)
                {
                    message += "VRMSpringBoneのアタッチ先が見つかりませんでした\n";
                    foreach(var e in this.springBoneError)
                    {
                        message += e+"\n";
                    }
                }

                if(this.springTargetBone.Count > 0)
                {
                    message += "揺れ骨が見つかりませんでした\n";
                    foreach(var e in this.springTargetBone)
                    {
                        message += e+"\n";
                    }
                }

                EditorUtility.DisplayDialog("コピー完了", message, "OK");
            }
        }

        Transform searchTranform(Transform t)
        {
            if(t == null)
            {
                return null;
            }

            var value = this.transformList.FirstOrDefault(x => x.name == t.name);

            return value;
        }

        void deleteComponents()
        {
            var cols = this.dstGo.GetComponentsInChildren<VRMSpringBoneColliderGroup>();
            foreach(var c in cols)
            {
                DestroyImmediate(c);
            }

            var springs = this.dstGo.GetComponentsInChildren<VRMSpringBone>();
            foreach(var s in springs)
            {
                DestroyImmediate(s);
            }
        }

        // private変数のコピー
        void copyField(object src, object dst, string fieldName)
        {
            var srcField = src.GetType().GetField(fieldName,BindingFlags.Public | BindingFlags.NonPublic |
                                    BindingFlags.Instance | BindingFlags.Static |
                                    BindingFlags.DeclaredOnly);

            if(srcField == null)
            {
                Debug.LogWarning("srcField is null");
                return;
            }

            var dstField = dst.GetType().GetField(fieldName,BindingFlags.Public | BindingFlags.NonPublic |
                                    BindingFlags.Instance | BindingFlags.Static |
                                    BindingFlags.DeclaredOnly);

            if(dstField == null)
            {
                Debug.LogWarning("dstField is null");
                return;
            }

            dstField.SetValue(dst, srcField.GetValue(src));
        }
    }

}

解説

  • やっていることは本当に簡単で、コピー元のVRM Spring BoneがアタッチされているTransform名を取得し、コピー先の一致するTransformにAddComponentしてパラメータをコピーしているだけです

this.transformList = this.dstGo.GetComponentsInChildren<Transform>();
これでコピー先のTransform一覧を取得

var springbone = this.srcGo.GetComponentsInChildren<VRMSpringBone>();
foreach(var c in springbone)
{
    var targetTransform = this.transformList.FirstOrDefault(x => x.name == c.transform.name);

これでコピー元とTransform名が一致するコピー先のTransformを取得します

VRM Spring Boneは基本的にパラメータがpublicなのでそのままコピーできるのですが
SerializeFieldで表示しているパラメータはprivateになっているためコピーできません
そこで、リフレクションを使ってパラメータの取得と設定を行っています

// private変数のコピー
void copyField(object src, object dst, string fieldName)
{
    var srcField = src.GetType().GetField(fieldName,BindingFlags.Public | BindingFlags.NonPublic |
                            BindingFlags.Instance | BindingFlags.Static |
                            BindingFlags.DeclaredOnly);

    if(srcField == null)
    {
        Debug.LogWarning("srcField is null");
        return;
    }

    var dstField = dst.GetType().GetField(fieldName,BindingFlags.Public | BindingFlags.NonPublic |
                            BindingFlags.Instance | BindingFlags.Static |
                            BindingFlags.DeclaredOnly);

    if(dstField == null)
    {
        Debug.LogWarning("dstField is null");
        return;
    }

    dstField.SetValue(dst, srcField.GetValue(src));
}

参考

13
8
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
13
8