GameObject saving

Easy Save 2 has been replaced by Easy Save 3, so is no longer supported.
Locked
Soulzr
Posts: 2
Joined: Wed Jul 20, 2016 9:30 am

GameObject saving

Post by Soulzr »

I know this a frequently question, i also know that this is not possible by saving the entire GameObject. But what i'm thinking is that many people should have resolved this same problem so they will have the answer. I would appreciate someone to show me how they did resolve this question. And i think it should be a sample of this in the tutorials. Thank you for the time helping me out.
User avatar
Joel
Moodkie Staff
Posts: 4849
Joined: Wed Nov 07, 2012 10:32 pm

Re: GameObject saving

Post by Joel »

Hi there,

I've created an example ES2Type for GameObject below which automatically saves any supported Component on the GameObject. It is only intended as an example and is provided without warranty, but seems to work well at our end.
using System.Collections.Generic;
using UnityEngine;

public sealed class ES2_GameObject : ES2Type
{
    public ES2_GameObject() : base(typeof(GameObject)) { }

    public override void Write(object data, ES2Writer writer)
    {
        GameObject go = (GameObject)data;
        // Get the Components of the GameObject that you want to save and save them.
        var components = go.GetComponents(typeof(Component));
        var supportedComponents = new List<Component>();

        // Get the supported Components and put them in a List.
        foreach (var component in components)
            if (ES2TypeManager.GetES2Type(component.GetType()) != null)
                supportedComponents.Add(component);

        // Write how many Components we're saving so we know when we're loading.
        writer.Write(supportedComponents.Count);

        // Save each Component individually.
        foreach (var component in supportedComponents)
        {
			var es2Type = ES2TypeManager.GetES2Type(component.GetType());
			writer.Write(es2Type.hash);
            es2Type.Write(component, writer);
        }
    }

    public override void Read(ES2Reader reader, object obj)
    {
        GameObject go = (GameObject)obj;
        // How many components do we need to load?
        int componentCount = reader.Read<int>();

        for (int i = 0; i < componentCount; i++)
		{
			// Get the ES2Type using the hash stored before the component.
			var es2Type = ES2TypeManager.GetES2Type(reader.Read<int>());
			// Get Component from GameObject, or add it if it doesn't have one.
			Component c = go.GetComponent (es2Type.type);
			if(c == null)
				c = go.AddComponent(es2Type.type);
			// Use the ES2Type to read.
			es2Type.Read(reader, c);
		}
    }

    public override object Read(ES2Reader reader)
    {
        GameObject go = new GameObject();
        Read(reader, go);
        return go;
    }
}
Also just a quick note that after you've added the ES2Type to your project, you need to go to Assets > Easy Save 2 > Manage Types and press Refresh ES2Init to add it to the type list.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Soulzr
Posts: 2
Joined: Wed Jul 20, 2016 9:30 am

Re: GameObject saving

Post by Soulzr »

Thank you for your time.
Do you have any ETA in that feature?
And how would you serialize child structure? Because i have complex structures. https://gyazo.com/c69339051f67f2dbd947d5ee7a3de208
User avatar
Joel
Moodkie Staff
Posts: 4849
Joined: Wed Nov 07, 2012 10:32 pm

Re: GameObject saving

Post by Joel »

There's no current ETA for that feature unfortunately, as there's other more highly requested features we need to work on before we can complete it.

With regards to saving a parent-child relationship, this can be tricky because Unity doesn't maintain it's own reference IDs. However, I recently described a method for doing this here: http://www.moodkie.com/forum/viewtopic.php?f=5&t=1018

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mruce
Posts: 5
Joined: Fri Dec 30, 2016 2:05 pm

Re: GameObject saving

Post by mruce »

Hi Joel,
I tried using above code, but it gives me errors. I tested it on simple GameObject with single component (BoxCollider). I'm getting error on deserialization:
(Exception) ArgumentException: GetComponent requires that the requested component 'UInt32' derives from MonoBehaviour or Component or is an interface.
ES2Reader.ReadComponent (UnityEngine.GameObject) <IL 0x0001b, 0x0012d>
(wrapper dynamic-method) object.MC<>(object,object[]) <IL 0x0000d, 0x000d0>
Slitghtly edited code I'm using:

Code: Select all

using System.Collections.Generic;
using UnityEngine;

public sealed class ES2_GameObject : ES2Type
{
    public ES2_GameObject() : base(typeof(GameObject)) { }

    public override void Write(object data, ES2Writer writer)
    {
        GameObject go = (GameObject)data;
        writer.Write(go.transform);


        var components = go.GetComponents(typeof(Component));
        var supportedComponents = new List<Component>();
        

        foreach (var component in components)
        {
            if (component == null) { Debug.Log("Component is null!"); continue; }
            if (component is Transform) continue;
            if (ES2TypeManager.GetES2Type(component.GetType()) == null) { Debug.Log("Type {0} is not supported!".Fmt(component.GetType())); continue; }

            Debug.Log("Adding type {0}".Fmt(component.GetType()));
            supportedComponents.Add(component);
        }

        Debug.Log("Writing {0} components".Fmt(supportedComponents.Count));
        writer.Write(supportedComponents.Count);        
        foreach (var component in supportedComponents)
            writer.Write(component);
    }

