Created
March 18, 2015 23:22
-
-
Save karl-/992aed0936a1ef78d83b to your computer and use it in GitHub Desktop.
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; | |
/** | |
* An empty script used to provide some reference for null components so that we can delete them. | |
*/ | |
public class pb_DummyScript : MonoBehaviour {} |
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
/** | |
* Repairs missing pb_Object and pb_Entity references. It is based | |
* on this article by Unity Gems: http://unitygems.com/lateral1/ | |
*/ | |
using UnityEngine; | |
using UnityEditor; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace ProBuilder2.EditorCommon | |
{ | |
/** | |
* Extends MonoBehaviour Inspector, automatically fixing missing script | |
* references (typically caused by ProBuilder upgrade process). | |
*/ | |
[CustomEditor(typeof(MonoBehaviour))] | |
public class pb_MissingScriptEditor : Editor | |
{ | |
#region Members | |
static bool applyDummyScript = true; ///< If true, any null components that can't be set will have this script applied to their reference, allowing us to later remove them. | |
static int index = 0; ///< general idea of where we are in terms of processing this scene. | |
static float total; ///< general idea of how many missing script references are in this scene. | |
static bool doFix = false; ///< while true, the inspector will attempt to cycle to broken gameobjects until none are found. | |
static List<GameObject> unfixable = new List<GameObject>(); ///< if a non-pb missing reference is encountered, need to let the iterator know not to bother, | |
static MonoScript _mono_pb; ///< MonoScript assets | |
static MonoScript _mono_pe; ///< MonoScript assets | |
static MonoScript _mono_dummy; ///< MonoScript assets | |
/** | |
* Load the pb_Object and pb_Entity classes to MonoScript assets. Saves us from having to fall back on Reflection. | |
*/ | |
static void LoadMonoScript() | |
{ | |
GameObject go = new GameObject(); | |
pb_Object pb = go.AddComponent<pb_Object>(); | |
pb_Entity pe = go.AddComponent<pb_Entity>(); | |
pb_DummyScript du = go.AddComponent<pb_DummyScript>(); | |
_mono_pb = MonoScript.FromMonoBehaviour( pb ); | |
_mono_pe = MonoScript.FromMonoBehaviour( pe ); | |
_mono_dummy = MonoScript.FromMonoBehaviour( du ); | |
DestroyImmediate(go); | |
} | |
public MonoScript pb_monoscript | |
{ | |
get | |
{ | |
if(_mono_pb == null) LoadMonoScript(); | |
return _mono_pb; | |
} | |
} | |
public MonoScript pe_monoscript | |
{ | |
get | |
{ | |
if(_mono_pe == null) LoadMonoScript(); | |
return _mono_pe; | |
} | |
} | |
public MonoScript dummy_monoscript | |
{ | |
get | |
{ | |
if(_mono_dummy == null) LoadMonoScript(); | |
return _mono_dummy; | |
} | |
} | |
#endregion | |
[MenuItem("Tools/" + pb_Constant.PRODUCT_NAME + "/Repair/Repair Missing Script References")] | |
public static void MenuRepairMissingScriptReferences() | |
{ | |
FixAllScriptReferencesInScene(); | |
} | |
static void FixAllScriptReferencesInScene() | |
{ | |
EditorApplication.ExecuteMenuItem("Window/Inspector"); | |
Object[] all = Resources.FindObjectsOfTypeAll(typeof(GameObject)).Where(x => ((GameObject)x).GetComponents<Component>().Any(n => n == null) ).ToArray(); | |
total = all.Length; | |
unfixable.Clear(); | |
if(total > 1) | |
{ | |
Undo.RecordObjects(all, "Fix missing script references"); | |
index = 0; | |
doFix = true; | |
Next(); | |
} | |
else | |
{ | |
if( applyDummyScript ) | |
DeleteDummyScripts(); | |
EditorUtility.DisplayDialog("Success", "No missing ProBuilder script references found.", "Okay"); | |
} | |
} | |
/** | |
* Advance to the next gameobject with missing components. If none are found, display dialog and exit. | |
*/ | |
static void Next() | |
{ | |
EditorUtility.DisplayProgressBar("Repair ProBuilder Script References", "Fixing " + (index+1) + " out of " + total + " objects in scene.", ((float)index/total) ); | |
// Cycle through FindObjectsOfType on every Next() because using a static list didn't work for some reason. | |
foreach(GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject))) | |
{ | |
if(go.GetComponents<Component>().Any(x => x == null) && !unfixable.Contains(go)) | |
{ | |
if( (PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance || | |
PrefabUtility.GetPrefabType(go) == PrefabType.Prefab ) ) | |
{ | |
GameObject pref = (GameObject)PrefabUtility.GetPrefabParent(go); | |
if(pref && (pref.GetComponent<pb_Object>() || pref.GetComponent<pb_Entity>())) | |
{ | |
unfixable.Add(go); | |
continue; | |
} | |
} | |
if(go.hideFlags != HideFlags.None) | |
{ | |
unfixable.Add(go); | |
continue; | |
} | |
Selection.activeObject = go; | |
return; | |
} | |
} | |
pb_Object[] pbs = (pb_Object[])Resources.FindObjectsOfTypeAll(typeof(pb_Object)); | |
for(int i = 0; i < pbs.Length; i++) | |
{ | |
EditorUtility.DisplayProgressBar("Checking ProBuilder Meshes", "Refresh " + (i+1) + " out of " + total + " objects in scene.", ((float)i/pbs.Length) ); | |
pbs[i].ToMesh(); | |
pbs[i].Refresh(); | |
pbs[i].Finalize(); | |
} | |
EditorUtility.ClearProgressBar(); | |
if( applyDummyScript ) | |
DeleteDummyScripts(); | |
EditorUtility.DisplayDialog("Success", "Successfully repaired " + total + " ProBuilder objects.", "Okay"); | |
if(!EditorApplication.SaveCurrentSceneIfUserWantsTo()) | |
Debug.LogWarning("Repaired script references will be lost on exit if this scene is not saved!"); | |
doFix = false; | |
skipEvent = true; | |
} | |
/** | |
* SerializedProperty names found in pb_Entity. | |
*/ | |
List<string> PB_OBJECT_SCRIPT_PROPERTIES = new List<string>() | |
{ | |
"_sharedIndices", | |
"_vertices", | |
"_uv", | |
"_sharedIndicesUV", | |
"_quads" | |
}; | |
/** | |
* SerializedProperty names found in pb_Object. | |
*/ | |
List<string> PB_ENTITY_SCRIPT_PROPERTIES = new List<string>() | |
{ | |
"pb", | |
"userSetDimensions", | |
"_entityType", | |
"forceConvex" | |
}; | |
// Prevents ArgumentException after displaying 'Done' dialog. For some reason the Event loop skips layout phase after DisplayDialog. | |
private static bool skipEvent = false; | |
public override void OnInspectorGUI() | |
{ | |
if(skipEvent && Event.current.type == EventType.Repaint) | |
{ | |
skipEvent = false; | |
return; | |
} | |
SerializedProperty scriptProperty = this.serializedObject.FindProperty("m_Script"); | |
if(scriptProperty == null || scriptProperty.objectReferenceValue != null) | |
{ | |
if(doFix) | |
{ | |
if(Event.current.type == EventType.Repaint) | |
{ | |
Next(); | |
} | |
} | |
else | |
{ | |
base.OnInspectorGUI(); | |
} | |
return; | |
} | |
int pbObjectMatches = 0, pbEntityMatches = 0; | |
// Shows a detailed tree view of all the properties in this serializedobject. | |
// GUILayout.Label( SerializedObjectToString(this.serializedObject) ); | |
SerializedProperty iterator = this.serializedObject.GetIterator(); | |
iterator.Next(true); | |
while( iterator.Next(true) ) | |
{ | |
if( PB_OBJECT_SCRIPT_PROPERTIES.Contains(iterator.name) ) | |
pbObjectMatches++; | |
if( PB_ENTITY_SCRIPT_PROPERTIES.Contains(iterator.name) ) | |
pbEntityMatches++; | |
} | |
// If we can fix it, show the help box, otherwise just default inspector it up. | |
if(pbObjectMatches >= 3 || pbEntityMatches >= 3) | |
{ | |
EditorGUILayout.HelpBox("Missing Script Reference\n\nProBuilder can automatically fix this missing reference. To fix all references in the scene, click \"Fix All in Scene\". To fix just this one, click \"Reconnect\".", MessageType.Warning); | |
} | |
else | |
{ | |
if(doFix) | |
{ | |
if( applyDummyScript ) | |
{ | |
scriptProperty.objectReferenceValue = dummy_monoscript; | |
scriptProperty.serializedObject.ApplyModifiedProperties(); | |
scriptProperty = this.serializedObject.FindProperty("m_Script"); | |
scriptProperty.serializedObject.Update(); | |
} | |
else | |
{ | |
unfixable.Add( ((Component)target).gameObject ); | |
} | |
Next(); | |
GUIUtility.ExitGUI(); | |
return; | |
} | |
else | |
{ | |
base.OnInspectorGUI(); | |
} | |
return; | |
} | |
GUI.backgroundColor = Color.green; | |
if(!doFix) | |
{ | |
if(GUILayout.Button("Fix All in Scene")) | |
{ | |
FixAllScriptReferencesInScene(); | |
return; | |
} | |
} | |
GUI.backgroundColor = Color.cyan; | |
if((doFix && Event.current.type == EventType.Repaint) || GUILayout.Button("Reconnect")) | |
{ | |
if(pbObjectMatches >= 3) // only increment for pb_Object otherwise the progress bar will fill 2x faster than it should | |
{ | |
index++; | |
} | |
else | |
{ | |
// Make sure that pb_Object is fixed first if we're automatically cycling objects. | |
if(doFix && ((Component)target).gameObject.GetComponent<pb_Object>() == null) | |
return; | |
} | |
if(!doFix) | |
{ | |
Undo.RegisterCompleteObjectUndo(target, "Fix missing reference."); | |
} | |
// Debug.Log("Fix: " + (pbObjectMatches > 2 ? "pb_Object" : "pb_Entity") + " " + ((Component)target).gameObject.name); | |
scriptProperty.objectReferenceValue = pbObjectMatches >= 3 ? pb_monoscript : pe_monoscript; | |
scriptProperty.serializedObject.ApplyModifiedProperties(); | |
scriptProperty = this.serializedObject.FindProperty("m_Script"); | |
scriptProperty.serializedObject.Update(); | |
if(doFix) | |
Next(); | |
GUIUtility.ExitGUI(); | |
} | |
GUI.backgroundColor = Color.white; | |
} | |
/** | |
* Scan the scene for gameObjects referencing `pb_DummyScript` and delete them. | |
*/ | |
static void DeleteDummyScripts() | |
{ | |
pb_DummyScript[] dummies = (pb_DummyScript[])Resources.FindObjectsOfTypeAll(typeof(pb_DummyScript)); | |
dummies = dummies.Where(x => x.hideFlags == HideFlags.None).ToArray(); | |
if(dummies.Length > 0) | |
{ | |
if( EditorUtility.DisplayDialog("Found Unrepairable Objects", "Repair script found " + dummies.Length + " missing components that could not be repaired. Would you like to delete those components now?", "Delete", "Cancel")) | |
{ | |
Undo.RecordObjects(dummies.Select(x=>x.gameObject).ToArray(), "Delete Broken Scripts"); | |
for(int i = 0; i < dummies.Length; i++) | |
GameObject.DestroyImmediate( dummies[i] ); | |
} | |
} | |
} | |
/** | |
* Returns a formatted string with all properties in serialized object. | |
*/ | |
static string SerializedObjectToString(SerializedObject serializedObject) | |
{ | |
System.Text.StringBuilder sb = new System.Text.StringBuilder(); | |
if(serializedObject == null) | |
{ | |
sb.Append("NULL"); | |
return sb.ToString(); | |
} | |
SerializedProperty iterator = serializedObject.GetIterator(); | |
iterator.Next(true); | |
while( iterator.Next(true) ) | |
{ | |
string tabs = ""; | |
for(int i = 0; i < iterator.depth; i++) tabs += "\t"; | |
sb.AppendLine(tabs + iterator.name + (iterator.propertyType == SerializedPropertyType.ObjectReference && iterator.type.Contains("Component") && iterator.objectReferenceValue == null ? " -> NULL" : "") ); | |
tabs += " - "; | |
sb.AppendLine(tabs + "Type: (" + iterator.type + " / " + iterator.propertyType + " / " + " / " + iterator.name + ")"); | |
sb.AppendLine(tabs + iterator.propertyPath); | |
sb.AppendLine(tabs + "Value: " + SerializedPropertyValue(iterator)); | |
} | |
return sb.ToString(); | |
} | |
/** | |
* Return a string from the value of a SerializedProperty. | |
*/ | |
static string SerializedPropertyValue(SerializedProperty sp) | |
{ | |
switch(sp.propertyType) | |
{ | |
case SerializedPropertyType.Integer: | |
return sp.intValue.ToString(); | |
case SerializedPropertyType.Boolean: | |
return sp.boolValue.ToString(); | |
case SerializedPropertyType.Float: | |
return sp.floatValue.ToString(); | |
case SerializedPropertyType.String: | |
return sp.stringValue.ToString(); | |
case SerializedPropertyType.Color: | |
return sp.colorValue.ToString(); | |
case SerializedPropertyType.ObjectReference: | |
return (sp.objectReferenceValue == null ? "null" : sp.objectReferenceValue.name); | |
case SerializedPropertyType.LayerMask: | |
return sp.intValue.ToString(); | |
case SerializedPropertyType.Enum: | |
return sp.enumValueIndex.ToString(); | |
case SerializedPropertyType.Vector2: | |
return sp.vector2Value.ToString(); | |
case SerializedPropertyType.Vector3: | |
return sp.vector3Value.ToString(); | |
// Not public api as of 4.3? | |
// case SerializedPropertyType.Vector4: | |
// return sp.vector4Value.ToString(); | |
case SerializedPropertyType.Rect: | |
return sp.rectValue.ToString(); | |
case SerializedPropertyType.ArraySize: | |
return sp.intValue.ToString(); | |
case SerializedPropertyType.Character: | |
return "Character"; | |
case SerializedPropertyType.AnimationCurve: | |
return sp.animationCurveValue.ToString(); | |
case SerializedPropertyType.Bounds: | |
return sp.boundsValue.ToString(); | |
case SerializedPropertyType.Gradient: | |
return "Gradient"; | |
default: | |
return "Unknown type"; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment