0
\$\begingroup\$

Is there any way to save an animation curve by playerprefs ? The animation curves in the inspector reset each time the script is changed (public variables added) I want record my animation In runtime as animation curve then I load It next time(same playerprefs I want save/load it)

using UnityEngine;
using System.Collections;

public class curvRecord : MonoBehaviour {
    public AnimationCurve xPositionCurve;
    public float timer = 0;
    public float recorder = 0;
    public bool play;
    public bool Reset;
    public bool record;


    void Update () {
        if (Reset && !play && ! record) {
            xPositionCurve.RemoveKey (5);
            play = false;
            recorder = 0;
            timer = 0;
        }
        if(play && timer<=recorder){
            record = false;  Reset = false;
            Vector3 localPosition = transform.position;
            localPosition.x = xPositionCurve.Evaluate (timer);
            transform.localPosition = localPosition;

            // Increase the timer by the time since last frame
            timer += Time.deltaTime;

        } else if(!play && record) {
            Reset = false;
            recorder += Time.deltaTime;
            xPositionCurve.AddKey (recorder, gameObject.transform.position.x);
        }
    }
}

I can record and play my curve animation but when I go exit from game reset curve animation.when I start game again I want load last curve animation enter image description here

You can see my problem in this video: videoLink

I have to use playerprefsX but when I use It in runtime I have lag In my game please tell me what Is easiest way?!?!?! for saving animation curves using UnityEngine; using System.Collections;

public class curvRecord : MonoBehaviour {
    [SerializeField]
    public AnimationCurve xPositionCurve;
    public AnimationCurve yPositionCurve;
    public AnimationCurve zPositionCurve;
    public float timer = 0;
    public float recorder = 0;
    public bool record;
    public bool play;
    public bool Reset;
    public float[] CurveFrameValue;
    public bool OnceRunSaving;
    public bool OnceRunLoading;
    Keyframe[] ks = new Keyframe[500];

    void Start () {


    }

    void Saver(){
        for (int m = 0; m < CurveFrameValue.Length; m++) {
            CurveFrameValue [m] = xPositionCurve [m].value;
            PlayerPrefsX.SetFloatArray ("xpos", CurveFrameValue);
            print (m);
            }
    }

    // Update is called once per frame
    void Update () {
        if(OnceRunLoading){
            for (int n = 0; n < CurveFrameValue.Length; n++) {
                CurveFrameValue = PlayerPrefsX.GetFloatArray ("xpos");
                ks[n] = new Keyframe(n, CurveFrameValue[n]);
                xPositionCurve = new AnimationCurve(ks);
            }
            transform.position = new Vector3(Time.time, xPositionCurve.Evaluate(Time.time), 0);
            OnceRunLoading = false;
        }
        if (Reset && !play && !record) {
            xPositionCurve.RemoveKey (50);
            yPositionCurve.RemoveKey (50);
            zPositionCurve.RemoveKey (50);
            play = false;
            recorder = 0;
            timer = 0;
        }
        if(play && timer<=recorder){
            record = false;  Reset = false;
            Vector3 localPosition = transform.position;
            localPosition.x = xPositionCurve.Evaluate (timer);
            localPosition.y = yPositionCurve.Evaluate (timer);
            localPosition.z = zPositionCurve.Evaluate (timer);
            transform.localPosition = localPosition;

            // Increase the timer by the time since last frame
            timer += Time.deltaTime;
        } else if(!play && record) {
            timer = 0;
            Reset = false;
            recorder += Time.deltaTime;
            xPositionCurve.AddKey (recorder, gameObject.transform.position.x);
            yPositionCurve.AddKey (recorder, gameObject.transform.position.y);
            zPositionCurve.AddKey (recorder, gameObject.transform.position.z);
            if(!OnceRunSaving){
                Saver ();
                OnceRunSaving = true;
            }
        }else if(play && timer>=recorder){

            // Increase the timer by the time since last frame
            timer = 0;
        }
        else if(play && record) {
            Reset = true;
        }
        else if(Reset) {
            Reset = true;
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

If you go to the definition of AnimationCurve (F12 in VS), you'll see that it is not marked [System.Serializable] and no amount of coaxing will change that.

When you change your code and go back to the editor, everything that was there is Unity.Serialized() (don't look for it; not a real thing) to native, the new managed code is loaded, and then the old values are Unity.Deserialized() back into the new data structure blindly. If you rename something, for example, the editor won't know to put the old data in the new place so, instead, it just throws away the old-data and creates new, new-data.

Your only recourse is to provide the methods needed to System.De/Serialize the non-serializable curves. You can do so by iterating each KeyFrame in the curve and manually exporting the data somewhere, some way. You'll also need to provide the method that converts that raw data back into an AnimationCurve that appears to be the same one as before.

ScriptableObject is not meant for persistent storage of run-time-generated data. They allow you to create design-time "assets" out of chunks of data that scripts can reference, rather than contain/copy. You are free to create new ScriptableObjects at run-time and you are able to permanently store them in the asset database. However (big but), this depends on the Editor namespace which doesn't exist in builds. If you use them just to help design the game, you're still required to provide the implementation to make them System.Serializable (they are also only Unity.Serializable). In case you consider doing so, you cannot simply add [System.Serializable] to a ScriptableObject because the base class (UnityEngine.Object) still won't be [System.Serializable].

Instead, the object that contains the curves can implement the ISerializationCallbackReceiver interface and use the corresponding methods to extract the KeyFrames into a serializable form. You can make a new SerializableKeyFrame class that contains the same names and types as the real KeyFrame class except they'll be stored as members, not as properties. The SerializableKeyFrame class will need to be marked [System.Serializable] to be eligible for serialization. As well, the for-serialization-only member within the curve-containing object (presumably a List<SerializableKeyFrame> will need to be public or marked [SerializeField]. You can leave it public and hide it with [HideInInspector]. You are free to use a struct instead of a class but are subject to the normal drawbacks (no default values for members) which may not really affect you.

Overview:

You:

  1. Save code; go back to editor

Then, Unity:

  1. Unity-serializes everything to native
    • (The callback) Deconstruct the AnimationCurve into the for-serialization-only member (composed only of System.Serializable types)
  2. Load the new Managed assemblies
  3. Unity-deserialize from native into new Managed
    • (The callback) Reconstruct the AnimationCurve's data using the data in the for-serialization-only member
\$\endgroup\$
1
\$\begingroup\$

You cant save complex types in PlayerPrefs. You can only save floats,ints and strings. I suggest you search online for a way to serialize the AnimationCurve object. Maybe you can use a binnary formatter like here. In one of my projects i used ProtoBuff to serialize my own classes and it works great on both android and iOS. Here is a nifty tutorial

\$\endgroup\$
2
  • \$\begingroup\$ thanks but I tested [SerializeField] but don't save it? I add [SerializeField] top of my code same tomodomo.org/animation-curves-and-unity but don't work.Is It possible you correct my code \$\endgroup\$ – Seyed Morteza Kamali Apr 13 '16 at 15:18
  • \$\begingroup\$ simply addig [SerializeField] will not save your curve. If you share any code you have written I can take a look at it. \$\endgroup\$ – Uri Popov Apr 14 '16 at 7:31
1
\$\begingroup\$

You can do this by storing the AnimationCurve variable in a ScriptableObject. Unlike MonoBehaviours that are attached to GameObjects, ScriptableObjects are created as scene independent assets, so they're not reset when exiting playmode.

\$\endgroup\$
2
  • \$\begingroup\$ this way is hard. if i want save curve value by playerprefs I have to use array because each frame need save playerprefs("Array?!",position.x); ____playerprefs("Array?!",position.y);___ playerprefs("Array?!",position.z); I tested this hard way and I can save animation curve but I found easy way.next way Is use Don'tDestroy function but I want when I exit my game save It. \$\endgroup\$ – Seyed Morteza Kamali Apr 15 '16 at 6:40
  • \$\begingroup\$ thanks a lot !!!! I can fix my problem github.com/smkplus/CurveAnimationStoreInRuntime \$\endgroup\$ – Seyed Morteza Kamali Apr 19 '16 at 11:58
0
\$\begingroup\$

I have done this! Didn't try the ScriptableObject solution since I read that it might not work in production mode. Instead I created a SerializableAnimationCurve and SerializableKeyframe:

using System;
using UnityEngine;

[Serializable]
public class SerializableCurve
{
    SerializableKeyframe[] keys;
    string postWrapMode;
    string preWrapMode;

    [Serializable]
    public class SerializableKeyframe
    {
        public Single inTangent;
        public Single outTangent;
        public Int32 tangentMode;
        public Single time;
        public Single value;

        public SerializableKeyframe(Keyframe original) {
            inTangent = original.inTangent;
            outTangent = original.outTangent;
            tangentMode = original.tangentMode;
            time = original.time;
            value = original.value;
        }
    }

    public SerializableCurve(AnimationCurve original) {
        postWrapMode = getWrapModeAsString(original.postWrapMode);
        preWrapMode = getWrapModeAsString(original.preWrapMode);
        keys = new SerializableKeyframe[original.length];
        for (int i = 0; i < original.keys.Length; i++) {
            keys[i] = new SerializableKeyframe(original.keys[i]);
        }
    }

    public AnimationCurve toCurve() {
        AnimationCurve res = new AnimationCurve();
        res.postWrapMode = getWrapMode(postWrapMode);
        res.preWrapMode = getWrapMode(preWrapMode);
        Keyframe [] newKeys = new Keyframe[keys.Length];
        for (int i = 0; i < keys.Length; i++) {
            SerializableKeyframe aux = keys[i];
            Keyframe newK = new Keyframe();
            newK.inTangent = aux.inTangent;
            newK.outTangent = aux.outTangent;
            newK.tangentMode = aux.tangentMode;
            newK.time = aux.time;
            newK.value = aux.value;
            newKeys[i] = newK;
        }
        res.keys = newKeys;
        return res;
    }

    private WrapMode getWrapMode(String mode) {
        if (mode.Equals("Clamp")) {
            return WrapMode.Clamp;
        }
        if (mode.Equals("ClampForever")) {
            return WrapMode.ClampForever;
        }
        if (mode.Equals("Default")) {
            return WrapMode.Default;
        }
        if (mode.Equals("Loop")) {
            return WrapMode.Loop;
        }
        if (mode.Equals("Once")) {
            return WrapMode.Once;
        }
        if (mode.Equals("PingPong")) {
            return WrapMode.PingPong;
        }
        Debug.LogError("Wat is this wrap mode???");
        return WrapMode.Default;
    }

    private string getWrapModeAsString(WrapMode mode) {
        if (mode.Equals(WrapMode.Clamp)) {
            return "Clamp";
        }
        if (mode.Equals(WrapMode.ClampForever)) {
            return "ClampForever";
        }
        if (mode.Equals(WrapMode.Default)) {
            return "Default";
        }
        if (mode.Equals(WrapMode.Loop)) {
            return "Loop";
        }
        if (mode.Equals(WrapMode.Once)) {
            return "Once";
        }
        if (mode.Equals(WrapMode.PingPong)) {
            return "PingPong";
        }
        Debug.LogError("Wat is this wrap mode???");
        return "f you";
    }
}

In order to use it, create this Serializer class. Be aware that with this you can serialize any unity component, you just gotta tweak it a little

using System;
using System.IO;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;

public class Serializer
{
    public static T Load<T>(string filename) where T : class {
        if (File.Exists(filename)) {
            try {
                using (Stream stream = File.OpenRead(filename)) {
                    BinaryFormatter formatter = new BinaryFormatter();
                    return formatter.Deserialize(stream) as T;
                }
            }
            catch (Exception e) {
                Debug.Log(e.Message);
            }
        }
        return default(T);
    }

    public static void Save<T>(string filename, T data) where T : class {
        using (Stream stream = File.OpenWrite(filename)) {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, data);
        }
    }
}
\$\endgroup\$
0
\$\begingroup\$

If you check "Runtime Curve Editor" on Unity Asset Store, not just that you can load/save an animation curve, but you can even edit it at runtime.

\$\endgroup\$
0
\$\begingroup\$

The simple way:

Enter Play Mode -> record the desired curve -> click the cogwheel on your script -> "copy component". Exit Play Mode -> "paste component values".

\$\endgroup\$
0
\$\begingroup\$

enter image description here

Creating Animation Data Scriptable Object

    using UnityEngine;
    
    [CreateAssetMenu(fileName = "new AnimationData",menuName = "Data/AnimationData")]
    public class AnimationData : ScriptableObject
    {
        public AnimationCurve animationCurve;
    
        public void AddKey(Keyframe keyframe)
        {
            animationCurve.AddKey(keyframe);
        }
    
        public float GetKeyAt(float time)
        {
            return animationCurve.Evaluate(time);
        }
    }

Recording Animation

using UnityEngine;

public class RecordAnimation : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private bool isRecording;
    [SerializeField] private AnimationData[] animationData;

    private void Update()
    {
        if (isRecording)
        {
            RecordPosition();
        }
        else
        {
            LoadPosition();
        }
    }

    private void RecordPosition()
    {
        var position = target.position;
        animationData[0].AddKey(new Keyframe(Time.time, position.x));
        animationData[1].AddKey(new Keyframe(Time.time, position.y));
        animationData[2].AddKey(new Keyframe(Time.time, position.z));
    }

    private void LoadPosition()
    {
        target.position = new Vector3(
            animationData[0].GetKeyAt(Time.time),
            animationData[1].GetKeyAt(Time.time),
            animationData[2].GetKeyAt(Time.time)
        );
    }
}

Source is Available on my Github

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.