Created
June 5, 2024 13:58
-
-
Save kolja/57f7a8cc5009716747ee80208c3c177f to your computer and use it in GitHub Desktop.
Unity: Create Prefab from Selection
This file contains hidden or 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
// Drop this script in the Editor folder of your project | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using System; | |
using System.Reflection; | |
using System.Linq; | |
using System.IO; | |
public class CreatePrefabWindow : EditorWindow | |
{ | |
private string prefabName = "NewPrefab"; | |
private bool shouldFocusTextField = true; | |
[MenuItem("Tools/Create Prefab from Selection %#M", false, 10)] | |
public static void ShowWindow() | |
{ | |
GetWindow<CreatePrefabWindow>("Create Prefab"); | |
} | |
private void OnGUI() | |
{ | |
GUILayout.Label("Enter Prefab Name", EditorStyles.boldLabel); | |
GUI.SetNextControlName("PrefabNameField"); | |
prefabName = EditorGUILayout.TextField("Prefab Name", prefabName); | |
if (shouldFocusTextField) | |
{ | |
GUI.FocusControl("PrefabNameField"); | |
EditorGUI.FocusTextInControl("PrefabNameField"); | |
shouldFocusTextField = false; | |
} | |
Event e = Event.current; | |
if (e.type == EventType.KeyDown && e.keyCode == KeyCode.Return) | |
{ | |
CreatePrefab(); | |
e.Use(); | |
} | |
if (GUILayout.Button("Create Prefab")) | |
{ | |
CreatePrefab(); | |
} | |
} | |
private bool IsDocked() | |
{ | |
BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; | |
MethodInfo isDockedMethod = typeof( EditorWindow ).GetProperty( "docked", fullBinding ).GetGetMethod( true ); | |
return (bool) isDockedMethod.Invoke(this, null); | |
} | |
// Overloaded CalculateBounds function for a single GameObject | |
private Bounds? CalculateBounds(GameObject obj) | |
{ | |
Renderer renderer = obj.GetComponent<Renderer>(); | |
if (renderer != null) | |
{ | |
return renderer.bounds; | |
} | |
Bounds? combinedBounds = null; | |
foreach (Transform child in obj.transform) | |
{ | |
Bounds? childBounds = CalculateBounds(child.gameObject); | |
combinedBounds = Combine(combinedBounds, childBounds); | |
} | |
return combinedBounds; | |
} | |
// Overloaded CalculateBounds function for a list of GameObjects | |
private Bounds? CalculateBounds(GameObject[] objects) | |
{ | |
return objects.Aggregate((Bounds?)null, (acc, obj) => Combine(acc, CalculateBounds(obj))); | |
} | |
// Helper function to encapsulate two Bounds | |
private Bounds? Combine(Bounds? bounds1, Bounds? bounds2) | |
{ | |
if (bounds1.HasValue && bounds2.HasValue) { | |
Bounds encapsulatedBounds = bounds1.Value; | |
encapsulatedBounds.Encapsulate(bounds2.Value); | |
return encapsulatedBounds; | |
} | |
return bounds1 ?? bounds2; | |
} | |
private void CreatePrefab() | |
{ | |
// Get the selected objects | |
GameObject[] selectedObjects = Selection.gameObjects; | |
if (selectedObjects.Length == 0) | |
{ | |
Debug.LogError("No objects selected to create prefab."); | |
return; | |
} | |
// Create a new empty GameObject | |
GameObject parentObject = new GameObject(prefabName); | |
// Parent the selected objects to the new empty GameObject | |
// and Temporarily disable colliders of the selected objects | |
List<Collider> disabledColliders = new List<Collider>(); | |
foreach (GameObject obj in selectedObjects) | |
{ | |
Collider collider = obj.GetComponent<Collider>(); | |
obj.transform.SetParent(parentObject.transform); | |
if (collider != null) | |
{ | |
collider.enabled = false; | |
disabledColliders.Add(collider); | |
} | |
} | |
// Calculate the bounding box of the selected objects | |
Bounds? bounds = CalculateBounds(selectedObjects); | |
if (!bounds.HasValue) | |
{ | |
Debug.LogError("No renderers found in the selected objects or their children."); | |
return; | |
} | |
// Calculate the center and the top point of the bounding box | |
Vector3 center = bounds.Value.center; | |
float topY = bounds.Value.max.y; | |
float rootYPosition = 0; | |
// Perform a downward raycast from the center position to find the rootYPosition | |
Vector3 raycastOrigin = new Vector3(center.x, topY, center.z); | |
RaycastHit hit; | |
if (Physics.Raycast(raycastOrigin, Vector3.down, out hit)) rootYPosition = hit.point.y; | |
// Adjust the selected objects' positions relative to the common center | |
foreach (GameObject obj in selectedObjects) | |
{ | |
Vector3 newPosition = obj.transform.position - center; | |
obj.transform.position = new Vector3(newPosition.x, obj.transform.position.y - rootYPosition, newPosition.z); | |
} | |
// re-enable the colliders | |
foreach (Collider collider in disabledColliders) | |
{ | |
collider.enabled = true; | |
} | |
// Create a prefab | |
string prefabPath = "Assets/Prefabs/" + prefabName + ".prefab"; | |
prefabPath = AssetDatabase.GenerateUniqueAssetPath(prefabPath); | |
if (!Directory.Exists("Assets/Prefabs")) | |
{ | |
Directory.CreateDirectory("Assets/Prefabs"); | |
} | |
PrefabUtility.SaveAsPrefabAsset(parentObject, prefabPath); | |
DestroyImmediate(parentObject); | |
// Load the newly created prefab and instantiate it in the scene | |
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath); | |
if (prefab == null) | |
{ | |
Debug.LogError("Prefab could not be loaded from " + prefabPath); | |
return; | |
} | |
GameObject instantiatedPrefab = PrefabUtility.InstantiatePrefab(prefab) as GameObject; | |
if (instantiatedPrefab == null) { | |
Debug.LogError("Failed to instantiate the prefab."); | |
return; | |
} | |
instantiatedPrefab.transform.position = new Vector3(center.x, rootYPosition, center.z); | |
Debug.Log("Prefab instantiated at " + instantiatedPrefab.transform.position); | |
shouldFocusTextField = true; | |
// Close the window if it's not docked | |
if (!IsDocked()) Close(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment