Browse Source

Sujith :) ->
1. Implemented a lot of schema's some of them work

Sujith:) 1 month ago
parent
commit
9cb6b69fa6

+ 4 - 1
Assets/AssetBank/AssetDatabase.asmdef

@@ -1,7 +1,10 @@
 {
     "name": "AssetBank",
     "rootNamespace": "",
-    "references": ["GUID:e7c9410007423435ea89724962e62f27","GUID:9d1c8fe9b713a4564bd5e25792a66436"],
+    "references": [
+        "GUID:e7c9410007423435ea89724962e62f27",
+        "GUID:9d1c8fe9b713a4564bd5e25792a66436"
+    ],
     "includePlatforms": [],
     "excludePlatforms": [],
     "allowUnsafeCode": false,

+ 1 - 2
Assets/AssetBank/Assets/ProjectExporterSettings.asset

@@ -12,10 +12,9 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: e9378e9b5e63d41a4873d9f290c74043, type: 3}
   m_Name: ProjectExporterSettings
   m_EditorClassIdentifier: 
-  m_ConvertToNewSchema: 1
+  m_ConvertToNewSchema: 0
   m_OptimiseExport: 1
   m_CategoriesToOptimise:
-  - Scenes
   - Prefabs
   m_SafeComponents: []
   m_HardcodedComponents:

+ 37 - 22
Assets/AssetBank/Editor/DockableWindow/ProjectExporterController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using AssetBank.Editor.SceneSchema;
 using AssetBank.Editor.SchemaConverter;
 using AssetBank.Editor.Tools;
 using AssetBank.Settings;
@@ -176,28 +177,11 @@ namespace AssetBank.DockableWindow
                         Directory.CreateDirectory(fileOutputDir);
                     }
 
-                    var arguments =
-                        $"\"{conversionScript}\" \"{absoluteAssetPath}\" \"{outputJsonPath}\"";
+                    var exitCode = asset.Category == "Scenes" ? 
+                        ProcessUsingSimpleSceneSerializer(relativeAssetPath, outputJsonPath, out var output, out var error) : 
+                        ProcessUsingPythonTool(conversionScript, absoluteAssetPath, outputJsonPath, pythonExecutable, out output, out error);
 
-                    var process = new Process
-                    {
-                        StartInfo = new ProcessStartInfo
-                        {
-                            FileName = pythonExecutable,
-                            Arguments = arguments,
-                            RedirectStandardOutput = true,
-                            RedirectStandardError = true,
-                            UseShellExecute = false,
-                            CreateNoWindow = true
-                        }
-                    };
-
-                    process.Start();
-                    string output = process.StandardOutput.ReadToEnd();
-                    string error = process.StandardError.ReadToEnd();
-                    process.WaitForExit();
-
-                    if (process.ExitCode != 0)
+                    if (exitCode != 0)
                     {
                         UnityEngine.Debug.LogError($"Error exporting {relativeAssetPath}: {error}");
                     }
@@ -210,7 +194,7 @@ namespace AssetBank.DockableWindow
                             File.WriteAllText(outputJsonPath, processedJson);
                         }
 
-                        if (_settings.ConvertToNewSchema && _settings.CategoriesToOptimise.Contains(asset.Category))
+                        if (_settings.ConvertToNewSchema && asset.Category == "Scenes")
                         {
                             var jsonToConvert = File.ReadAllText(outputJsonPath);
                             var convertedJson = JsonSchemaConverter.Convert(jsonToConvert);
@@ -227,6 +211,37 @@ namespace AssetBank.DockableWindow
             }
         }
 
+        private static int ProcessUsingPythonTool(string conversionScript, string absoluteAssetPath, string outputJsonPath, string pythonExecutable, out string output, out string error)
+        {
+            var arguments =
+                $"\"{conversionScript}\" \"{absoluteAssetPath}\" \"{outputJsonPath}\"";
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = pythonExecutable,
+                    Arguments = arguments,
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true,
+                    UseShellExecute = false,
+                    CreateNoWindow = true
+                }
+            };
+
+            process.Start();
+            output = process.StandardOutput.ReadToEnd();
+            error = process.StandardError.ReadToEnd();
+            process.WaitForExit();
+            return process.ExitCode;
+        }
+
+        private static int ProcessUsingSimpleSceneSerializer(string relativeAssetPath, string outputJsonPath, out string output, out string error)
+        {
+            SceneParser.Process(relativeAssetPath, outputJsonPath, out output, out error);
+            return error == null ? 0 : 1;
+        }
+
         private List<string> FilterAssets(List<string> assetPaths)
         {
             if (_settings == null)

+ 8 - 0
Assets/AssetBank/Editor/SceneSchema.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a7f8d2a51497541729967cb1120811d4
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 3 - 0
Assets/AssetBank/Editor/SceneSchema/Data.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 38491ddf115244628f4e7226f443b9bd
+timeCreated: 1753693431

+ 100 - 0
Assets/AssetBank/Editor/SceneSchema/Data/SceneData.cs

@@ -0,0 +1,100 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace AssetBank.Editor.Data
+{
+    [Serializable]
+    public class SceneData
+    {
+        // --- LEGEND ---
+        // p: PrefabsInScene
+        // h: Hierarchy
+
+        [JsonProperty("p", NullValueHandling = NullValueHandling.Ignore)]
+        public List<string> PrefabsInScene { get; set; } = new();
+
+        [JsonProperty("h", NullValueHandling = NullValueHandling.Ignore)]
+        public List<GameObjectData> Hierarchy { get; set; } = new();
+        
+        public bool ShouldSerializePrefabsInScene() => PrefabsInScene is { Count: > 0 };
+        public bool ShouldSerializeHierarchy() => Hierarchy is { Count: > 0 };
+    }
+
+    [Serializable]
+    public class GameObjectData
+    {
+        // --- LEGEND ---
+        // e: Enabled
+        // n: Name
+        // t: Tag
+        // l: Layer
+        // a: AnchorId
+        // pi: PrefabIndex
+        // c: Components
+        // ac: AddedComponents
+        // rc: RemovedComponentAnchorIds
+        // ch: Children
+
+        [JsonProperty("e", DefaultValueHandling = DefaultValueHandling.Ignore)]
+        [DefaultValue(true)]
+        public bool Enabled { get; set; } = true;
+        
+        [JsonProperty("n")]
+        public string Name { get; set;}
+        
+        [JsonProperty("t", DefaultValueHandling = DefaultValueHandling.Ignore)]
+        [DefaultValue("Untagged")]
+        public string Tag { get; set; } = "Untagged";
+        
+        [JsonProperty("l", DefaultValueHandling = DefaultValueHandling.Ignore)]
+        [DefaultValue(0)]
+        public int Layer { get; set; }
+        
+        [JsonProperty("a")]
+        public string AnchorId { get; set;}
+        
+        [JsonProperty("pi", NullValueHandling = NullValueHandling.Ignore)]
+        public int? PrefabIndex { get; set; }
+
+        [JsonProperty("c", NullValueHandling = NullValueHandling.Ignore)]
+        public List<ComponentData> Components { get; set; } = new();
+
+        [JsonProperty("ac", NullValueHandling = NullValueHandling.Ignore)]
+        public List<ComponentData> AddedComponents { get; set; } = new();
+
+        [JsonProperty("rc", NullValueHandling = NullValueHandling.Ignore)]
+        public List<string> RemovedComponentAnchorIds { get; set; } = new();
+
+        [JsonProperty("ch", NullValueHandling = NullValueHandling.Ignore)]
+        public List<GameObjectData> Children { get; set; } = new();
+        
+        public bool ShouldSerializeComponents() => Components is { Count: > 0 };
+        public bool ShouldSerializeAddedComponents() => AddedComponents is { Count: > 0 };
+        public bool ShouldSerializeRemovedComponentAnchorIds() => RemovedComponentAnchorIds is { Count: > 0 };
+        public bool ShouldSerializeChildren() => Children is { Count: > 0 };
+    }
+
+    [Serializable]
+    public class ComponentData
+    {
+        // --- LEGEND ---
+        // t: Name (Type)
+        // a: AnchorId
+        // f: Fields (via JsonExtensionData)
+
+        [JsonProperty("t")]
+        public string Name { get; set; }
+        
+        [JsonProperty("a")]
+        public string AnchorId { get; set; }
+        
+        [JsonProperty("f")]
+        [JsonExtensionData]
+        public IDictionary<string, JToken> Fields { get; set; } = new Dictionary<string, JToken>();
+        
+        public bool ShouldSerializeFields() => Fields is { Count: > 0 };
+    }
+}

+ 3 - 0
Assets/AssetBank/Editor/SceneSchema/Data/SceneData.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ffe0a5188eee44129fd0bc36ecf806bb
+timeCreated: 1753693442

+ 8 - 0
Assets/AssetBank/Editor/SceneSchema/Internal.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 708a8735918494045b1fce58e9ac1aed
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 22 - 0
Assets/AssetBank/Editor/SceneSchema/Internal/EditorUtilities.cs

@@ -0,0 +1,22 @@
+using UnityEditor;
+using System.Reflection;
+
+namespace AssetBank.Editor.SceneSchema.Internal
+{
+    public static class EditorUtilities
+    {
+        public static int GetLocalFileID(UnityEngine.Object obj)
+        {
+            if (obj == null) return 0;
+
+            var inspectorModeInfo =
+                typeof(SerializedObject).GetProperty("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
+
+            var serializedObject = new SerializedObject(obj);
+            if (inspectorModeInfo != null) inspectorModeInfo.SetValue(serializedObject, InspectorMode.Debug, null);
+
+            var localIdProp = serializedObject.FindProperty("m_LocalIdentfierInFile");
+            return localIdProp.intValue;
+        }
+    }
+}

+ 11 - 0
Assets/AssetBank/Editor/SceneSchema/Internal/EditorUtilities.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 71d185c1a446c418faf110973041522c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 117 - 0
Assets/AssetBank/Editor/SceneSchema/Internal/MonoBehaviourSerializer.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using Newtonsoft.Json.Linq;
+
+namespace AssetBank.Editor.SceneSchema.Internal
+{
+    public static class MonoBehaviourSerializer
+    {
+        public static IDictionary<string, JToken> Serialize(MonoBehaviour obj)
+        {
+            var fields = new Dictionary<string, JToken>();
+            if (obj == null) return fields;
+
+            var serializedObject = new SerializedObject(obj);
+            var property = serializedObject.GetIterator();
+            property.NextVisible(true); 
+
+            while (property.NextVisible(false))
+            {
+                fields[property.name] = ConvertPropertyToJToken(property);
+            }
+
+            return fields;
+        }
+
+        private static JToken ConvertPropertyToJToken(SerializedProperty prop)
+        {
+            if (prop.depth > 10) return JValue.CreateNull();
+
+            switch (prop.propertyType)
+            {
+                // Basic Types
+                case SerializedPropertyType.Integer: return new JValue(prop.longValue);
+                case SerializedPropertyType.Boolean: return new JValue(prop.boolValue);
+                case SerializedPropertyType.Float: return new JValue(prop.doubleValue);
+                case SerializedPropertyType.String: return new JValue(prop.stringValue);
+                case SerializedPropertyType.Enum: return new JValue(prop.enumDisplayNames[prop.enumValueIndex]);
+
+                // Unity-specific Structs
+                case SerializedPropertyType.Color:
+                    var c = prop.colorValue;
+                    return new JObject { { "r", c.r }, { "g", c.g }, { "b", c.b }, { "a", c.a } };
+                case SerializedPropertyType.Vector2:
+                    var v2 = prop.vector2Value;
+                    return new JObject { { "x", v2.x }, { "y", v2.y } };
+                case SerializedPropertyType.Vector3:
+                    var v3 = prop.vector3Value;
+                    return new JObject { { "x", v3.x }, { "y", v3.y }, { "z", v3.z } };
+                case SerializedPropertyType.Vector4:
+                    var v4 = prop.vector4Value;
+                    return new JObject { { "x", v4.x }, { "y", v4.y }, { "z", v4.z }, { "w", v4.w } };
+                case SerializedPropertyType.Quaternion:
+                    var q = prop.quaternionValue;
+                    return new JObject { { "x", q.x }, { "y", q.y }, { "z", q.z }, { "w", q.w } };
+                case SerializedPropertyType.Rect:
+                    var r = prop.rectValue;
+                    return new JObject { { "x", r.x }, { "y", r.y }, { "width", r.width }, { "height", r.height } };
+                case SerializedPropertyType.Bounds:
+                    var b = prop.boundsValue;
+                    return new JObject { { "center", new JObject { { "x", b.center.x }, { "y", b.center.y }, { "z", b.center.z } } }, { "size", new JObject { { "x", b.size.x }, { "y", b.size.y }, { "z", b.size.z } } } };
+
+                // Reference Types
+                case SerializedPropertyType.ObjectReference:
+                    return SerializeObjectReference(prop.objectReferenceValue);
+
+                // Arrays and Lists
+                case SerializedPropertyType.Generic when prop.isArray:
+                    var array = new JArray();
+                    for (var i = 0; i < prop.arraySize; i++)
+                    {
+                        array.Add(ConvertPropertyToJToken(prop.GetArrayElementAtIndex(i)));
+                    }
+                    return array;
+
+                // Nested Serializable Classes
+                case SerializedPropertyType.Generic:
+                    var nestedObject = new JObject();
+                    var copy = prop.Copy();
+                    var end = copy.GetEndProperty();
+                    copy.NextVisible(true);
+                    while (!SerializedProperty.EqualContents(copy, end))
+                    {
+                        nestedObject[copy.name] = ConvertPropertyToJToken(copy);
+                        copy.NextVisible(false);
+                    }
+                    return nestedObject;
+
+                default:
+                    return new JValue($"Unsupported Type: {prop.propertyType}");
+            }
+        }
+
+        private static JToken SerializeObjectReference(UnityEngine.Object obj)
+        {
+            if (obj == null) return JValue.CreateNull();
+
+            var reference = new JObject
+            {
+                ["type"] = obj.GetType().FullName
+            };
+
+            if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _))
+            {
+                reference["guid"] = guid;
+                reference["assetPath"] = AssetDatabase.GUIDToAssetPath(guid);
+            }
+            else
+            {
+                reference["scene_object_id"] = EditorUtilities.GetLocalFileID(obj);
+            }
+
+            return reference;
+        }
+    }
+}

+ 11 - 0
Assets/AssetBank/Editor/SceneSchema/Internal/MonoBehaviourSerializer.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 53374d6d20e7c4b3caff8c315de8e0ce
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 170 - 0
Assets/AssetBank/Editor/SceneSchema/SceneParser.cs

@@ -0,0 +1,170 @@
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using Newtonsoft.Json;
+using AssetBank.Editor.Data;
+using System.Collections.Generic;
+using UnityEditor.SceneManagement;
+using UnityEngine.SceneManagement;
+using AssetBank.Editor.SceneSchema.Internal;
+
+namespace AssetBank.Editor.SceneSchema
+{
+    public static class SceneParser
+    {   
+        public static void Process(string scenePath, string savePath, out string output, out string error)
+        {
+            output = null;
+            error = null;
+            
+            if (string.IsNullOrEmpty(scenePath) || !File.Exists(scenePath))
+            {
+                error = $"Scene path is invalid or does not exist. Scene path: {scenePath}";
+                return;
+            }
+
+            var originalScene = SceneManager.GetActiveScene().path;
+            
+            try
+            {
+                var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
+                var sceneData = new SceneData();
+                var prefabList = new List<string>();
+
+                var rootGameObjects = scene.GetRootGameObjects();
+                foreach (var rootGo in rootGameObjects)
+                {
+                    var goData = ParseGameObjectRecursive(rootGo, prefabList);
+                    if (goData != null)
+                    {
+                        sceneData.Hierarchy.Add(goData);
+                    }
+                }
+                
+                sceneData.PrefabsInScene = prefabList;
+                var json = JsonConvert.SerializeObject(sceneData, new JsonSerializerSettings
+                {
+                    Formatting = Formatting.None,
+                    NullValueHandling = NullValueHandling.Ignore,
+                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+                });
+                
+                File.WriteAllText(savePath, json);
+                output = $"Successfully processed scene '{scenePath}' and saved JSON to '{savePath}'";
+            }
+            catch (System.Exception e)
+            {
+                error = $"Failed to process scene '{scenePath}'. Error: \n{e}";
+            }
+            finally
+            {
+                if (!string.IsNullOrEmpty(originalScene))
+                {
+                    EditorSceneManager.OpenScene(originalScene);
+                }
+            }
+        }
+
+        private static GameObjectData ParseGameObjectRecursive(GameObject go, List<string> prefabList)
+        {
+            if (go == null) return null;
+
+            var goData = new GameObjectData
+            {
+                Enabled = go.activeSelf,
+                Name = go.name,
+                Tag = go.tag,
+                Layer = go.layer,
+                AnchorId = EditorUtilities.GetLocalFileID(go).ToString()
+            };
+
+            var isPrefab = PrefabUtility.IsPartOfPrefabInstance(go);
+            var isNewlyAdded = isPrefab && PrefabUtility.GetCorrespondingObjectFromSource(go) == null;
+
+            // Case A: Regular GameObject or a new object added to a prefab instance.
+            if (!isPrefab || isNewlyAdded)
+            {
+                foreach (var component in go.GetComponents<Component>())
+                {
+                    if (component == null) continue;
+                    goData.Components.Add(ParseComponent(component));
+                }
+            }
+            // Case B & C: Object is part of a prefab (root or child).
+            else
+            {
+                // Handle prefab root
+                if (PrefabUtility.GetNearestPrefabInstanceRoot(go) == go)
+                {
+                    var prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(go);
+                    if (prefabAsset != null)
+                    {
+                        var prefabGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(prefabAsset));
+                        if (!prefabList.Contains(prefabGuid))
+                        {
+                            prefabList.Add(prefabGuid);
+                        }
+                        goData.PrefabIndex = prefabList.IndexOf(prefabGuid);
+                    }
+                }
+                
+                // Handle added/removed components for this specific GameObject
+                goData.AddedComponents = PrefabUtility.GetAddedComponents(go)
+                    .Select(c => ParseComponent(c.instanceComponent))
+                    .ToList();
+
+                goData.RemovedComponentAnchorIds = PrefabUtility.GetRemovedComponents(go)
+                    .Select(c => EditorUtilities.GetLocalFileID(c.assetComponent).ToString())
+                    .ToList();
+            }
+
+            // Recursively parse children and prune empty nodes
+            foreach (Transform child in go.transform)
+            {
+                var childData = ParseGameObjectRecursive(child.gameObject, prefabList);
+                if (childData != null)
+                {
+                    goData.Children.Add(childData);
+                }
+            }
+
+            // Pruning logic: If a prefab node has no overrides and no new children, discard it.
+            var isOriginalPrefabPart = isPrefab && !isNewlyAdded;
+            if (isOriginalPrefabPart)
+            {
+                var hasOverrides = goData.AddedComponents.Any() || goData.RemovedComponentAnchorIds.Any();
+                var hasChildrenWithOverrides = goData.Children.Any();
+                var isRoot = goData.PrefabIndex.HasValue;
+
+                // A non-root part of a prefab is empty if it has no overrides of its own and no children with overrides.
+                if (!isRoot && !hasOverrides && !hasChildrenWithOverrides)
+                {
+                    return null;
+                }
+            }
+
+            return goData;
+        }
+
+        private static ComponentData ParseComponent(Component component)
+        {
+            var componentData = new ComponentData
+            {
+                Name = component.GetType().Name,
+                AnchorId = EditorUtilities.GetLocalFileID(component).ToString()
+            };
+
+            if (component is MonoBehaviour monoBehaviour)
+            {
+                var fullTypeName = monoBehaviour.GetType().FullName;
+                if (fullTypeName != null && !fullTypeName.StartsWith("UnityEngine."))
+                {
+                    componentData.Fields = MonoBehaviourSerializer.Serialize(monoBehaviour);
+                }
+            }
+
+            return componentData;
+        }
+    }
+}

