Skip to content

Instantly share code, notes, and snippets.

@JLChnToZ
Last active August 10, 2021 12:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JLChnToZ/92ff421d45df811aea4012adf9bc738b to your computer and use it in GitHub Desktop.
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
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