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\$
  • \$\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\$
  • \$\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\$

you should use Scriptable object

    using UnityEngine;
    using UnityEditor;
    using System.Collections;

[CustomEditor(typeof(TestManager))]
public class TestManagerEditor : Editor {
    #region Variables
    TestManager targetMgr;

    string path = "Assets/Database/Data.asset";
    #endregion

    #region Main Methods
    // Use this for initialization
    void OnEnable () {
        targetMgr = (TestManager)target;
        if(targetMgr.curClass == null){
            targetMgr.curClass = CreateClass ();
        }
    }

    // Update is called once per frame
    public override void OnInspectorGUI () {
        DrawDefaultInspector ();
        EditorUtility.SetDirty(targetMgr);
    }
    #endregion
    #region Utility Methods
    DataClass CreateClass(){
        DataClass curClass = (DataClass)ScriptableObject.CreateInstance (typeof(DataClass));
        if (curClass != null) {
            AssetDatabase.CreateAsset (curClass, path);
            AssetDatabase.Refresh ();
            AssetDatabase.SaveAssets ();
            return curClass;
        }else{
            return null;
        }
    }
    #endregion
}

using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[Serializable]
public class DataClass : ScriptableObject {
    #region Variables
    public Color myColor = Color.white;
    public float myFloat = 1.0f;
    public string curText = "Enter Text here..";
    public AnimationCurve animcurve;
    #endregion

    #region Private Variables
    #endregion

    #region Main Methods
    public void StartClass() {
        Debug.Log ("Started Class...");
    }
    public void UpdateClass() {
        Debug.Log ("Started Class...");
    }
    #endregion

    #region editor GUI
    #if UNITY_EDITOR
    public void OnDrawEditorGUI(){
        myFloat = EditorGUILayout.FloatField ("My Float: ", myFloat);
        myColor = EditorGUILayout.ColorField ("My Color: ", myColor);

        GUILayout.Space (10);

        curText = EditorGUILayout.TextArea (curText, GUILayout.Height (200));
    }

    #endif
    #endregion
    #region Utility Methods
    #endregion
}

using UnityEngine;
using System.Collections;
using System.Collections.Generic;


public class TestManager : MonoBehaviour {
    #region public Variables
    public static TestManager Instance;
    public DataClass curClass;
    #endregion


    #region Private Variables
    #endregion
    #region Main Methods
    // Use this for initialization
    void Awake () {
        Instance = this;
    }

    // Update is called once per frame
    void Update () {

    }
    #endregion
    void OnDrawGizmos(){

    }
    #region Utility Methods
    #endregion
}

Download this from Github

thanks I can solve my problem but I need attach scriptable object to my objects.because I want record all animaionCurve of objects. I can fix It: 1.I use array In scriptable object then animation curve of objects equal with it.

using UnityEngine;
using System.Collections;

public class Saver: MonoBehaviour {
    public AnimationCurve xPositionCurve;
    public DataClass curClass;
    void Start () {
        Vector3 localPosition = transform.position;
        localPosition.x = xPositionCurve.Evaluate (Time.time);
        transform.localPosition = localPosition;
    }

    void Update () {

        xPositionCurve.AddKey (Time.time, gameObject.transform.position.x);
        curClass.animcurve [0] = xPositionCurve;
    }
}
\$\endgroup\$
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\$

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.