+ 11 - 0
Assets/AssetBank/Editor/SceneSchema/SceneParser.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 85a8585aa6e7d43e69f89565122b11be
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 2 - 2
Assets/AssetBank/Editor/SchemaConverter/Data/Output/SceneGraph.cs

@@ -12,8 +12,8 @@ namespace AssetBank.Editor.SchemaConverter.Data.Output
         [JsonProperty(Order = 1)]
         public Metadata metadata;
 
-        [JsonProperty(Order = 2)]
-        public JObject scene_data;
+        // [JsonProperty(Order = 2)]
+        // public JObject scene_data;
 
         [JsonProperty(Order = 3)]
         public List<string> prefabs;

+ 1 - 1
Assets/AssetBank/Editor/SchemaConverter/JsonSchemaConverter.cs

@@ -58,7 +58,7 @@ namespace AssetBank.Editor.SchemaConverter
             }
 
             // 3. Process Scene Data with the remaining, unconsumed nodes
-            sceneGraph.scene_data = SceneDataProcessor.Process(allNodes, consumedIds);
+            // sceneGraph.scene_data = SceneDataProcessor.Process(allNodes, consumedIds);
             
             // 4. Assemble Metadata
             sceneGraph.metadata = new Metadata

+ 137 - 32
Assets/AssetBank/Editor/SchemaConverter/Processors/HierarchyProcessor.cs

