Unity杂文——序列化AnimationCurve

  1. 问题
  2. 解决方案
  3. 总结

原文地址

问题

在开发地图编辑器时,需要配置摄像机的移动速度,策划提出了通过设置曲线的方式来设置摄像机的移动。笔者想到了使用Unity编辑器自带的AnimationCurve来获得运动曲线。但是,由于需要将配置保存下来,笔者选择了使用JSON格式进行保存。然而,JSON序列化无法序列化AnimationCurve对象。在这种情况下,我们可以考虑将AnimationCurve对象转换为可序列化的数据类型,例如数组或自定义类,然后再进行JSON序列化。这样就可以保存配置信息并在需要时重新加载。

解决方案

如果您需要序列化AnimationCurve,但是无法直接序列化,可以通过保存Keyframe数组来实现。在保存时,只需要将Keyframe数组保存下来,然后在加载时,通过添加保存的Keyframe来生成所需的AnimationCurve。
然而,Keyframe的字段都是私有的,无法直接序列化。为了解决这个问题,我们可以自定义一个新的Keyframe类,然后通过自己的Keyframe来生成需要的Keyframe。
以下是一个示例代码,演示如何自定义Keyframe类并使用它来生成AnimationCurve:
通过查看keyframe的源码,我们可以摘取我们需要的数据,然后自定义类即可。(这里定义struct,打包的时候序列化会出错,具体原因还未查明)

public class LBSerializeAnimCurveKeyFrame
{
    #region 字段

    public float m_Time;
    public float m_Value;
    public float m_InTangent;
    public float m_OutTangent;
    public int m_TangentMode;
    public int m_WeightedMode;
    public float m_InWeight;
    public float m_OutWeight;

    #endregion
    
    #region 属性

    

    #endregion
    
    #region 方法
    
    public LBSerializeAnimCurveKeyFrame(
        float time,
        float value,
        float inTangent,
        float outTangent,
        float inWeight,
        float outWeight)
    {
        this.m_Time = time;
        this.m_Value = value;
        this.m_InTangent = inTangent;
        this.m_OutTangent = outTangent;
        this.m_WeightedMode = 3;
        this.m_InWeight = inWeight;
        this.m_OutWeight = outWeight;
        this.m_TangentMode = 0;
    }

    public static implicit operator LBSerializeAnimCurveKeyFrame(Keyframe keyframe)
    {
        return new LBSerializeAnimCurveKeyFrame(keyframe);
    }
    
    public static implicit operator Keyframe(LBSerializeAnimCurveKeyFrame keyframe)
    {
        return new Keyframe(keyframe.m_Time, keyframe.m_Value, keyframe.m_InTangent, keyframe.m_OutTangent,
            keyframe.m_InWeight, keyframe.m_OutWeight);
    }
    
    #endregion
}

上面就是笔者自己定义的keyframe,然后我们只需要在我们需要序列化的类添加上即可,示例如下:
首先查看我们需要序列化的数据应该怎么设计:

public class SerializeAnimationCurveData
{
    #region 字段
    
    public List<LBSerializeAnimCurveKeyFrame> m_AnimCurveKeyFrameList;
    
    [IgnoreDataMember]
    public AnimationCurve AnimCurve
    {
        get
        {
            Keyframe[] keyframeArray;
            if (m_AnimCurveKeyFrameList != null)
            {
                keyframeArray = new Keyframe[m_AnimCurveKeyFrameList.Count];
                for (int i = 0; i < m_AnimCurveKeyFrameList.Count; i++)
                {
                    keyframeArray[i] = m_AnimCurveKeyFrameList[i];
                }
            }
            else
            {
                keyframeArray = AnimationCurve.Linear(0f, 0f, 1f, 1f).keys;
            }

            return new AnimationCurve(keyframeArray);
        }
        set
        {
            m_AnimCurveKeyFrameList.Clear();
            foreach (var keyframe in value.keys)
            {
                m_AnimCurveKeyFrameList.Add(keyframe);
            }
        }
    }

    #endregion

    #region 方法

    public SerializeAnimationCurveData()
    {
        AnimCurve = AnimationCurve.Linear(0, 0, 1, 1);
        m_AnimCurveKeyFrameList = new List<LBSerializeAnimCurveKeyFrame>();
        foreach (var keyframe in AnimCurve.keys)
        {
            m_AnimCurveKeyFrameList.Add(keyframe);
        }
    }

    #endregion
}

接着我们看一下如何编辑器上设置,笔者是在自定义的窗口上绘制的,其他面板也是类似:

private SerializeAnimationCurveData m_SerAnimCurveData;

private void Awake()
{
    m_SerAnimCurveData = new SerializeAnimationCurveData();
}

private void OnGUI()
{
    EditorGUILayout.BeginVertical(GUILayout.Width(400f));
    {
        m_SerAnimCurveData.AnimCurve =
            EditorGUILayout.CurveField(LBEditorLNG.SerAnimCurveTitle, m_SerAnimCurveData.AnimCurve);
    }
    EditorGUILayout.EndVertical();
}

总结

当我们想序列化一个字段的时候,如果字段不可以序列化,我们可以通过保留组成的数据,然后在反序列化的时候通过保留下来的数据重新组成我们需要的字段。如果字段也是封装好的私有字段,我们只需要自己重新设计一个类,用来保留这些字段。当我们需要一个成品的时候,如果我们没有办法保留成品,我们可以拆分这些,这样使用的时候重新组成就行了。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 841774407@qq.com

×

喜欢就点赞,疼爱就打赏