如何在运行时中存储或读取动画剪辑数据? [英] How can i store or read a animation clip data in runtime?

查看:141
本文介绍了如何在运行时中存储或读取动画剪辑数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个可以在运行时修改动画的小程序(例如,当您运行得更快时,动画不仅播放速度更快,而且移动幅度更大)。因此,我需要获取现有动画,更改其值,然后将其发送回。

I'm working on a small program that can modify the animation at run time(Such as when you run faster the animation not only play faster but also with larger movement). So i need to get the existing animation, change its value, then send it back.

我发现我可以为动画设置新曲线很有趣,但是我无法访问已经拥有的内容。所以我要么写一个文件来存储动画曲线(例如文本文件),要么在启动时找到某种方式来读取动画。

I found it is interesting that i can set a new curve to the animation, but i can't get access to what i already have. So I either write a file to store my animation curve (as text file for example), or i find someway to read the animation on start up.

我尝试使用

AnimationUtility.GetCurveBindings(AnimationCurve);

它在我的测试中有效,但是在某些页面上它说这是编辑器代码,如果我将项目构建为独立程序,它将无法正常工作。真的吗?如果是这样,有什么方法可以在运行时获得曲线吗?

It worked in my testing, but in some page it says this is a "Editor code", that if i build the project into a standalone program it will not work anymore. Is that true? If so, is there any way to get the curve at run time?

感谢本杰明·扎克(Benjamin Zach)的澄清,以及TehMightyPotato
的建议,我想保持有关在运行时修改动画的想法。因为它可以适应imo的更多情况。

Thanks to the clearify from Benjamin Zach and suggestion from TehMightyPotato I'd like to keep the idea about modifying the animation at runtime. Because it could adapt to more situations imo.

我目前的想法是编写一段编辑器代码,该代码可以从编辑器中的曲线读取并输出所有有关的必要信息。将曲线(关键帧)转换为文本文件。然后在运行时读取该文件并创建新曲线以覆盖现有曲线。我将让这个问题待几天,然后检查它,看看是否有人对此有更好的想法。

My idea for now is to write a piece of editor code that can read from the curve in Editor and output all necesseary information about the curve (keyframes) into a text file. Then read that file at runtime and create new curve to overwrite the existing one. I will leave this question open for a few days and check it to see if anyone has a better idea about it.

推荐答案

已经说过 AnimationUtility 属于 UnityEditor 命名空间。整个名称空间在构建中被完全删除,最终应用程序中将无法使用它,但只能在Unity编辑器中使用。

As said already AnimationUtility belongs to the UnityEditor namespace. This entire namespace is completely stripped of in a build and nothing in it will be available in the final app but only within the Unity Editor.

为了将所有需要的信息存储到文件中,您可以使用脚本来一次序列化特定的动画曲线在编辑器中,然后使用例如 BinaryFormatter.Serialize 。然后稍后在运行时,您可以使用 BinaryFormatter.Deserialize 用于再次返回信息列表。

In order to store all needed information to a file you could have a script for once serializing your specific animation curve(s) in the editor before building using e.g. BinaryFormatter.Serialize. Then later on runtime you can use BinaryFormatter.Deserialize for returning the info list again.

