Last active
August 10, 2021 12:11
-
-
Save JLChnToZ/92ff421d45df811aea4012adf9bc738b to your computer and use it in GitHub Desktop.
Editor helper for finding (and try to fix) missing Udon references when updating 2018 project to 2019
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using UnityEditor.Experimental.SceneManagement; | |
using System.Collections.Generic; | |
using VRC.SDKBase; | |
using VRC.Udon; | |
using UnityObject = UnityEngine.Object; | |
public class FixSceneReference : EditorWindow { | |
const string PROGRAM_SOURCE_PROP = "programSource"; | |
const string SERIALIZED_ASSET_PROP = "serializedProgramAsset"; | |
UdonBehaviour[] searchResult; | |
Dictionary<UdonBehaviour, UdonBehaviour> prefabMapping = new Dictionary<UdonBehaviour, UdonBehaviour>(); | |
Vector2 scrollPos; | |
bool showMissingOnly; | |
UdonBehaviour assignTarget; | |
int currentPickerWindow; | |
[MenuItem("Tools/Fix Scene UdonBehaviour References")] | |
public static void GetWindow() => GetWindow<FixSceneReference>(); | |
void OnGUI() { | |
if (GUILayout.Button("Find Assets")) { | |
searchResult = FindObjectsOfType<UdonBehaviour>(); | |
prefabMapping.Clear(); | |
scrollPos = Vector2.zero; | |
} | |
showMissingOnly = EditorGUILayout.ToggleLeft("Show Missing Only", showMissingOnly); | |
if (searchResult != null && searchResult.Length > 0) { | |
if (GUILayout.Button("Fix All")) { | |
foreach (var dest in searchResult) FixOne(dest); | |
} | |
scrollPos = EditorGUILayout.BeginScrollView(scrollPos); | |
EditorGUILayout.BeginVertical(); | |
var contentColor = GUI.contentColor; | |
var options = new GUILayoutOption[] { GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.ExpandWidth(false) }; | |
foreach (var dest in searchResult) { | |
UnityObject progSource = dest.programSource; | |
if (progSource != null && showMissingOnly) continue; | |
EditorGUILayout.BeginHorizontal(); | |
if (progSource == null) progSource = dest; | |
if (GUILayout.Button(EditorGUIUtility.ObjectContent(progSource, progSource.GetType()), EditorStyles.label, options)) { | |
EditorGUIUtility.PingObject(dest); | |
} | |
GUILayout.FlexibleSpace(); | |
UdonBehaviour src = dest, last = dest; | |
GUI.contentColor = dest.programSource == null ? Color.red : Color.green; | |
EditorGUILayout.SelectableLabel(dest.GetInstanceID().ToString(), EditorStyles.textField, options); | |
while (prefabMapping.TryGetValue(src, out src)) { | |
GUI.contentColor = src.programSource == null ? Color.red : Color.green; | |
EditorGUILayout.SelectableLabel(src.GetInstanceID().ToString(), EditorStyles.textField, options); | |
if (GUILayout.Button("Ping", EditorStyles.miniButton, options)) | |
EditorGUIUtility.PingObject(src); | |
last = src; | |
} | |
GUI.contentColor = contentColor; | |
if (GUILayout.Button("Fetch Prefab", EditorStyles.miniButton, options)) { | |
src = PrefabUtility.GetCorrespondingObjectFromSource(last); | |
if (src != null) prefabMapping[last] = src; | |
} | |
if (GUILayout.Button("Auto Fix", EditorStyles.miniButtonLeft, options)) { | |
FixOne(dest); | |
} | |
if (GUILayout.Button("Manual Fix", EditorStyles.miniButtonRight, options)) { | |
assignTarget = dest; | |
ShowPicker(); | |
} | |
EditorGUILayout.EndHorizontal(); | |
} | |
GUI.contentColor = contentColor; | |
GUILayout.FlexibleSpace(); | |
EditorGUILayout.EndVertical(); | |
EditorGUILayout.EndScrollView(); | |
} | |
if (assignTarget != null && Event.current.commandName == "ObjectSelectorUpdated" && EditorGUIUtility.GetObjectPickerControlID() == currentPickerWindow) { | |
currentPickerWindow = -1; | |
if (EditorGUIUtility.GetObjectPickerObject() is AbstractUdonProgramSource destObj) { | |
var destSo = new SerializedObject(assignTarget); | |
destSo.FindProperty(PROGRAM_SOURCE_PROP).objectReferenceValue = destObj; | |
destSo.FindProperty(SERIALIZED_ASSET_PROP).objectReferenceValue = destObj.SerializedProgramAsset; | |
destSo.ApplyModifiedProperties(); | |
} | |
} | |
} | |
public static void FixOne(UdonBehaviour dest) { | |
if (dest.programSource != null) return; | |
var destSo = new SerializedObject(dest); | |
var destProgSoruceProp = destSo.FindProperty(PROGRAM_SOURCE_PROP); | |
var destSerializedAssetProp = destSo.FindProperty(SERIALIZED_ASSET_PROP); | |
bool replaced = false; | |
var parent = dest; | |
while (!replaced && parent != null) { | |
var assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(parent); | |
parent = PrefabUtility.GetCorrespondingObjectFromSource(parent); | |
var siblingPath = GetSiblingPath(dest); | |
if (string.IsNullOrEmpty(assetPath)) continue; | |
var go = PrefabUtility.LoadPrefabContents(assetPath); | |
if (go == null) continue; | |
foreach (var x in RecursiveFind<UdonBehaviour>(go)) | |
if (CompareSiblingPath(GetSiblingPath(x), siblingPath)) { | |
var so = new SerializedObject(x); | |
var progSoruce = so.FindProperty(PROGRAM_SOURCE_PROP).objectReferenceValue; | |
if (progSoruce != null) { | |
destProgSoruceProp.objectReferenceValue = progSoruce; | |
replaced = true; | |
} | |
var serializedAsset = so.FindProperty(SERIALIZED_ASSET_PROP).objectReferenceValue; | |
if (serializedAsset != null) { | |
destSerializedAssetProp.objectReferenceValue = serializedAsset; | |
replaced = true; | |
} | |
destSo.ApplyModifiedProperties(); | |
break; | |
} | |
PrefabUtility.UnloadPrefabContents(go); | |
} | |
} | |
public static int[] GetSiblingPath(Component target) { | |
var result = new List<int>(); | |
var transform = target.transform; | |
while (transform != null) { | |
var parent = transform.parent; | |
if (parent == null) break; | |
result.Add(transform.GetSiblingIndex()); | |
transform = parent; | |
} | |
return result.ToArray(); | |
} | |
public static bool CompareSiblingPath(int[] src, int[] dest) { | |
for (int i = 0, l = Mathf.Min(src.Length, dest.Length); i < l; i++) { | |
if (src[i] != dest[i]) return false; | |
} | |
return true; | |
} | |
public static T[] RecursiveFind<T>(GameObject root) where T : Component { | |
var stack = new Stack<Transform>(); | |
var result = new List<T>(); | |
stack.Push(root.transform); | |
while (stack.Count > 0) { | |
var current = stack.Pop(); | |
result.AddRange(current.GetComponents<T>()); | |
for (int i = 0, l = current.childCount; i < l; i++) | |
stack.Push(current.GetChild(i)); | |
} | |
return result.ToArray(); | |
} | |
void ShowPicker() { | |
currentPickerWindow = GUIUtility.GetControlID(FocusType.Passive) + 100; | |
EditorGUIUtility.ShowObjectPicker<AbstractUdonProgramSource>(null, false, "", currentPickerWindow); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment