Last active
February 14, 2025 17:52
-
-
Save MattRix/f174836be788124b80d99c447df46ec5 to your computer and use it in GitHub Desktop.
Revisions
-
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,7 +1,8 @@ Right-click on a prefab and choose "Generate Prefab Class" to get a base class with references to the prefab's components and children (it uses the first component of each child). Use these prefab classes as base classes for your own code. For example, if you have a prefab called "Ball", this tool will generate "BallPrefab.cs" next to it. You can then make your own script "Ball.cs" which extends from BallPrefab, giving you properly typed references to all of its components and children. You can select multiple prefabs (or even an entire folder) to generate a bunch of classes at once. If the object has multiple children with the exact same name, it'll generate a list reference to them instead. -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 3 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -2,4 +2,6 @@ Right-click on a prefab and choose "Generate Prefab Class" to get a base class w You can select multiple prefabs (or even an entire folder) to generate a bunch of classes at once. If the object has multiple children with the exact same name, it'll generate a list reference to them instead. I don't recommend modifying these prefab classes directly, instead use them as base classes for your own code. For example, if you have a prefab called "Ball", this tool will generate "BallPrefab.cs" next to it. You can then make your own script called "Ball.cs" which extends from BallPrefab, giving you properly typed references to all of its components and children. -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ Right-click on a prefab and choose "Generate Prefab Class" to get a base class with references to the prefab's components and children (it uses the first component of each child). You can select multiple prefabs (or even an entire folder) to generate a bunch of classes at once. -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 5 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,5 @@ Right click on a prefab and choose "Generate Prefab Class" to get a base class with references to the prefab's components and children (it uses the first component of each child). You can select multiple prefabs (or even an entire folder) to generate a bunch of classes at once. If the object has multiple children with the exact same name, it'll generate a list reference to them instead. -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Text; public class PrefabClassGenerator { -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -43,6 +43,7 @@ private static List<GameObject> FindAllPrefabsInSelection() { var folderPath = AssetDatabase.GetAssetPath(Selection.activeObject); //if a folder is selected, find all prefabs in the folder recursively if(AssetDatabase.IsValidFolder(folderPath)) { string[] assetGUIDs = AssetDatabase.FindAssets("", new[] { folderPath }); @@ -58,7 +59,7 @@ private static List<GameObject> FindAllPrefabsInSelection() } } } else //find all the prefabs in our current selected objects { foreach(var go in Selection.gameObjects) { -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -233,13 +233,13 @@ private static string GenerateCodeForPrefab(GameObject prefab, string className) sb.AppendLine(line); sb.AppendLine(); sb.AppendLine(" virtual protected void Setup()"); sb.AppendLine(" {"); foreach (string line in setupLines) sb.AppendLine(line); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" virtual protected void Awake()"); sb.AppendLine(" {"); sb.AppendLine(" Setup();"); sb.AppendLine(" }"); -
MattRix revised this gist
Feb 14, 2025 . 1 changed file with 132 additions and 58 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,3 @@ #if UNITY_EDITOR using UnityEngine; @@ -7,41 +6,78 @@ using System.Collections.Generic; using System.IO; using System.Text; using JetBrains.Annotations; public class PrefabClassGenerator { // If OUTPUT_FOLDER is empty, generated scripts will be placed next to their prefab. //static string OUTPUT_FOLDER = "Assets/PrefabClasses/"; static string OUTPUT_FOLDER = ""; [MenuItem("Assets/Generate Prefab Class", validate = true)] private static bool ValidateGeneratePrefabClass() { var prefabs = FindAllPrefabsInSelection(); return prefabs.Count > 0; } [MenuItem("Assets/Generate Prefab Class", false, 1)] private static void GeneratePrefabClass() { var prefabs = FindAllPrefabsInSelection(); foreach(var prefab in prefabs) { CreatePrefabClass(prefab); } AssetDatabase.Refresh(); } private static List<GameObject> FindAllPrefabsInSelection() { List<GameObject> prefabs = new List<GameObject>(); if(Selection.activeObject != null) { var folderPath = AssetDatabase.GetAssetPath(Selection.activeObject); if(AssetDatabase.IsValidFolder(folderPath)) { string[] assetGUIDs = AssetDatabase.FindAssets("", new[] { folderPath }); foreach (string guid in assetGUIDs) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); if(go != null) { prefabs.Add(go); } } } else { foreach(var go in Selection.gameObjects) { if(PrefabUtility.IsPartOfPrefabAsset(go)) { prefabs.Add(go); } } } } return prefabs; } [MenuItem("Tools/Generate Classes For All Prefabs")] public static void GenerateClassesForPrefabs() { if (!string.IsNullOrEmpty(OUTPUT_FOLDER) && !Directory.Exists(OUTPUT_FOLDER)) { Directory.CreateDirectory(OUTPUT_FOLDER); @@ -60,18 +96,15 @@ public static void GenerateClassesForPrefabs() Debug.LogWarning($"Could not load prefab at path: {prefabPath}"); continue; } CreatePrefabClass(prefab); } AssetDatabase.Refresh(); } static void CreatePrefabClass(GameObject prefab) { string className = SanitizeClassName(prefab.name) + "Prefab"; string folderPath = OUTPUT_FOLDER; if (string.IsNullOrEmpty(OUTPUT_FOLDER)) { @@ -89,24 +122,23 @@ private static string GenerateCodeForPrefab(GameObject prefab, string className) { StringBuilder sb = new StringBuilder(); sb.AppendLine("using UnityEngine;"); sb.AppendLine("using System.Collections.Generic;"); sb.AppendLine(); sb.AppendLine($"public class {className} : MonoBehaviour"); sb.AppendLine("{"); HashSet<string> usedNames = new HashSet<string>(); // Built-in component types that conflict with MonoBehaviour properties. HashSet<string> builtIn = new HashSet<string>() { "Rigidbody", "Rigidbody2D", "Camera", "Animation", "ConstantForce", "Collider", "Collider2D", "HingeJoint", "NetworkView", "GUIText", "GUITexture", "Audio" }; List<string> propertyDeclarations = new List<string>(); List<string> setupLines = new List<string>(); // Process root components (excluding Transform) Component[] comps = prefab.GetComponents<Component>(); foreach (var comp in comps) @@ -115,9 +147,7 @@ private static string GenerateCodeForPrefab(GameObject prefab, string className) continue; Type compType = comp.GetType(); if (compType.Name == prefab.name || compType.Name == prefab.name + "Prefab") continue; string typeString = GetTypeString(compType); @@ -138,27 +168,65 @@ private static string GenerateCodeForPrefab(GameObject prefab, string className) setupLines.Add($" {propName} = GetComponent<{typeString}>();"); } // Process immediate child transforms and group them by name. Dictionary<string, List<ChildInfo>> childGroups = new Dictionary<string, List<ChildInfo>>(); CollectDirectChildGroups(prefab.transform, childGroups); // Build group info: for a single child, generate a single property; // for multiple children with the same name, generate a List<> property. var childGroupList = new List<(string groupName, string propertyName, string typeString, bool isList)>(); foreach (var kvp in childGroups) { string groupName = kvp.Key; List<ChildInfo> children = kvp.Value; bool isList = children.Count > 1; string typeString = children[0].TypeName; string propName = SanitizePropertyName(groupName, usedNames); if (isList) propName += "List"; usedNames.Add(propName); childGroupList.Add((groupName, propName, typeString, isList)); if (isList) propertyDeclarations.Add($" public List<{typeString}> {propName} {{ get; protected set; }} = new List<{typeString}>();"); else propertyDeclarations.Add($" public {typeString} {propName} {{ get; protected set; }}"); } // Generate Setup() method with a switch statement for child transforms. if(childGroupList.Count > 0) { if(setupLines.Count > 0) setupLines.Add(""); //add spacing if there are other component lines above this setupLines.Add(" for (int i = 0; i < transform.childCount; i++)"); setupLines.Add(" {"); setupLines.Add(" var child = transform.GetChild(i);"); setupLines.Add(" switch(child.name)"); setupLines.Add(" {"); foreach (var group in childGroupList) { setupLines.Add($" case \"{group.groupName}\":"); if (group.isList) { if (group.typeString == GetTypeString(typeof(Transform))) setupLines.Add($" {group.propertyName}.Add(child);"); else setupLines.Add($" {group.propertyName}.Add(child.GetComponent<{group.typeString}>());"); } else { if (group.typeString == GetTypeString(typeof(Transform))) setupLines.Add($" {group.propertyName} = child;"); else setupLines.Add($" {group.propertyName} = child.GetComponent<{group.typeString}>();"); } setupLines.Add(" break;"); } setupLines.Add(" }"); setupLines.Add(" }"); } foreach (string line in propertyDeclarations) @@ -180,26 +248,15 @@ private static string GenerateCodeForPrefab(GameObject prefab, string className) return sb.ToString(); } // Collects only the immediate children and groups them by name. private static void CollectDirectChildGroups(Transform parent, Dictionary<string, List<ChildInfo>> childGroups) { foreach (Transform child in parent) { string baseName = child.name.Replace(" ", ""); if (string.IsNullOrEmpty(baseName)) continue; string typeString = GetTypeString(typeof(Transform)); Component[] comps = child.GetComponents<Component>(); foreach (var comp in comps) @@ -210,11 +267,30 @@ private static void CollectChildTransforms(Transform parent, Dictionary<string, break; } ChildInfo info = new ChildInfo(child.name, typeString); if (!childGroups.ContainsKey(child.name)) childGroups[child.name] = new List<ChildInfo>(); childGroups[child.name].Add(info); } } // Helper to generate a sanitized property name and ensure uniqueness. private static string SanitizePropertyName(string name, HashSet<string> usedNames) { string baseName = name.Replace(" ", ""); if (string.IsNullOrEmpty(baseName)) baseName = "child"; string propName = char.ToLowerInvariant(baseName[0]) + baseName.Substring(1); string original = propName; int duplicateCount = 1; while (usedNames.Contains(propName)) { propName = original + duplicateCount; duplicateCount++; } return propName; } private class ChildInfo { public string ChildName; @@ -226,28 +302,26 @@ public ChildInfo(string childName, string typeName) } } // Returns a proper type string; if the type belongs to UnityEngine, return only the short name. private static string GetTypeString(Type type) { if (type.Namespace == "UnityEngine") return type.Name; return type.FullName; } // Sanitizes the class name by removing invalid characters and ensuring it starts with a letter. private static string SanitizeClassName(string name) { StringBuilder sb = new StringBuilder(); foreach (char c in name) if (char.IsLetterOrDigit(c) || c == '_') sb.Append(c); string valid = sb.ToString(); if (string.IsNullOrEmpty(valid) || !char.IsLetter(valid[0])) valid = "_" + valid; return valid; } } #endif -
MattRix created this gist
Feb 14, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,253 @@ #if UNITY_EDITOR using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; using System.IO; using System.Text; public class PrefabClassGenerator { // If OUTPUT_FOLDER is empty, generated scripts will be placed next to their prefab. //static string OUTPUT_FOLDER = "Assets/PrefabClasses/"; static string OUTPUT_FOLDER = ""; // This validate function ensures the menu item only shows when a prefab asset is selected. [MenuItem("Assets/Generate Prefab Class", validate = true)] private static bool ValidateDoSomething() { if (Selection.activeObject is GameObject go) { return PrefabUtility.IsPartOfPrefabAsset(go); } return false; } [MenuItem("Assets/Generate Prefab Class", false, 1)] private static void DoSomething() { GameObject prefab = Selection.activeObject as GameObject; if(prefab != null) { CreatePrefabClass(prefab); AssetDatabase.Refresh(); } } [MenuItem("Tools/Generate Classes For All Prefabs")] public static void GenerateClassesForPrefabs() { // Only attempt to create OUTPUT_FOLDER if it's specified. if (!string.IsNullOrEmpty(OUTPUT_FOLDER) && !Directory.Exists(OUTPUT_FOLDER)) { Directory.CreateDirectory(OUTPUT_FOLDER); AssetDatabase.Refresh(); } string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets" }); Debug.Log($"Found {guids.Length} prefabs."); foreach (string guid in guids) { string prefabPath = AssetDatabase.GUIDToAssetPath(guid); GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath); if (prefab == null) { Debug.LogWarning($"Could not load prefab at path: {prefabPath}"); continue; } CreatePrefabClass(prefab); } AssetDatabase.Refresh(); } static void CreatePrefabClass(GameObject prefab) { string className = SanitizeClassName(prefab.name) + "Prefab"; // Determine folder path: use OUTPUT_FOLDER if set; otherwise, use prefab's directory. string folderPath = OUTPUT_FOLDER; if (string.IsNullOrEmpty(OUTPUT_FOLDER)) { string prefabAssetPath = AssetDatabase.GetAssetPath(prefab); folderPath = Path.GetDirectoryName(prefabAssetPath); } string filePath = Path.Combine(folderPath, className + ".cs"); string code = GenerateCodeForPrefab(prefab, className); File.WriteAllText(filePath, code); Debug.Log($"Generated {filePath}"); } private static string GenerateCodeForPrefab(GameObject prefab, string className) { StringBuilder sb = new StringBuilder(); sb.AppendLine("using UnityEngine;"); sb.AppendLine(); sb.AppendLine($"public class {className} : MonoBehaviour"); sb.AppendLine("{"); HashSet<string> usedNames = new HashSet<string>(); // Built-in component types that conflict with MonoBehaviour properties. HashSet<string> builtIn = new HashSet<string>() { "Rigidbody", "Rigidbody2D", "Camera", "Animation", "ConstantForce", "Collider", "Collider2D", "HingeJoint", "NetworkView", "GUIText", "GUITexture", "Audio" }; List<string> propertyDeclarations = new List<string>(); List<string> setupLines = new List<string>(); // Store prefab name for filtering components string prefabBaseName = prefab.name; // Process root components (excluding Transform) Component[] comps = prefab.GetComponents<Component>(); foreach (var comp in comps) { if (comp == null || comp is Transform) continue; Type compType = comp.GetType(); // Skip if component's type name matches the prefab name or prefab name + "Prefab" if (compType.Name == prefabBaseName || compType.Name == prefabBaseName + "Prefab") continue; string typeString = GetTypeString(compType); string shortTypeName = compType.Name; string propName = char.ToLowerInvariant(shortTypeName[0]) + shortTypeName.Substring(1); string baseName = propName; int duplicateCount = 1; while (usedNames.Contains(propName)) { propName = baseName + duplicateCount; duplicateCount++; } usedNames.Add(propName); string modifier = builtIn.Contains(shortTypeName) ? "new " : ""; propertyDeclarations.Add($" {modifier}public {typeString} {propName} {{ get; protected set; }}"); setupLines.Add($" {propName} = GetComponent<{typeString}>();"); } // Process child transforms recursively. Dictionary<string, ChildInfo> childProperties = new Dictionary<string, ChildInfo>(); CollectChildTransforms(prefab.transform, childProperties, usedNames); foreach (var kvp in childProperties) { string propName = kvp.Key; ChildInfo info = kvp.Value; string childName = info.ChildName; string childType = info.TypeName; if (childType == GetTypeString(typeof(Transform))) { propertyDeclarations.Add($" public {childType} {propName} {{ get; protected set; }}"); setupLines.Add($" {propName} = transform.Find(\"{childName}\");"); } else { propertyDeclarations.Add($" public {childType} {propName} {{ get; protected set; }}"); setupLines.Add($" {propName} = transform.Find(\"{childName}\")?.GetComponent<{childType}>();"); } } foreach (string line in propertyDeclarations) sb.AppendLine(line); sb.AppendLine(); sb.AppendLine(" protected void Setup()"); sb.AppendLine(" {"); foreach (string line in setupLines) sb.AppendLine(line); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" protected void Awake()"); sb.AppendLine(" {"); sb.AppendLine(" Setup();"); sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } // Recursively collects child transforms and determines their component type. private static void CollectChildTransforms(Transform parent, Dictionary<string, ChildInfo> childProps, HashSet<string> usedNames) { foreach (Transform child in parent) { string baseName = child.name.Replace(" ", ""); if (string.IsNullOrEmpty(baseName)) continue; string propName = char.ToLowerInvariant(baseName[0]) + baseName.Substring(1); string originalName = propName; int duplicateCount = 1; while (usedNames.Contains(propName)) { propName = originalName + duplicateCount; duplicateCount++; } usedNames.Add(propName); // Default type is UnityEngine.Transform string typeString = GetTypeString(typeof(Transform)); Component[] comps = child.GetComponents<Component>(); foreach (var comp in comps) { if (comp == null || comp is Transform) continue; typeString = GetTypeString(comp.GetType()); break; } childProps[propName] = new ChildInfo(child.name, typeString); CollectChildTransforms(child, childProps, usedNames); } } private class ChildInfo { public string ChildName; public string TypeName; public ChildInfo(string childName, string typeName) { ChildName = childName; TypeName = typeName; } } // Returns the proper type string. If the type is in UnityEngine, returns only the short name. private static string GetTypeString(Type type) { if (type.Namespace == "UnityEngine") return type.Name; return type.FullName; } // Sanitizes the class name to remove invalid characters and ensure it starts with a letter. private static string SanitizeClassName(string name) { StringBuilder sb = new StringBuilder(); foreach (char c in name) { if (char.IsLetterOrDigit(c) || c == '_') sb.Append(c); } string valid = sb.ToString(); if (string.IsNullOrEmpty(valid) || !char.IsLetter(valid[0])) valid = "_" + valid; return valid; } } #endif