如果您希望它更可编辑,则可以使用例如 JSON XML 当然

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class AnimationCurveManager : MonoBehaviour
{
    [Serializable]
    public sealed class ClipInfo
    {
        public int ClipInstanceID;
        public List<CurveInfo> CurveInfos = new List<CurveInfo>();

        // default constructor is sometimes required for (de)serialization
        public ClipInfo() { }

        public ClipInfo(Object clip, List<CurveInfo> curveInfos)
        {
            ClipInstanceID = clip.GetInstanceID();
            CurveInfos = curveInfos;
        }
    }

    [Serializable]
    public sealed class CurveInfo
    {
        public string PathKey;

        public List<KeyFrameInfo> Keys = new List<KeyFrameInfo>();
        public WrapMode PreWrapMode;
        public WrapMode PostWrapMode;

        // default constructor is sometimes required for (de)serialization
        public CurveInfo() { }

        public CurveInfo(string pathKey, AnimationCurve curve)
        {
            PathKey = pathKey;

            foreach (var keyframe in curve.keys)
            {
                Keys.Add(new KeyFrameInfo(keyframe));
            }

            PreWrapMode = curve.preWrapMode;
            PostWrapMode = curve.postWrapMode;
        }
    }

    [Serializable]
    public sealed class KeyFrameInfo
    {
        public float Value;
        public float InTangent;
        public float InWeight;
        public float OutTangent;
        public float OutWeight;
        public float Time;
        public WeightedMode WeightedMode;

        // default constructor is sometimes required for (de)serialization
        public KeyFrameInfo() { }

        public KeyFrameInfo(Keyframe keyframe)
        {
            Value = keyframe.value;
            InTangent = keyframe.inTangent;
            InWeight = keyframe.inWeight;
            OutTangent = keyframe.outTangent;
            OutWeight = keyframe.outWeight;
            Time = keyframe.time;
            WeightedMode = keyframe.weightedMode;
        }
    }

    // I know ... singleton .. but what choices do we have? ;)
    private static AnimationCurveManager _instance;
    public static AnimationCurveManager Instance
    {
        get
        {
            // lazy initialization/instantiation
            if(_instance) return _instance;

            _instance = FindObjectOfType<AnimationCurveManager>();

            if(_instance) return _instance;

            _instance = new GameObject("AnimationCurveManager").AddComponent<AnimationCurveManager>();

            return _instance;
        }
    }

    // Clips to manage e.g. reference these via the Inspector
    public List<AnimationClip> clips = new List<AnimationClip>();

    // every animation curve belongs to a specific clip and 
    // a specific property of a specific component on a specific object
    // for making this easier lets simply use a combined string as key
    private string CurveKey(string pathToObject, Type type, string propertyName)
    {
        return $"{pathToObject}:{type.FullName}:{propertyName}";
    }

    public List<ClipInfo> ClipCurves = new List<ClipInfo>();

    private void Awake()
    {
        if(_instance && _instance != this)
        {
             Debug.LogWarning("Multiple Instances of AnimationCurveManager! Will ignore this one!", this);
             return;
        }

        _instance = this;

        DontDestroyOnLoad(gameObject);

        // load infos on runtime
        LoadClipCurves();
    }

#if UNITY_EDITOR
    // Call this from the ContextMenu (or later via editor script)
    [ContextMenu("Save Animation Curves")]
    private void SaveAnimationCurves()
    {
        ClipCurves.Clear();

        foreach (var clip in clips)
        {
            var curveInfos = new List<CurveInfo>();
            ClipCurves.Add(new ClipInfo(clip, curveInfos));

            foreach (var binding in AnimationUtility.GetCurveBindings(clip))
            {
                var key = CurveKey(binding.path, binding.type, binding.propertyName);
                var curve = AnimationUtility.GetEditorCurve(clip, binding);

                curveInfos.Add(new CurveInfo(key, curve));
            }
        }

        // create the StreamingAssets folder if it does not exist
        try
        {
            if (!Directory.Exists(Application.streamingAssetsPath))
            {
                Directory.CreateDirectory(Application.streamingAssetsPath);
            }
        }
        catch (IOException ex)
        {
            Debug.LogError(ex.Message);
        }

        // create a new file e.g. AnimationCurves.dat in the StreamingAssets folder
        var fileStream = new FileStream(Path.Combine(Application.streamingAssetsPath, "AnimationCurves.dat"), FileMode.Create);

        // Construct a BinaryFormatter and use it to serialize the data to the stream.
        var formatter = new BinaryFormatter();
        try
        {
            formatter.Serialize(fileStream, ClipCurves);
        }
        catch (SerializationException e)
        {
            Debug.LogErrorFormat(this, "Failed to serialize. Reason: {0}", e.Message);
        }
        finally
        {
            fileStream.Close();
        }

        AssetDatabase.Refresh();
    }
#endif

    private void LoadClipCurves()
    {
        var filePath = Path.Combine(Application.streamingAssetsPath, "AnimationCurves.dat");

        if (!File.Exists(filePath))
        {
            Debug.LogErrorFormat(this, "File \"{0}\" not found!", filePath);
            return;
        }

        var fileStream = new FileStream(filePath, FileMode.Open);

        try
        {
            var formatter = new BinaryFormatter();

            // Deserialize the hashtable from the file and 
            // assign the reference to the local variable.
            ClipCurves = (List<ClipInfo>)formatter.Deserialize(fileStream);
        }
        catch (SerializationException e)
        {
            Debug.LogErrorFormat(this, "Failed to deserialize. Reason: {0}", e.Message);
        }
        finally
        {
            fileStream.Close();
        }
    }

    // now for getting a specific clip's curves
    public AnimationCurve GetCurve(AnimationClip clip, string pathToObject, Type type, string propertyName)
    {
        // either not loaded yet or error -> try again
        if (ClipCurves == null || ClipCurves.Count == 0) LoadClipCurves();
        // still null? -> error
        if (ClipCurves == null || ClipCurves.Count == 0)
        {
            Debug.LogError("Apparantly no clipCurves loaded!");
            return null;
        }

        var clipInfo = ClipCurves.FirstOrDefault(ci => ci.ClipInstanceID == clip.GetInstanceID());

        // does this clip exist in the dictionary?
        if (clipInfo == null)
        {
            Debug.LogErrorFormat(this, "The clip \"{0}\" was not found in clipCurves!", clip.name);
            return null;
        }

        var key = CurveKey(pathToObject, type, propertyName);

        var curveInfo = clipInfo.CurveInfos.FirstOrDefault(c => string.Equals(c.PathKey, key));

        // does the curve key exist for the clip?
        if (curveInfo == null)
        {
            Debug.LogErrorFormat(this, "The key \"{0}\" was not found for clip \"{1}\"", key, clip.name);
            return null;
        }

        var keyframes = new Keyframe[curveInfo.Keys.Count];

        for (var i = 0; i < curveInfo.Keys.Count; i++)
        {
            var keyframe = curveInfo.Keys[i];

            keyframes[i] = new Keyframe(keyframe.Time, keyframe.Value, keyframe.InTangent, keyframe.OutTangent, keyframe.InWeight, keyframe.OutWeight)
            {
                weightedMode = keyframe.WeightedMode
            };
        }

        var curve = new AnimationCurve(keyframes)
        {
            postWrapMode = curveInfo.PostWrapMode,
            preWrapMode = curveInfo.PreWrapMode
        };

        // otherwise finally return the AnimationCurve
        return curve;
    }
}

然后您可以执行类似ee的操作

Then you can do something like e.e.

AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
    clip, 
    "some/relative/GameObject", 
    typeof<SomeComponnet>, 
    "somePropertyName"
);

第二个参数 pathToObject 是一个空字符串如果属性/组件附加到根对象本身。否则,它会像Unity一样照常在Unity的层次结构路径中给出

the second parameter pathToObject is an empty string if the property/component is attached to the root object itself. Otherwise it is given in the hierachy path as usual for Unity like e.g. "ChildName/FurtherChildName".

现在,您可以更改值并在运行时分配新曲线。

Now you can change the values and assign a new curve on runtime.

在运行时,您可以使用 animator.runtimeanimatorController 以便检索 RuntimeAnimatorController 参考。

On runtime you can use animator.runtimeanimatorController in order to retrieve a RuntimeAnimatorController reference.

它具有属性 animationClips ,它返回所有 AnimationClip 分配给该控制器。

It has a property animationClips which returns all AnimationClips assigned to this controller.

然后可以使用例如 Linq FirstOrDefault ,以便按名称查找特定的 AnimationClip ,最后使用 AnimationClip.SetCurve 为特定的组件和属性分配新的动画曲线。

You could then use e.g. Linq FirstOrDefault in order to find a specific AnimationClip by name and finally use AnimationClip.SetCurve to assign a new animation curve to a certain component and property.

例如像

// you need those of course
string clipName;
AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
    clip, 
    "some/relative/GameObject", 
    typeof<SomeComponnet>, 
    "somePropertyName"
);

// TODO 
AnimationCurve newCurve = SomeMagic(originalCurve);

// get the animator reference
var animator = animatorObject.GetComponent<Animator>();
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips
var clips = controller.animationClips;
// find the specific clip by name
// alternatively you could also get this as before using a field and
// reference the according script via the Inspector 
var someClip = clips.FirstOrDefault(clip => string.Equals(clipName, clip.name));

// was found?
if(!someClip)
{
    Debug.LogWarningFormat(this, "There is no clip called {0}!", clipName);
    return;
}

// assign a new curve
someClip.SetCurve("relative/path/to/some/GameObject", typeof(SomeComponnet), "somePropertyName", newCurve);






注意:在智能手机上键入,因此没有保证!但我希望这个主意能弄清楚...

也请查看 AnimationClip.SetCurve →您可能要使用 Animation 组件,而不是特定用例中的 Animator

这篇关于如何在运行时中存储或读取动画剪辑数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