    public override void Read(ES2Reader reader, object c)
    {
        GameObject go = (GameObject)c;

        Debug.Log("Reading Transform");
        reader.Read(go.transform);

        int componentCount = reader.Read<int>();
        Debug.Log("Nr of components to read: {0}".Fmt(componentCount));
        
        for (int i = 0; i < componentCount; i++)
        {
            Debug.Log("Reading component {0}".Fmt(i));
            reader.ReadComponent(go);
        }
    }

    public override object Read(ES2Reader reader)
    {
        GameObject go = new GameObject();
        Read(reader, go);
        return go;
    }
}

Any idea why doesn't it work?
User avatar
Joel
Moodkie Staff
Posts: 4849
Joined: Wed Nov 07, 2012 10:32 pm

Re: GameObject saving

Post by Joel »

Hi there,

The example indeed does not work as the LoadComponent method now expects a partial header. I've modified my example above so that it writes a header, and then skips to the correct part of the header.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mruce
Posts: 5
Joined: Fri Dec 30, 2016 2:05 pm

Re: GameObject saving

Post by mruce »

Thanks Joel,
what does 'c' in

Code: Select all

writer.WriteHeader("c", ES2Keys.Key._Null, ES2TypeManager.GetES2Type(component.GetType()), null);
stands for? Why 'c' ?
User avatar
Joel
Moodkie Staff
Posts: 4849
Joined: Wed Nov 07, 2012 10:32 pm

Re: GameObject saving

Post by Joel »

It's just a placeholder tag. What we use as the tag is not important as long as it's not empty, but it needs to be there.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
mruce
Posts: 5
Joined: Fri Dec 30, 2016 2:05 pm

Re: GameObject saving

Post by mruce »

Thanks Joel, that helped. Different error showed up though. I can now serialize and deserialize gameobject properly - but when trying to overwrite it it throws
(Exception) ES2InvalidDataException: Easy Save 2 Error: The file provided does not contain data that is readable by Easy Save. Please make sure that file was created by Easy Save.
ES2Reader.Next () <IL 0x0006a, 0x00278>
ES2Reader.DeleteTags (System.Collections.Generic.ICollection`1<string>,ES2Writer) <IL 0x000c6, 0x00529>
ES2Writer.Delete () <IL 0x00014, 0x000ad>
ES2Writer.Save (bool) <IL 0x00017, 0x000b7>
ES2Writer.Save () <IL 0x00002, 0x00051>
ES2.Save<UnityEngine.GameObject> (UnityEngine.GameObject,strin
...
Code I'm using:

Code: Select all

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

public sealed class ES2_GameObject : ES2Type
{
    public ES2_GameObject() : base(typeof(GameObject)) { }

    public override void Write(object data, ES2Writer writer)
    {
        GameObject go = (GameObject)data;
        var serializableComponents = GetSerializableComponents(go);

        writer.Write(go.name);
        writer.Write(serializableComponents.Count);

        foreach (var component in serializableComponents)
        {
            Debug.Log("Writing {0}".Fmt(component.GetType()));

            writer.WriteHeader("component", ES2Keys.Key._Null, ES2TypeManager.GetES2Type(component.GetType()), null);
            writer.Write(component);
        }
        Debug.Log("Finished writing {0}".Fmt(go.name));
    }

    private static List<Component> GetSerializableComponents(GameObject go)
    {
        var components = go.GetComponents(typeof(Component)).ToList();
        for (int i = components.Count - 1; i >= 0; i--)
        {
            var component = components[i];
            if (ES2TypeManager.GetES2Type(component.GetType()) == null)
            {
                Debug.Log("Type {0} is not supported!".Fmt(component.GetType()));
                components.Remove(component);
                continue;
            }
        }

        return components;
    }

    public override void Read(ES2Reader reader, object c)
    {
        GameObject go = (GameObject)c;
        go.name = reader.Read<string>();
        int componentCount = reader.Read<int>();
        Debug.Log("Nr of components to read: {0}".Fmt(componentCount));

        for (int i = 0; i < componentCount; i++)
        {
            Debug.Log("Reading component {0}".Fmt(i));
            reader.Read<byte>();
            reader.Read<string>();
            reader.Read<int>();
            reader.ReadComponent(go);
        }
    }

    public override object Read(ES2Reader reader)
    {
        GameObject go = new GameObject();
        Read(reader, go);
        return go;
    }
}
And

Code: Select all

[Show]
    private void Serialize()
    {
        ES2.Save(gO, "TEST");
    }

    [Show]
    private void DeSerialize()
    {
        ES2.Load<GameObject>("TEST");
    }
Happy new year btw!
User avatar
Joel
Moodkie Staff
Posts: 4849
Joined: Wed Nov 07, 2012 10:32 pm

Re: GameObject saving

Post by Joel »

Happy New Year to you too!

I think I've been trying to hard to make it read the standard header, when actually we could just write the hash of the ES2Type (essentially creating our own header). And then instead of using ES2.ReadComponent, we simply mirror some of the functionality of ES2.ReadComponent ourselves.

I've updated the ES2Type above to work this way, and can confirm it's working for overwrites too.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Locked