@@ -19,7 +19,7 @@ namespace AssetBank.Editor.SchemaConverter.Processors
 
         private static readonly HashSet<string> s_RedundantTransformKeys = new()
         {
-            "m_Father", "m_Children", "m_PrefabInstance"
+            "m_Father", "m_Children", "m_PrefabInstance", "m_CorrespondingSourceObject", "m_PrefabAsset"
         };
 
         public (List<HierarchicalNode> rootNodes, List<string> warnings, HashSet<string> consumedAnchorIds) Process(
@@ -48,15 +48,18 @@ namespace AssetBank.Editor.SchemaConverter.Processors
 
                     if (guidIndex != -1)
                     {
-                        var prefabNode = new HierarchicalNode
+                        if (!_nodeMap.TryGetValue(transformNode.anchor_id, out var prefabNode))
                         {
-                            anchor_id = transformNode.anchor_id,
-                            type_id = transformNode.type_id,
-                            prefab_guid_index = (ulong)guidIndex
-                        };
+                            prefabNode = new HierarchicalNode
+                            {
+                                anchor_id = transformNode.anchor_id,
+                                type_id = transformNode.type_id,
+                            };
+                            _nodeMap[transformNode.anchor_id] = prefabNode;
+                        }
+                        prefabNode.prefab_guid_index = (ulong)guidIndex;
                         PopulateNodeFields(prefabNode, transformNode);
                         _unappendedPrefabs[transformNode.anchor_id] = prefabNode;
-                        _nodeMap[transformNode.anchor_id] = prefabNode;
                         consumedAnchorIds.Add(transformNode.anchor_id);
                     }
                     else if (!string.IsNullOrEmpty(guid))
@@ -75,16 +78,20 @@ namespace AssetBank.Editor.SchemaConverter.Processors
 
                 if (fatherId == "0")
                 {
-                    var rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
-                    PopulateNodeFields(rootNode, goNode);
+                    if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
+                    {
+                        rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
+                        PopulateNodeFields(rootNode, goNode);
+                        _nodeMap[goNode.anchor_id] = rootNode;
+                    }
                     rootHierarchicalNodes.Add(rootNode);
-                    _nodeMap[rootNode.anchor_id] = rootNode;
                     _visitedGameObjects.Add(rootNode.anchor_id);
                 }
             }
 
             // 3. Recursively build the hierarchy for each root GameObject.
-            foreach (var rootNode in rootHierarchicalNodes)
+            // Create a copy for iteration as the collection will be modified.
+            foreach (var rootNode in rootHierarchicalNodes.ToList())
             {
                 BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
             }
@@ -104,10 +111,17 @@ namespace AssetBank.Editor.SchemaConverter.Processors
 
                     if (parentNode != null)
                     {
-                        var childNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
-                        PopulateNodeFields(childNode, goNode);
-                        _nodeMap[childNode.anchor_id] = childNode;
-                        parentNode.children.Add(childNode);
+                        if (!_nodeMap.TryGetValue(goNode.anchor_id, out var childNode))
+                        {
+                            childNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
+                            PopulateNodeFields(childNode, goNode);
+                            _nodeMap[childNode.anchor_id] = childNode;
+                        }
+                        
+                        if (!parentNode.children.Contains(childNode))
+                        {
+                            parentNode.children.Add(childNode);
+                        }
                         _visitedGameObjects.Add(childNode.anchor_id);
                         
                         BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
@@ -120,18 +134,43 @@ namespace AssetBank.Editor.SchemaConverter.Processors
             var finalUnvisited = allGameObjects.Where(go => !_visitedGameObjects.Contains(go.anchor_id)).ToList();
             foreach (var goNode in finalUnvisited)
             {
-                var rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
-                PopulateNodeFields(rootNode, goNode);
+                if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
+                {
+                    rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
+                    PopulateNodeFields(rootNode, goNode);
+                    _nodeMap[rootNode.anchor_id] = rootNode;
+                }
+                
                 rootNode.is_orphan = true;
                 rootHierarchicalNodes.Add(rootNode);
-                _nodeMap[rootNode.anchor_id] = rootNode;
                 _visitedGameObjects.Add(rootNode.anchor_id);
                 _warnings.Add($"GameObject '{goNode.anchor_id}' was treated as an orphan root.");
                 
                 BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
             }
             
-            // 6. Final Cleanup Pass
+            // 5b. Prune false roots: remove any nodes from the root list that are actually children of other nodes.
+            var allChildren = new HashSet<HierarchicalNode>();
+            var queue = new Queue<HierarchicalNode>(rootHierarchicalNodes);
+            var visitedForPruning = new HashSet<HierarchicalNode>();
+
+            while (queue.Count > 0)
+            {
+                var node = queue.Dequeue();
+                if (!visitedForPruning.Add(node)) continue;
+
+                foreach (var child in node.children)
+                {
+                    allChildren.Add(child);
+                    queue.Enqueue(child);
+                }
+            }
+            rootHierarchicalNodes.RemoveAll(node => allChildren.Contains(node));
+            
+            // 6. Update nodes that are implicitly prefabs.
+            UpdateImplicitPrefabInstances(rootHierarchicalNodes, prefabInstances, prefabGuids);
+            
+            // 7. Final Cleanup Pass
             CleanupNodes(rootHierarchicalNodes);
 
             return (rootHierarchicalNodes, _warnings, consumedAnchorIds);
@@ -144,7 +183,7 @@ namespace AssetBank.Editor.SchemaConverter.Processors
             var parentOriginalNode = _allNodes.FirstOrDefault(n => n.anchor_id == parentAnchorId);
             if (parentOriginalNode == null) return;
             
-            JToken parentTransformToken = GetTransformComponent(parentOriginalNode);
+            var parentTransformToken = GetTransformComponent(parentOriginalNode);
             if (parentOriginalNode.type_id == "4")
             {
                 parentTransformToken = parentOriginalNode.data;
@@ -163,23 +202,89 @@ namespace AssetBank.Editor.SchemaConverter.Processors
 
                 if (childOriginalNode != null)
                 {
-                    var childNode = new HierarchicalNode { anchor_id = childOriginalNode.anchor_id, type_id = childOriginalNode.type_id };
-                    PopulateNodeFields(childNode, childOriginalNode);
-                    _nodeMap[childNode.anchor_id] = childNode;
-                    parentNode.children.Add(childNode);
-                    _visitedGameObjects.Add(childNode.anchor_id);
-                    BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
+                    if (!_nodeMap.TryGetValue(childOriginalNode.anchor_id, out var childNode))
+                    {
+                        childNode = new HierarchicalNode { anchor_id = childOriginalNode.anchor_id, type_id = childOriginalNode.type_id };
+                        PopulateNodeFields(childNode, childOriginalNode);
+                        _nodeMap[childNode.anchor_id] = childNode;
+                    }
+                    
+                    if (!parentNode.children.Contains(childNode))
+                    {
+                        parentNode.children.Add(childNode);
+                    }
+                    
+                    if (_visitedGameObjects.Add(childNode.anchor_id))
+                    {
+                        BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
+                    }
                 }
                 else if (_unappendedPrefabs.TryGetValue(childId, out var prefabChildNode))
                 {
-                    parentNode.children.Add(prefabChildNode);
+                    if (!parentNode.children.Contains(prefabChildNode))
+                    {
+                        parentNode.children.Add(prefabChildNode);
+                    }
                     _unappendedPrefabs.Remove(childId);
                     BuildHierarchyRecursive(prefabChildNode.anchor_id, allGameObjects);
                 }
             }
         }
