You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中C#自定义标记类的序列化解析继承方案问题

解决方案:Unity多态标记存读系统的实现问题

你的核心问题出在三个关键点:方法重载而非重写、Unity JsonUtility不支持多态反序列化,以及缺少类型识别的分发机制。以下是具体的落地解决方案:

1. 修正方法重写逻辑

你当前TextNodeParser中的CreateMarkerObject是重载(参数为TextMarkerData),而非对基类MarkerData参数方法的重写。C#方法调用是编译时绑定,基类循环传入MarkerData类型时,只会触发基类方法,不会自动匹配派生类的重载。

修改派生类方法签名,确保是合法重写:

public class TextNodeParser : NodeParser
{
    // 参数与基类保持一致,确保是重写而非重载
    public override void CreateMarkerObject(MarkerData data)
    {
        // 先判断类型,转为子类后处理专属逻辑
        if (data is TextMarkerData textData)
        {
            // 文本标记专属创建逻辑:比如生成带TextMeshPro的对象
            GameObject textMarker = new GameObject("TextMarker");
            textMarker.transform.position = textData.pos;
            var textComp = textMarker.AddComponent<TextMeshPro>();
            textComp.text = textData.textContent;
            textComp.fontSize = textData.fontSize;
        }
        else
        {
            // 可选:调用基类逻辑处理其他类型
            base.CreateMarkerObject(data);
        }
    }
}

2. 解决Json多态反序列化问题

Unity原生JsonUtility不支持多态类型反序列化——即便JSON包含子类字段,反序列化为MarkerData后会丢失子类信息,类型被截断为基类。解决办法是给MarkerData添加类型标识,手动处理子类反序列化:

第一步:给数据类添加类型标识

public enum MarkerType
{
    Base,
    Text,
    Image,
    Object3D
}

[Serializable]
public class MarkerData
{
    public MarkerType type;
    public Vector3 pos;
    // 基类通用字段
}

[Serializable]
public class TextMarkerData : MarkerData
{
    public string textContent;
    public int fontSize;
    // 文本标记专属字段
}

第二步:修改JSON解析逻辑,手动转换子类

修改NodeParser的解析方法,先读取类型标识,再针对性反序列化为对应子类:

public class NodeParser : MonoBehaviour
{
    public List<MarkerData> markers = new List<MarkerData>();

    public void CreateMarkersFromJSON()
    {
        string jsonPath = Path.Combine(Application.streamingAssetsPath, "MarkerData.json");
        string json = File.ReadAllText(jsonPath);
        
        // 先解析临时容器,获取每个标记的类型信息
        var tempCollection = JsonUtility.FromJson<MarkerCollectionTemp>(json);
        markers.Clear();

        foreach (var tempData in tempCollection.markers)
        {
            MarkerData markerData = null;
            // 根据类型反序列化为对应子类
            switch (tempData.type)
            {
                case MarkerType.Text:
                    markerData = JsonUtility.FromJson<TextMarkerData>(JsonUtility.ToJson(tempData));
                    break;
                case MarkerType.Image:
                    markerData = JsonUtility.FromJson<ImageMarkerData>(JsonUtility.ToJson(tempData));
                    break;
                case MarkerType.Object3D:
                    markerData = JsonUtility.FromJson<Object3DMarkerData>(JsonUtility.ToJson(tempData));
                    break;
                default:
                    markerData = tempData;
                    break;
            }

            if (markerData != null)
            {
                CreateMarkerObject(markerData);
                markers.Add(markerData);
            }
        }
    }

    // 临时容器类,用于先读取类型标识
    [Serializable]
    private class MarkerCollectionTemp
    {
        public List<MarkerData> markers;
    }

    public virtual void CreateMarkerObject(MarkerData data)
    {
        // 基类通用创建逻辑:比如生成基础标记对象
        GameObject markerObj = new GameObject("BaseMarker");
        markerObj.transform.position = data.pos;
    }
}

3. 用工厂模式优化类型分发(可选,避免臃肿switch)

如果标记类型较多,switch会变得冗余,可使用工厂模式注册每种类型的创建逻辑:

public static class MarkerFactory
{
    private static readonly Dictionary<MarkerType, Action<MarkerData>> _creators = new();

    // 注册每种标记的创建逻辑
    public static void RegisterCreator(MarkerType type, Action<MarkerData> creator)
    {
        if (!_creators.ContainsKey(type))
        {
            _creators.Add(type, creator);
        }
    }

    // 根据类型创建标记
    public static void CreateMarker(MarkerData data)
    {
        if (_creators.TryGetValue(data.type, out var creator))
        {
            creator.Invoke(data);
        }
        else
        {
            // 默认创建逻辑
            GameObject markerObj = new GameObject("UnknownMarker");
            markerObj.transform.position = data.pos;
        }
    }
}

在派生类初始化时注册专属逻辑:

public class TextNodeParser : MonoBehaviour
{
    private void Awake()
    {
        MarkerFactory.RegisterCreator(MarkerType.Text, data =>
        {
            if (data is TextMarkerData textData)
            {
                GameObject textMarker = new GameObject("TextMarker");
                textMarker.transform.position = textData.pos;
                var textComp = textMarker.AddComponent<TextMeshPro>();
                textComp.text = textData.textContent;
                textComp.fontSize = textData.fontSize;
            }
        });
    }
}

此时NodeParser的CreateMarkerObject可简化为:

public virtual void CreateMarkerObject(MarkerData data)
{
    MarkerFactory.CreateMarker(data);
}

关键注意事项

  • 所有自定义数据类必须标记[Serializable],否则JsonUtility无法解析。
  • 若需更灵活的多态序列化,可考虑使用Newtonsoft.Json for Unity等第三方库,但原生类型标识方案已满足大多数需求。
  • 基类专注通用逻辑,派生类处理特定类型细节,避免代码重复。

内容的提问来源于stack exchange,提问作者Adam L.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:50:56