【AI编码】【UI信息】增加UI信息提取器

main
刘涛 2026-02-04 15:01:56 +08:00
parent 80c7d490f9
commit c46093301d
23 changed files with 13734 additions and 0 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f36e65c488d04421a7e348157f4511d2
timeCreated: 1770186775

View File

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

View File

@ -0,0 +1,93 @@
using UIInfoBuilder.Data;
using UnityEngine;
using UnityEngine.UI;
namespace UIInfoBuilder.ComponentExtractors
{
/// <summary>
/// Button 精细提取器
/// </summary>
public class ButtonExtractor : IComponentExtractor
{
public bool CanExtract(Component component)
{
return component is Button;
}
public void Extract(Component component, UIInfoComponent info)
{
var button = (Button)component;
// Interactable
info.properties.Add($"Interactable:{button.interactable.ToString().ToLower()}");
// Transition
info.properties.Add($"Transition:{button.transition}");
// Target Graphic
string targetGraphicName = button.targetGraphic != null ? button.targetGraphic.name : "";
info.properties.Add($"TargetGraphic:{targetGraphicName}");
// 根据 Transition 类型提取不同属性
switch (button.transition)
{
case Selectable.Transition.ColorTint:
_ExtractColorBlock(button.colors, info);
break;
case Selectable.Transition.SpriteSwap:
_ExtractSpriteState(button.spriteState, info);
break;
case Selectable.Transition.Animation:
_ExtractAnimationTriggers(button.animationTriggers, info);
break;
}
// Navigation
info.properties.Add($"NavigationMode:{button.navigation.mode}");
}
private void _ExtractColorBlock(ColorBlock colors, UIInfoComponent info)
{
info.properties.Add($"NormalColor:{_ColorToHex(colors.normalColor)}");
info.properties.Add($"HighlightedColor:{_ColorToHex(colors.highlightedColor)}");
info.properties.Add($"PressedColor:{_ColorToHex(colors.pressedColor)}");
info.properties.Add($"SelectedColor:{_ColorToHex(colors.selectedColor)}");
info.properties.Add($"DisabledColor:{_ColorToHex(colors.disabledColor)}");
info.properties.Add($"ColorMultiplier:{colors.colorMultiplier}");
info.properties.Add($"FadeDuration:{colors.fadeDuration}");
}
private void _ExtractSpriteState(SpriteState spriteState, UIInfoComponent info)
{
string highlightedSprite = spriteState.highlightedSprite != null ? spriteState.highlightedSprite.name : "";
string pressedSprite = spriteState.pressedSprite != null ? spriteState.pressedSprite.name : "";
string selectedSprite = spriteState.selectedSprite != null ? spriteState.selectedSprite.name : "";
string disabledSprite = spriteState.disabledSprite != null ? spriteState.disabledSprite.name : "";
info.properties.Add($"HighlightedSprite:{highlightedSprite}");
info.properties.Add($"PressedSprite:{pressedSprite}");
info.properties.Add($"SelectedSprite:{selectedSprite}");
info.properties.Add($"DisabledSprite:{disabledSprite}");
}
private void _ExtractAnimationTriggers(AnimationTriggers triggers, UIInfoComponent info)
{
info.properties.Add($"NormalTrigger:{triggers.normalTrigger}");
info.properties.Add($"HighlightedTrigger:{triggers.highlightedTrigger}");
info.properties.Add($"PressedTrigger:{triggers.pressedTrigger}");
info.properties.Add($"SelectedTrigger:{triggers.selectedTrigger}");
info.properties.Add($"DisabledTrigger:{triggers.disabledTrigger}");
}
private string _ColorToHex(Color color)
{
int r = Mathf.RoundToInt(color.r * 255);
int g = Mathf.RoundToInt(color.g * 255);
int b = Mathf.RoundToInt(color.b * 255);
int a = Mathf.RoundToInt(color.a * 255);
return $"#{r:X2}{g:X2}{b:X2}{a:X2}";
}
}
}

View File

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

View File

