@syuji syuji / PhysCopy.cs
Created at Sat Apr 08 19:22:03 JST 2023
PhysCopy
PhysCopy.cs
Raw
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using VRC.Dynamics;

public class PhysCopy : EditorWindow
{
    GameObject targetAvatar;
    GameObject baseAvatar;
    bool delFlg;
    bool delDFlg;
    bool backUpFlg;
    bool checkFlg;
    string alertMessage;
    Vector2 textScrollPos = Vector2.zero;

    [MenuItem("USUAJI_tool/PhysCopy")]
    static void Init()
    {
        PhysCopy window = (PhysCopy)EditorWindow.GetWindow(typeof(PhysCopy));
        window.titleContent = new GUIContent("PhysCopy");
        window.Show();
    }
    void OnGUI()
    {
        using (var checkScope = new EditorGUI.ChangeCheckScope())
        {
            baseAvatar = EditorGUILayout.ObjectField("コピー元アバター", baseAvatar, typeof(GameObject), true) as GameObject;
            EditorGUILayout.LabelField("  ↓ copy");
            targetAvatar = EditorGUILayout.ObjectField("コピー先アバター", targetAvatar, typeof(GameObject), true) as GameObject;
            EditorGUILayout.Space();

            delFlg = EditorGUILayout.ToggleLeft("コピー先PhysBone/Colliderを削除", delFlg);
            delDFlg = EditorGUILayout.ToggleLeft("コピー先DynamicBone/Colliderを削除", delDFlg);
            checkFlg = EditorGUILayout.ToggleLeft("AvatarDescripterの有無を気にしない", checkFlg);
            backUpFlg = EditorGUILayout.ToggleLeft("怖いので一応バックアップ", backUpFlg);
            EditorGUILayout.Space();

            if (GUILayout.Button("コピー実行(ポチっとな)"))
            {
                CopyOperate();
            }
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("🔻エラーがあると、ここに出力(見づらかったら、メモ帳等にコピペしてね)");
            textScrollPos = EditorGUILayout.BeginScrollView(textScrollPos, GUILayout.Width(350), GUILayout.Height(100));
            {
                alertMessage = EditorGUILayout.TextArea(alertMessage, GUILayout.Width(350));
            }
        }
    }
    string CheckAvatar(GameObject avatar, string name)
    {
        string alertText = "";
        if (baseAvatar is null)
        {
            alertText += name + "がセットされておりません\n";
        }
        else
        {
            if (!checkFlg && baseAvatar.GetComponent("VRCAvatarDescriptor") is null)
            {
                alertText += name + "にはVRCAvatarDescriptorが含まれておりません\n";
            }

        }
        return alertText;
    }

    bool Check()
    {
        string alertText = "";

        alertText += CheckAvatar(baseAvatar, "コピー元アバター");
        alertText += CheckAvatar(targetAvatar, "コピー先アバター");
        if (alertText.Length != 0)
        {
            EditorUtility.DisplayDialog("alert", alertText, "Oh no");
            return false;
        }
        return true;
    }
    void CopyOperate()
    {
        if (!Check())
        {
            return;
        }
        if (!EditorUtility.DisplayDialog("確認",
        "コピー元アバター: " + baseAvatar.name + "\n" +
        "コピー先アバター: " + targetAvatar.name + "\n" +
        "コピー先PhysBone/Collider: " + (delFlg ? "削除" : "残す") + "\n" +
        "コピー先DynamicBone/Collider: " + (delDFlg ? "削除" : "残す") + "\n" +
        "バックアップ: " + (backUpFlg ? "する" : "しない") + "\n" +
        "\nこちらの設定で実行してよろしいですか?",
        "いいぞ!",
        "ちょっと待て"))
        {
            return;
        }
        if (backUpFlg)
        {
            Instantiate(targetAvatar);
        }
        if (delFlg)
        {
            foreach (Component c in targetAvatar.GetComponentsInChildren(typeof(VRCPhysBoneColliderBase)))
            {
                DestroyImmediate(c);
            }
            foreach (Component c in targetAvatar.GetComponentsInChildren(typeof(VRCPhysBoneBase)))
            {
                DestroyImmediate(c);
            }
        }
        if (delDFlg)
        {
            foreach (Component c in targetAvatar.GetComponentsInChildren<Component>())
            {
                if (c.GetType().Name.Equals("DynamicBone") || c.GetType().Name.Equals("DynamicBoneCollider"))
                {
                    DestroyImmediate(c);
                }
            }
        }
        string alertText = "";
        alertText += CopyPBC();
        if (alertText.Length != 0)
        {
            alertText = "以下のコライダーはコピー先オブジェクトが見つからず、コピーに失敗しました\n" + alertText + "\n";
        }

        string alertText2 = "";
        alertText2 += CopyPB();
        if (alertText2.Length != 0)
        {
            alertText2 = "以下のボーンはコピーに失敗したか、参照コライダーの変更に失敗しました\n" + alertText2 + "\n";
        }

        if (alertText.Length != 0 || alertText2.Length != 0)
        {
            EditorUtility.DisplayDialog("エラー箇所あり", "コピー中、エラーが発生した箇所がありました。詳しくは、エラーメッセージをご確認ください。", "ok");
            alertMessage = alertText + alertText2;
        }
        else
        {
            EditorUtility.DisplayDialog("終了", "コピー完了しました。", "ok");
        }
    }
    string GetPath(GameObject now)
    {

        string path = now.name;
        var current = now.transform.parent;
        while (current != null)
        {
            if (current.parent is null)
            {
                break;
            }
            path = current.name + "/" + path;
            current = current.parent;
        }
        return path;
    }
    string CopyPBC()
    {
        string alertText = "";

        List<string> createdPaths = new List<string>();
        // Create missing GameObjects before copying components
        foreach (Component c in baseAvatar.GetComponentsInChildren(typeof(VRCPhysBoneColliderBase)))
        {
            string path = GetPath(c.gameObject);
            Transform tf = targetAvatar.transform.Find(path);

            if (tf is null)
            {
                CreateEmptyGameObject(targetAvatar.transform, path, c.transform);
                createdPaths.Add(path);
            }
        }

        // Now copy the components
        foreach (Component c in baseAvatar.GetComponentsInChildren(typeof(VRCPhysBoneColliderBase)))
        {
            string path = GetPath(c.gameObject);
            Transform tf = targetAvatar.transform.Find(path);

            if (!(tf is null))
            {
                UnityEditorInternal.ComponentUtility.CopyComponent(c);
                UnityEditorInternal.ComponentUtility.PasteComponentAsNew(tf.gameObject);
            }
            else if (!createdPaths.Contains(path))
            {
                alertText += "  " + path + "\n";
            }
        }

        // Update rootTransform references
        foreach (VRCPhysBoneColliderBase c in targetAvatar.GetComponentsInChildren(typeof(VRCPhysBoneColliderBase)))
        {
            Transform rtf = c.rootTransform;

            if (rtf != null)
            {
                c.rootTransform = targetAvatar.transform.Find(GetPath(rtf.gameObject));
            }
        }

        return alertText;
    }
    string CopyPB()
    {
        string alertText = "";

        List<string> createdPaths = new List<string>();
        // Create missing GameObjects before copying components
        foreach (Component c in baseAvatar.GetComponentsInChildren(typeof(VRCPhysBoneBase)))
        {
            string path = GetPath(c.gameObject);
            Transform tf = targetAvatar.transform.Find(path);

            if (tf is null)
            {
                CreateEmptyGameObject(targetAvatar.transform, path, c.transform);
                createdPaths.Add(path);
            }
        }

        // Now copy the components
        foreach (Component c in baseAvatar.GetComponentsInChildren(typeof(VRCPhysBoneBase)))
        {
            string path = GetPath(c.gameObject);
            Transform tf = targetAvatar.transform.Find(path);

            if (!(tf is null))
            {
                UnityEditorInternal.ComponentUtility.CopyComponent(c);
                UnityEditorInternal.ComponentUtility.PasteComponentAsNew(tf.gameObject);
            }
            else if (!createdPaths.Contains(path))
            {
                alertText += "  " + path + "\n";
            }
        }
        //コピー元の参照先をtargetに直す
        foreach (VRCPhysBoneBase c in targetAvatar.GetComponentsInChildren(typeof(VRCPhysBoneBase)))
        {
            Transform rtf = c.rootTransform;
            if (rtf != null)
            {
                c.rootTransform = targetAvatar.transform.Find(GetPath(rtf.gameObject));
            }
            List<VRCPhysBoneColliderBase> cols = c.colliders;

            for (int i = 0; i < cols.Count; i++)
            {
                VRCPhysBoneColliderBase col = cols[i];
                if (col != null)
                {
                    string path = GetPath(col.gameObject);
                    Transform tf = targetAvatar.transform.Find(path);
                    if (tf != null)
                    {
                        Component[] tcols = tf.gameObject.GetComponents(typeof(VRCPhysBoneColliderBase));
                        foreach (VRCPhysBoneColliderBase tcol in tcols)
                        {
                            if (CeckColliderEqual(col, tcol))
                            {
                                cols[i] = tcol;
                            }
                        }
                    }
                    else
                    {
                        alertText += "  PhysBoneのCollider先を自アバに変更できませんでした: " + path + "\n";
                    }

                }
            }
        }
        return alertText;

    }
    //Objectに複数のコンポーネントがついてる時、一致を確認する
    bool CeckColliderEqual(VRCPhysBoneColliderBase col, VRCPhysBoneColliderBase tcol)
    {
        if (col.shapeType != tcol.shapeType)
        {
            return false;
        }
        if (col.radius != tcol.radius)
        {
            return false;
        }
        if (col.position.x != tcol.position.x ||
            col.position.y != tcol.position.y ||
            col.position.z != tcol.position.z)
        {
            return false;
        }
        if (col.rotation.x != tcol.rotation.x ||
            col.rotation.y != tcol.rotation.y ||
            col.rotation.z != tcol.rotation.z)
        {
            return false;
        }
        return true;
    }

    // Create empty GameObject function
    void CreateEmptyGameObject(Transform parent, string path, Transform referenceTransform)
    {
        string[] parts = path.Split('/');
        Transform currentParent = parent;

        for (int i = 0; i < parts.Length; i++)
        {
            string part = parts[i];
            Transform child = currentParent.Find(part);

            if (child == null)
            {
                GameObject newGameObject = new GameObject(part);
                newGameObject.transform.SetParent(currentParent);

                // Set the transform values to match the reference transform
                if (referenceTransform != null)
                {
                    newGameObject.transform.localPosition = referenceTransform.localPosition;
                    newGameObject.transform.localRotation = referenceTransform.localRotation;
                    newGameObject.transform.localScale = referenceTransform.localScale;
                }

                currentParent = newGameObject.transform;
            }
            else
            {
                currentParent = child;
            }
        }
    }
}