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"; } } //コピー元の参照先を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"; } } //コピー元の参照先を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; } } } }