Skip to content

Instantly share code, notes, and snippets.

@kolja
Created June 5, 2024 13:58
Show Gist options
  • Save kolja/57f7a8cc5009716747ee80208c3c177f to your computer and use it in GitHub Desktop.
Save kolja/57f7a8cc5009716747ee80208c3c177f to your computer and use it in GitHub Desktop.
Unity: Create Prefab from Selection
// 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