+        
+        private static void UpdateImplicitPrefabInstances(
+            IEnumerable<HierarchicalNode> nodes,
+            IReadOnlyDictionary<string, OriginalNode> prefabInstances,
+            List<string> prefabGuids)
+        {
+            if (nodes == null) return;
+
+            foreach (var node in nodes)
+            {
+                // Only try to update the node if its prefab status is unknown.
+                if (!node.prefab_guid_index.HasValue)
+                {
+                    string prefabInstanceFileId = null;
+                
+                    if (node.type_id == "1" && node.Fields.TryGetValue("m_Component", out var componentsToken) && componentsToken is JArray components)
+                    {
+                        foreach (var component in components)
+                        {
+                            var fileID = component?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString();
+
+                            if (!string.IsNullOrEmpty(fileID) && fileID != "0")
+                            {
+                                prefabInstanceFileId = fileID;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (prefabInstanceFileId != null)
+                    {
+                        if (prefabInstances.TryGetValue(prefabInstanceFileId, out var prefabInstanceNode))
+                        {
+                            var guid = prefabInstanceNode.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
+                            if (!string.IsNullOrEmpty(guid))
+                            {
+                                var guidIndex = prefabGuids.IndexOf(guid);
+                                if (guidIndex != -1)
+                                {
+                                    node.prefab_guid_index = (ulong)guidIndex;
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                // ALWAYS recurse, regardless of the parent's status.
+                if (node.children.Any())
+                {
+                    UpdateImplicitPrefabInstances(node.children, prefabInstances, prefabGuids);
+                }
+            }
+        }
 
-        private void PopulateNodeFields(HierarchicalNode node, OriginalNode originalNode)
+        private static void PopulateNodeFields(HierarchicalNode node, OriginalNode originalNode)
         {
             JObject dataBlock = null;
             if (originalNode.type_id == "1")
@@ -199,7 +304,7 @@ namespace AssetBank.Editor.SchemaConverter.Processors
             }
         }
 
-        private void CleanupNodes(IEnumerable<HierarchicalNode> nodes)
+        private static void CleanupNodes(IEnumerable<HierarchicalNode> nodes)
         {
             foreach (var node in nodes)
             {
@@ -232,17 +337,17 @@ namespace AssetBank.Editor.SchemaConverter.Processors
             }
         }
 
-        private JToken GetTransformComponent(OriginalNode goNode)
+        private static JToken GetTransformComponent(OriginalNode goNode)
         {
             return goNode.data?["GameObject"]?["m_Component"]?.FirstOrDefault(c => c["Transform"] != null);
         }
         
-        private string GetTransformId(OriginalNode goNode)
+        private static string GetTransformId(OriginalNode goNode)
         {
             return GetTransformComponent(goNode)?["anchor_id"]?.ToString();
         }
 
-        private string GetFatherIdFromTransform(JToken transformComponent)
+        private static string GetFatherIdFromTransform(JToken transformComponent)
         {
             return transformComponent?["Transform"]?["m_Father"]?["fileID"]?.ToString() ?? "0";
         }

+ 77 - 1
Assets/AssetBank/Editor/Tools/JsonDataTrimmer.cs

@@ -1,6 +1,8 @@
 using System.Collections.Generic;
 using System.Linq;
+using AssetBank.Editor.SchemaConverter.Data.Input;
 using AssetBank.Settings;
+using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using UnityEngine;
 
@@ -10,7 +12,9 @@ namespace AssetBank.Editor.Tools
     {
         public static string Process(string rawJsonText, ProjectExporterSettings settings)
         {
-            var allComponents = JArray.Parse(rawJsonText);
+            var processedJson = ShortenBrokenPrefabGameObjects(rawJsonText);
+            
+            var allComponents = JArray.Parse(processedJson);
             
             // Pass 1: Build the definitive ruleset
             var rules = settings.SafeComponents.ToDictionary(r => r.Component, r => r.ExposedFields.ToHashSet());
@@ -163,5 +167,77 @@ namespace AssetBank.Editor.Tools
 
             return finalOutput.ToString(Newtonsoft.Json.Formatting.None);
         }
+
+        private static string ShortenBrokenPrefabGameObjects(string data)
+        {
+            var nodes = JsonConvert.DeserializeObject<List<OriginalNode>>(data);
+            
+            if (nodes == null)
+            {
+                return data;
+            }
+            
+            const string gameObjectTypeId = "1";
+            const string prefabTypeId = "1001";
+            const string transformTypeId = "4";
+            var allGameObjects = nodes.Where(n => n.type_id == gameObjectTypeId).ToList();
+            var allTransforms = nodes.Where(n => n.type_id == transformTypeId).ToList();
+            var allPrefabs = nodes.Where(n => n.type_id == prefabTypeId).ToList();
+            var restOfComponents = nodes.Where(n => n.type_id != gameObjectTypeId && n.type_id != prefabTypeId && n.type_id != transformTypeId).ToList();
+            
+            foreach (var gameObject in allGameObjects)
+            {
+                var nodeIndex = nodes.IndexOf(gameObject);
+                var prefabInstanceId = gameObject.data?["GameObject"]?["m_PrefabInstance"]?["fileID"]?.ToString();
+                if (string.IsNullOrEmpty(prefabInstanceId) || prefabInstanceId == "0") continue;
+                var prefab = allPrefabs.FirstOrDefault(p => p.anchor_id == prefabInstanceId);
+                if (prefab == null)
+                {
+                    Debug.LogWarning($"JsonDataTrimmer: Could not find prefab with anchor_id '{prefabInstanceId}' referenced by GameObject '{gameObject.data?["GameObject"]?["m_Name"]}'.");
+                    continue;
+                }
+                var prefabGuid = prefab.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
+                if (string.IsNullOrEmpty(prefabGuid))
+                {
+                    Debug.LogWarning($"JsonDataTrimmer: Could not find GUID for prefab with anchor_id '{prefabInstanceId}' referenced by GameObject '{gameObject.data?["GameObject"]?["m_Name"]}'.");
+                    continue;
+                }
+                var relatedTransform = allTransforms.FirstOrDefault(t => t.data?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString() == prefabInstanceId);
+                if (relatedTransform == null)
+                {
+                    Debug.LogWarning($"JsonDataTrimmer: Could not find Transform with anchor_id '{prefabInstanceId}' referenced by GameObject '{gameObject.data?["GameObject"]?["m_Name"]}'.");
+                    continue;
+                }
+                var relatedComponents = restOfComponents.Where(c => c.data?.SelectToken("*.m_GameObject.fileID")?.ToString() == gameObject.anchor_id).ToList();
+                
+                var newNode = new OriginalNode
+                {
+                    type_id = gameObject.type_id,
+                    anchor_id = gameObject.anchor_id,
+                    data = gameObject.data
+                };
+                var jArray = new JArray();
+                foreach (var component in relatedComponents)
+                {
+                    jArray.Add(JObject.FromObject(new
+                    {
+                        component = new
+                        {
+                            fileID = component.anchor_id
+                        }
+                    }));
+                }
+                jArray.Add(JObject.FromObject(new
+                {
+                    component = new
+                    {
+                        fileID = relatedTransform.anchor_id
+                    }
+                }));
+                newNode.data["GameObject"]!["m_Component"] = jArray;
+                nodes[nodeIndex] = newNode;
+            }
+            return JsonConvert.SerializeObject(nodes, Formatting.None);
+        }
     }
 }

+ 81 - 0
SCENE_SCHEMA.md

@@ -0,0 +1,81 @@
+# Scene JSON Schema Definition
+
+This document outlines the structure of the JSON files generated by the scene parser. The schema uses a highly compressed format with single or double-letter keys to reduce file size. This document serves as the legend to decode that structure.
+
+---
+
+## 1. Top-Level Object (`SceneData`)
+
+The root object of the JSON file.
+
+| Key | Property Name | Type | Description |
+|:---:|:---|:---|:---|
+| `p` | `PrefabsInScene` | `List<string>` | A list of all unique prefab GUIDs that are referenced by prefab instances within the scene. This property is omitted if no prefabs are used. |
+| `h` | `Hierarchy` | `List<GameObjectData>` | A list of all root `GameObject`s in the scene. This property is omitted if the scene is empty. |
+
+### Example
+```json
+{
+  "p": [
+    "f7d8f8f8f8f8f8f8f8f8f8f8f8f8f8f8",
+    "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
+  ],
+  "h": [
+    { ...GameObjectData... },
+    { ...GameObjectData... }
+  ]
+}
+```
+
+---
+
+## 2. GameObject Object (`GameObjectData`)
+
+Represents a single `GameObject` in the scene hierarchy.
+
+| Key | Property Name | Type | Description |
+|:---:|:---|:---|:---|
+| `e` | `Enabled` | `boolean` | The active state of the GameObject. **Omitted if `true` (default).** |
+| `n` | `Name` | `string` | The name of the GameObject. |
+| `t` | `Tag` | `string` | The tag assigned to the GameObject. **Omitted if `"Untagged"` (default).** |
+| `l` | `Layer` | `integer` | The layer number. **Omitted if `0` (default).** |
+| `a` | `AnchorId` | `string` | The unique `fileID` of this GameObject within the scene file, used for referencing. |
+| `pi`| `PrefabIndex` | `integer` | If this GameObject is a prefab instance, this is the 0-based index into the root `p` (PrefabsInScene) list. **Omitted if not a prefab.** |
+| `c` | `Components` | `List<ComponentData>` | A list of all components on this GameObject. This is **only used for non-prefab objects**. For prefabs, overrides are stored in `ac` and `rc`. Omitted if empty. |
+| `ac`| `AddedComponents` | `List<ComponentData>` | A list of components that were added to this specific prefab instance. Omitted if empty. |
+| `rc`| `RemovedComponentAnchorIds` | `List<string>` | A list of `fileID`s for components that were removed from this specific prefab instance. Omitted if empty. |
+| `ch`| `Children` | `List<GameObjectData>` | A list of child `GameObject`s. For prefabs, this only contains newly added children or original children that have overrides. Omitted if empty. |
+
+---
+
+## 3. Component Object (`ComponentData`)
+
+Represents a single `Component` attached to a `GameObject`.
+
+| Key | Property Name | Type | Description |
+|:---:|:---|:---|:---|
+| `t` | `Name` (Type) | `string` | The full C# type name of the component (e.g., `UnityEngine.Transform`, `MyGame.PlayerController`). |
+| `a` | `AnchorId` | `string` | The unique `fileID` of this component within the scene file. |
+| `f` | `Fields` | `object` | For `MonoBehaviour` scripts, this is a key-value map of all its serialized fields and their values. This property is omitted if the component is not a MonoBehaviour or has no serializable fields. |
+
+### Example
+```json
+{
+  "t": "UnityEngine.BoxCollider",
+  "a": "1234567890"
+}
+```
+```json
+{
+  "t": "MyGame.PlayerController",
+  "a": "9876543210",
+  "f": {
+    "m_Speed": 5.5,
+    "m_PlayerName": "Hero",
+    "m_Target": {
+      "type": "UnityEngine.Transform",
+      "scene_object_id": 1122334455
+    }
+  }
+}
+```