@ -0,0 +1,143 @@
using UIInfoBuilder.Data;
using UnityEditor;
using UnityEngine;
namespace UIInfoBuilder.ComponentExtractors
{
/// <summary>
/// 默认组件提取器,通过反射读取 SerializedProperty
/// </summary>
public class DefaultComponentExtractor : IComponentExtractor
{
public bool CanExtract(Component component)
{
return true; // 作为兜底,处理所有组件
}
public void Extract(Component component, UIInfoComponent info)
{
var serializedObject = new SerializedObject(component);
var iterator = serializedObject.GetIterator();
// 进入第一个属性
if (iterator.NextVisible(true))
{
do
{
// 跳过 m_Script 属性
if (iterator.name == "m_Script")
continue;
var propertyString = _FormatProperty(iterator);
if (!string.IsNullOrEmpty(propertyString))
{
info.properties.Add(propertyString);
}
} while (iterator.NextVisible(false));
}
}
private string _FormatProperty(SerializedProperty property)
{
string name = property.displayName;
string value = _GetPropertyValue(property);
if (value == null)
return null;
return $"{name}:{value}";
}
private string _GetPropertyValue(SerializedProperty property)
{
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
return property.intValue.ToString();
case SerializedPropertyType.Boolean:
return property.boolValue.ToString().ToLower();
case SerializedPropertyType.Float:
return property.floatValue.ToString("G");
case SerializedPropertyType.String:
return property.stringValue;
case SerializedPropertyType.Color:
return _ColorToHex(property.colorValue);
case SerializedPropertyType.ObjectReference:
if (property.objectReferenceValue != null)
return property.objectReferenceValue.name;
return "";
case SerializedPropertyType.Enum:
return property.enumNames[property.enumValueIndex];
case SerializedPropertyType.Vector2:
var v2 = property.vector2Value;
return $"{v2.x},{v2.y}";
case SerializedPropertyType.Vector3:
var v3 = property.vector3Value;
return $"{v3.x},{v3.y},{v3.z}";
case SerializedPropertyType.Vector4:
var v4 = property.vector4Value;
return $"{v4.x},{v4.y},{v4.z},{v4.w}";
case SerializedPropertyType.Rect:
var rect = property.rectValue;
return $"{rect.x},{rect.y},{rect.width},{rect.height}";
case SerializedPropertyType.Bounds:
var bounds = property.boundsValue;
return $"center:{bounds.center.x},{bounds.center.y},{bounds.center.z};size:{bounds.size.x},{bounds.size.y},{bounds.size.z}";
case SerializedPropertyType.Quaternion:
var euler = property.quaternionValue.eulerAngles;
return $"{euler.x},{euler.y},{euler.z}";
case SerializedPropertyType.Vector2Int:
var v2i = property.vector2IntValue;
return $"{v2i.x},{v2i.y}";
case SerializedPropertyType.Vector3Int:
var v3i = property.vector3IntValue;
return $"{v3i.x},{v3i.y},{v3i.z}";
case SerializedPropertyType.RectInt:
var rectInt = property.rectIntValue;
return $"{rectInt.x},{rectInt.y},{rectInt.width},{rectInt.height}";
case SerializedPropertyType.BoundsInt:
var boundsInt = property.boundsIntValue;
return $"position:{boundsInt.position.x},{boundsInt.position.y},{boundsInt.position.z};size:{boundsInt.size.x},{boundsInt.size.y},{boundsInt.size.z}";
case SerializedPropertyType.LayerMask:
return property.intValue.ToString();
case SerializedPropertyType.ArraySize:
case SerializedPropertyType.Generic:
case SerializedPropertyType.AnimationCurve:
case SerializedPropertyType.Gradient:
case SerializedPropertyType.ExposedReference:
case SerializedPropertyType.FixedBufferSize:
case SerializedPropertyType.ManagedReference:
default:
// 复杂类型跳过
return null;
}
}
private string _ColorToHex(Color color)
{
int r = Mathf.RoundToInt(color.r * 255);
int g = Mathf.RoundToInt(color.g * 255);
int b = Mathf.RoundToInt(color.b * 255);
int a = Mathf.RoundToInt(color.a * 255);
return $"#{r:X2}{g:X2}{b:X2}{a:X2}";
}
}
}

View File

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

View File

@ -0,0 +1,18 @@
using UIInfoBuilder.Data;
using UnityEngine;
namespace UIInfoBuilder.ComponentExtractors
{
public interface IComponentExtractor
{
/// <summary>
/// 判断是否可以处理该组件
/// </summary>
bool CanExtract(Component component);
/// <summary>
/// 提取组件属性到 UIInfoComponent
/// </summary>
void Extract(Component component, UIInfoComponent info);
}
}

View File

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

View File

@ -0,0 +1,73 @@
using UIInfoBuilder.Data;
using UnityEngine;
using UnityEngine.UI;
namespace UIInfoBuilder.ComponentExtractors
{
/// <summary>
/// Image 精细提取器
/// </summary>
public class ImageExtractor : IComponentExtractor
{
public bool CanExtract(Component component)
{
return component is Image;
}
public void Extract(Component component, UIInfoComponent info)
{
var image = (Image)component;
// Sprite
string spriteName = image.sprite != null ? image.sprite.name : "";
info.properties.Add($"Sprite:{spriteName}");
// Color
info.properties.Add($"Color:{_ColorToHex(image.color)}");
// Material
string materialName = image.material != null ? image.material.name : "";
info.properties.Add($"Material:{materialName}");
// Raycast Target
info.properties.Add($"RaycastTarget:{image.raycastTarget.ToString().ToLower()}");
// Raycast Padding
var padding = image.raycastPadding;
info.properties.Add($"RaycastPadding:{padding.x},{padding.y},{padding.z},{padding.w}");
// Maskable
info.properties.Add($"Maskable:{image.maskable.ToString().ToLower()}");
// Image Type
info.properties.Add($"ImageType:{image.type}");
// Fill相关属性
if (image.type == Image.Type.Filled)
{
info.properties.Add($"FillMethod:{image.fillMethod}");
info.properties.Add($"FillOrigin:{image.fillOrigin}");
info.properties.Add($"FillAmount:{image.fillAmount}");
info.properties.Add($"FillClockwise:{image.fillClockwise.ToString().ToLower()}");
}
// Preserve Aspect
info.properties.Add($"PreserveAspect:{image.preserveAspect.ToString().ToLower()}");
// Use Sprite Mesh
info.properties.Add($"UseSpriteMesh:{image.useSpriteMesh.ToString().ToLower()}");
// Pixels Per Unit Multiplier
info.properties.Add($"PixelsPerUnitMultiplier:{image.pixelsPerUnitMultiplier}");
}
private string _ColorToHex(Color color)
{
int r = Mathf.RoundToInt(color.r * 255);
int g = Mathf.RoundToInt(color.g * 255);
int b = Mathf.RoundToInt(color.b * 255);
int a = Mathf.RoundToInt(color.a * 255);
return $"#{r:X2}{g:X2}{b:X2}{a:X2}";
}
}
}

View File

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

View File

@ -0,0 +1,45 @@
using UIInfoBuilder.Data;
using UnityEngine;
namespace UIInfoBuilder.ComponentExtractors
{
/// <summary>
/// RectTransform 精细提取器
/// </summary>
public class RectTransformExtractor : IComponentExtractor
{
public bool CanExtract(Component component)
{
return component is RectTransform;
}
public void Extract(Component component, UIInfoComponent info)
{
var rt = (RectTransform)component;
// Anchors
info.properties.Add($"AnchorMin:{rt.anchorMin.x},{rt.anchorMin.y}");
info.properties.Add($"AnchorMax:{rt.anchorMax.x},{rt.anchorMax.y}");
// Pivot
info.properties.Add($"Pivot:{rt.pivot.x},{rt.pivot.y}");
// AnchoredPosition
info.properties.Add($"AnchoredPosition:{rt.anchoredPosition.x},{rt.anchoredPosition.y}");
info.properties.Add($"AnchoredPosition3D:{rt.anchoredPosition3D.x},{rt.anchoredPosition3D.y},{rt.anchoredPosition3D.z}");
// SizeDelta
info.properties.Add($"SizeDelta:{rt.sizeDelta.x},{rt.sizeDelta.y}");
// LocalPosition
info.properties.Add($"LocalPosition:{rt.localPosition.x},{rt.localPosition.y},{rt.localPosition.z}");
// LocalRotation (欧拉角)
var euler = rt.localRotation.eulerAngles;
info.properties.Add($"LocalRotation:{euler.x},{euler.y},{euler.z}");
// LocalScale
info.properties.Add($"LocalScale:{rt.localScale.x},{rt.localScale.y},{rt.localScale.z}");
}
}
}

View File

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

View File

@ -0,0 +1,145 @@
using UIInfoBuilder.Data;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace UIInfoBuilder.ComponentExtractors
{
/// <summary>
/// Text 和 TextMeshProUGUI 精细提取器
/// </summary>
public class TextExtractor : IComponentExtractor
{
public bool CanExtract(Component component)
{
return component is Text || component is TMP_Text;
}
public void Extract(Component component, UIInfoComponent info)
{
if (component is Text text)
{
_ExtractText(text, info);
}
else if (component is TMP_Text tmpText)
{
_ExtractTMPText(tmpText, info);
}
}
private void _ExtractText(Text text, UIInfoComponent info)
{
// Text
info.properties.Add($"Text:{text.text}");
// Font
string fontName = text.font != null ? text.font.name : "";
info.properties.Add($"Font:{fontName}");
// Font Style
info.properties.Add($"FontStyle:{text.fontStyle}");
// Font Size
info.properties.Add($"FontSize:{text.fontSize}");
// Line Spacing
info.properties.Add($"LineSpacing:{text.lineSpacing}");
// Rich Text
info.properties.Add($"RichText:{text.supportRichText.ToString().ToLower()}");
// Alignment
info.properties.Add($"Alignment:{text.alignment}");
// Align By Geometry
info.properties.Add($"AlignByGeometry:{text.alignByGeometry.ToString().ToLower()}");
// Horizontal Overflow
info.properties.Add($"HorizontalOverflow:{text.horizontalOverflow}");
// Vertical Overflow
info.properties.Add($"VerticalOverflow:{text.verticalOverflow}");
// Best Fit
info.properties.Add($"BestFit:{text.resizeTextForBestFit.ToString().ToLower()}");
// Color
info.properties.Add($"Color:{_ColorToHex(text.color)}");
// Material
string materialName = text.material != null ? text.material.name : "";
info.properties.Add($"Material:{materialName}");
// Raycast Target
info.properties.Add($"RaycastTarget:{text.raycastTarget.ToString().ToLower()}");
// Maskable
info.properties.Add($"Maskable:{text.maskable.ToString().ToLower()}");
}
private void _ExtractTMPText(TMP_Text tmpText, UIInfoComponent info)
{
// Text
info.properties.Add($"Text:{tmpText.text}");
// Font Asset
string fontName = tmpText.font != null ? tmpText.font.name : "";
info.properties.Add($"FontAsset:{fontName}");
// Material Preset
string materialName = tmpText.fontSharedMaterial != null ? tmpText.fontSharedMaterial.name : "";
info.properties.Add($"MaterialPreset:{materialName}");
// Font Style
info.properties.Add($"FontStyle:{tmpText.fontStyle}");
// Font Size
info.properties.Add($"FontSize:{tmpText.fontSize}");
// Auto Size
info.properties.Add($"AutoSize:{tmpText.enableAutoSizing.ToString().ToLower()}");
// Vertex Color
info.properties.Add($"Color:{_ColorToHex(tmpText.color)}");
// Color Gradient
info.properties.Add($"EnableVertexGradient:{tmpText.enableVertexGradient.ToString().ToLower()}");
// Spacing - Character
info.properties.Add($"CharacterSpacing:{tmpText.characterSpacing}");
// Spacing - Word
info.properties.Add($"WordSpacing:{tmpText.wordSpacing}");
// Spacing - Line
info.properties.Add($"LineSpacing:{tmpText.lineSpacing}");
// Spacing - Paragraph
info.properties.Add($"ParagraphSpacing:{tmpText.paragraphSpacing}");
// Alignment
info.properties.Add($"Alignment:{tmpText.alignment}");
// Wrapping
info.properties.Add($"EnableWordWrapping:{tmpText.enableWordWrapping.ToString().ToLower()}");
// Overflow
info.properties.Add($"OverflowMode:{tmpText.overflowMode}");
// Raycast Target
info.properties.Add($"RaycastTarget:{tmpText.raycastTarget.ToString().ToLower()}");
// Maskable
info.properties.Add($"Maskable:{tmpText.maskable.ToString().ToLower()}");
}
private string _ColorToHex(Color color)
{
int r = Mathf.RoundToInt(color.r * 255);
int g = Mathf.RoundToInt(color.g * 255);
int b = Mathf.RoundToInt(color.b * 255);
int a = Mathf.RoundToInt(color.a * 255);
return $"#{r:X2}{g:X2}{b:X2}{a:X2}";
}
}
}

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ca3089d0044b40ecaad89583bfcdbf43
timeCreated: 1770186781

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace UIInfoBuilder.Data
{
public class UIInfoComponent
{
public string componentName;
public bool enable;
public List<string> properties = new List<string>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e1a05d01923d414ab0733f15a2e9b267
timeCreated: 1770186861

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace UIInfoBuilder.Data
{
public class UIInfoNode
{
public string name;
public bool active;
public List<UIInfoComponent> components = new List<UIInfoComponent>();
public List<UIInfoNode> children = new List<UIInfoNode>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45573fea4d42434cbe8a1cbc92b14ece
timeCreated: 1770186795

View File

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UIInfoBuilder.ComponentExtractors;
using UIInfoBuilder.Data;
using UnityEditor;
using UnityEngine;
namespace UIInfoBuilder
{
public class UIInfoExtractor
{
private static readonly List<IComponentExtractor> _extractors = new List<IComponentExtractor>();
private static readonly DefaultComponentExtractor _defaultExtractor = new DefaultComponentExtractor();
static UIInfoExtractor()
{
// 注册特殊组件提取器(优先级高的放前面)
_extractors.Add(new RectTransformExtractor());
_extractors.Add(new ImageExtractor());
_extractors.Add(new TextExtractor());
_extractors.Add(new ButtonExtractor());
}
[MenuItem("Assets/UIInfoBuilder/ExtractPrefab")]
private static void _AssetHandle()
{
var selectedObjects = Selection.gameObjects;
foreach (var obj in selectedObjects)
{
string savePath = Application.dataPath + "/../ExtractedPrefabs/" + obj.name + ".json";
ExtractPrefab(obj, savePath);
Debug.Log($"[UIInfoBuilder] Extracted: {obj.name} -> {savePath}");
}
}
/// <summary>
/// 提取 Prefab 信息并保存为 JSON
/// </summary>
public static void ExtractPrefab(GameObject prefab, string savePath)
{
var rootNode = _ExtractNode(prefab);
_SaveToJson(rootNode, savePath);
}
/// <summary>
/// 递归提取节点信息
/// </summary>
private static UIInfoNode _ExtractNode(GameObject go)
{
var node = new UIInfoNode
{
name = go.name,
active = go.activeSelf
};
// 提取所有组件
var components = go.GetComponents<Component>();
foreach (var component in components)
{
if (component == null)
continue;
var compInfo = _ExtractComponent(component);
node.components.Add(compInfo);
}
// 递归提取子节点
for (int i = 0; i < go.transform.childCount; i++)
{
var child = go.transform.GetChild(i).gameObject;
var childNode = _ExtractNode(child);
node.children.Add(childNode);
}
return node;
}
/// <summary>
/// 提取单个组件信息
/// </summary>
private static UIInfoComponent _ExtractComponent(Component component)
{
var info = new UIInfoComponent
{
componentName = component.GetType().Name,
enable = _GetComponentEnabled(component)
};
// 查找匹配的特殊提取器
IComponentExtractor extractor = null;
foreach (var ext in _extractors)
{
if (ext.CanExtract(component))
{
extractor = ext;
break;
}
}
// 使用特殊提取器或默认提取器
if (extractor != null)
{
extractor.Extract(component, info);
}
else
{
_defaultExtractor.Extract(component, info);
}
return info;
}
/// <summary>
/// 获取组件的启用状态
/// </summary>
private static bool _GetComponentEnabled(Component component)
{
// Behaviour 类型的组件有 enabled 属性
if (component is Behaviour behaviour)
{
return behaviour.enabled;
}
// Renderer 类型的组件有 enabled 属性
if (component is Renderer renderer)
{
return renderer.enabled;
}
// Collider 类型的组件有 enabled 属性
if (component is Collider collider)
{
return collider.enabled;
}
// 其他组件默认返回 true
return true;
}
/// <summary>
/// 保存为 JSON 文件
/// </summary>
private static void _SaveToJson(UIInfoNode node, string savePath)
{
// 确保目录存在
string directory = Path.GetDirectoryName(savePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 使用自定义递归序列化JsonUtility 不支持嵌套 List 递归)
var sb = new StringBuilder();
_SerializeNode(node, sb, 0);
File.WriteAllText(savePath, sb.ToString());
}
/// <summary>
/// 递归序列化节点为 JSON
/// </summary>
private static void _SerializeNode(UIInfoNode node, StringBuilder sb, int indent)
{
string indentStr = new string(' ', indent * 2);
string indentStr1 = new string(' ', (indent + 1) * 2);
string indentStr2 = new string(' ', (indent + 2) * 2);
string indentStr3 = new string(' ', (indent + 3) * 2);
sb.AppendLine($"{indentStr}{{");
sb.AppendLine($"{indentStr1}\"name\": \"{_EscapeJsonString(node.name)}\",");
sb.AppendLine($"{indentStr1}\"active\": {node.active.ToString().ToLower()},");
// Components
sb.AppendLine($"{indentStr1}\"components\": [");
for (int i = 0; i < node.components.Count; i++)
{
var comp = node.components[i];
sb.AppendLine($"{indentStr2}{{");
sb.AppendLine($"{indentStr3}\"componentName\": \"{_EscapeJsonString(comp.componentName)}\",");
sb.AppendLine($"{indentStr3}\"enable\": {comp.enable.ToString().ToLower()},");
sb.Append($"{indentStr3}\"properties\": [");
if (comp.properties.Count > 0)
{
sb.AppendLine();
for (int j = 0; j < comp.properties.Count; j++)
{
string comma = j < comp.properties.Count - 1 ? "," : "";
sb.AppendLine($"{indentStr3} \"{_EscapeJsonString(comp.properties[j])}\"{comma}");
}
sb.AppendLine($"{indentStr3}]");
}
else
{
sb.AppendLine("]");
}
string compComma = i < node.components.Count - 1 ? "," : "";
sb.AppendLine($"{indentStr2}}}{compComma}");
}
sb.AppendLine($"{indentStr1}],");
// Children
sb.AppendLine($"{indentStr1}\"children\": [");
for (int i = 0; i < node.children.Count; i++)
{
_SerializeNode(node.children[i], sb, indent + 2);
if (i < node.children.Count - 1)
{
// 在上一个 } 后面加逗号
sb.Length -= Environment.NewLine.Length; // 移除换行
sb.AppendLine(",");
}
}
sb.AppendLine($"{indentStr1}]");
sb.AppendLine($"{indentStr}}}");
}
/// <summary>
/// 转义 JSON 字符串中的特殊字符
/// </summary>
private static string _EscapeJsonString(string str)
{
if (string.IsNullOrEmpty(str))
return str;
return str
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bdc0f7ce965c45b3a83334b4b6a116f8
timeCreated: 1770186962

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff