Skip to content

Instantly share code, notes, and snippets.

@JohannesDeml
Forked from ulrikdamm/EditPrefab.cs
Last active May 17, 2023 04:40
Show Gist options
  • Save JohannesDeml/5802473b569718c9c86de906b7039aec to your computer and use it in GitHub Desktop.
Save JohannesDeml/5802473b569718c9c86de906b7039aec to your computer and use it in GitHub Desktop.
Unity editor script for better editing of prefabs. Put in Assets/Editor.
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
// From https://gist.github.com/JohannesDeml/5802473b569718c9c86de906b7039aec
// Original https://gist.github.com/ulrikdamm/338392c3b0900de225ec6dd10864cab4
// Adds a "Edit Prefab" option in the Assets menu (or right clicking an asset in the project browser).
// This opens an empty scene with your prefab where you can edit it.
// Put this script in your project as Assets/Editor/EditPrefab.cs
public class EditPrefabInScene
{
private static Object GetPrefab(Object selection)
{
if (selection == null)
{
return null;
}
var prefabType = PrefabUtility.GetPrefabType(selection);
if (prefabType == PrefabType.PrefabInstance)
{
var prefab = PrefabUtility.GetPrefabParent(selection);
return prefab;
}
if (prefabType == PrefabType.Prefab)
{
return selection;
}
return null;
}
[MenuItem("Assets/Edit prefab")]
public static void EditPrefab()
{
var prefab = GetPrefab(Selection.activeObject);
EditorSceneManager.SaveOpenScenes();
var currentScenes = new SceneSetupWrapper();
currentScenes.TakeSnapshot();
var scene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
var instance = (GameObject) PrefabUtility.InstantiatePrefab(prefab, scene);
Selection.activeObject = instance;
SceneView.lastActiveSceneView.FrameSelected();
var returnHandler = new ReturnToSceneGUI();
returnHandler.PreviousSceneSetup = currentScenes;
returnHandler.PrefabInstance = instance;
}
[MenuItem("Assets/Edit prefab", true)]
public static bool EditPrefabValidator()
{
var prefab = GetPrefab(Selection.activeObject);
return prefab != null;
}
}
public sealed class SceneSetupWrapper
{
private string[] scenePaths;
private bool[] scenesLoaded;
private int activeSceneIndex;
public void TakeSnapshot()
{
int sceneCount = SceneManager.sceneCount;
scenePaths = new string[sceneCount];
scenesLoaded = new bool[sceneCount];
var activeScene = SceneManager.GetActiveScene();
for (int i = 0; i < sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
scenePaths[i] = scene.path;
scenesLoaded[i] = scene.isLoaded;
if (scene == activeScene)
{
activeSceneIndex = i;
}
}
}
public void OpenSetup()
{
if (scenePaths.Length == 0)
{
Debug.LogError("Can't open scene setup, no data stored.");
return;
}
try
{
bool canceled = false;
int sceneCount = scenePaths.Length;
canceled = EditorUtility.DisplayCancelableProgressBar("Loading scenes",
"Loading scenes from Setup", (float)0 / sceneCount);
Scene activeScene = EditorSceneManager.OpenScene(scenePaths[0]);
if (canceled)
{
EditorUtility.ClearProgressBar();
return;
}
for (int i = 1; i < sceneCount; i++)
{
canceled = EditorUtility.DisplayCancelableProgressBar("Loading scenes",
"Loading scenes from Setup", (float)i / sceneCount);
if (canceled)
{
EditorUtility.ClearProgressBar();
return;
}
var openSceneMode = scenesLoaded[i] ? OpenSceneMode.Additive : OpenSceneMode.AdditiveWithoutLoading;
var scene = EditorSceneManager.OpenScene(scenePaths[i], openSceneMode);
if (activeSceneIndex == i)
{
activeScene = scene;
}
}
SceneManager.SetActiveScene(activeScene);
}
finally
{
EditorUtility.ClearProgressBar();
}
}
}
public sealed class ReturnToSceneGUI
{
public SceneSetupWrapper PreviousSceneSetup;
public GameObject PrefabInstance;
private GUIStyle style;
public ReturnToSceneGUI()
{
SceneView.onSceneGUIDelegate += RenderSceneGUI;
style = new GUIStyle();
style.margin = new RectOffset(10, 10, 10, 10);
}
public void RenderSceneGUI(SceneView sceneview)
{
Handles.BeginGUI();
GUILayout.BeginArea(new Rect(20, 20, 180, 300), style);
var rect = EditorGUILayout.BeginVertical();
GUI.Box(rect, GUIContent.none);
if (GUILayout.Button("Apply and return", new GUILayoutOption[0]))
{
PrefabUtility.ReplacePrefab(PrefabInstance, PrefabUtility.GetPrefabParent(PrefabInstance), ReplacePrefabOptions.ConnectToPrefab);
SceneView.onSceneGUIDelegate -= RenderSceneGUI;
PreviousSceneSetup.OpenSetup();
}
if (GUILayout.Button("Discard changes", new GUILayoutOption[0]))
{
SceneView.onSceneGUIDelegate -= RenderSceneGUI;
PreviousSceneSetup.OpenSetup();
}
EditorGUILayout.EndVertical();
GUILayout.EndArea();
Handles.EndGUI();
}
}
@IRobS
Copy link

IRobS commented May 23, 2020

I'm really grateful for this code, it's really helpful, but when I hit the Discard or Apply button I get a NullReferenceException error in the Console. Nothing actually seems to break, but it concerns me.

NullReferenceException
UnityEditor.SceneView.OnGUI () (at <480b5a3bd3214ce5831f6dd8c68fdc55>:0)
(removed the massive callstack)

@JohannesDeml
Copy link
Author

Seems like an error for drawing the buttons. I hadn't had the problem when i created the script, Iguess unity might have changed some logic since then. The error should not break anything for your prefabs, but if you manage to fix it, i'm happy to include the changes here :)

@IRobS
Copy link

IRobS commented May 24, 2020

It seems that DisplayCancelableProgressBar is known to cause these NullReferenceExceptions, although it doesn't seem to do any harm.
I resolved it by adding
EditorGUIUtility.ExitGUI();
After each of the
EditorUtility.ClearProgressBar();

Not very important, but some of your solution is depreciated:
onSceneGUIDelegate
is replaced with
duringSceneGui

The prefabType is replaced with prefabAssetType, and ReplacePrefab -> SaveAsPrefabAssetAndConnect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment