@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 = "";
        //いったんコピー
        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 {
            alertText += "  " + path +"\n";
            // Create empty game object if not found
            CreateEmptyGameObject(targetAvatar.transform, path);
        }
    }
    //コピー元の参照先をtargetに直す
    foreach (VRCPhysBoneColliderBase c in targetAvatar.GetComponentsInChildren(typeof(VRCPhysBoneColliderBase)))
    {
        Transform rtf = c.rootTransform;
        //rootTransformが未設定の場合は触らない
        if (rtf != null) {
            c.rootTransform = targetAvatar.transform.Find(GetPath(rtf.gameObject));
        }
    }
    return alertText;
}
string CopyPB()
{
    string alertText = "";
    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
        {
            alertText += "  " + path + "\n";
            // Create empty game object if not found
            CreateEmptyGameObject(targetAvatar.transform, path);
        }

    }
    //コピー元の参照先を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)
    {
        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);
                currentParent = newGameObject.transform;
            }
            else
            {
                currentParent = child;
            }
        }
    }
}