Loading a type from a saved file

I’m working on a game that features randomly generated environments of multiple biome types. I have a parent biome class with the biome types as children. When I’m saving the game world, I’m creating a single world save file using a save data class. At present, this contains a list of all generated biomes, their start/end coordinates, and their biome types (I’ve tried saving these as strings and as types). The saving seems to be going fine.

I can’t find a way to get the data to properly load back in. I’m creating a biome, then attempting to assign the saved type to that biome. I’ve tried using ‘Convert.ChangeType(BiomeData, Type)’ (along with ‘typeof’, etc.) and casting as Type (with ‘GetType()’). I’m trying to avoid using a bunch of ‘if’ statements (‘if biome==forest, b=new Forest()’, etc.) because that’s annoying to maintain when adding new biomes and not friendly assuming someone ever wants to mod my game.

How would I be able to recreate my saved biomes with variable class types?

If it’s an enum you can read it in like this:

string InputValue = "InputBiomeStringFromFile";

try   
{
    m_BiomeType = (eBiomeType)Enum.Parse( typeof(eBiomeType) , InputValue , true );
}
catch
{
    Debug.Log("Warning: eBiomeType not recognised - " + InputValue );
    m_BiomeType = eBiomeType.default;
}
1 Like

Not sure you’re aware that this topic is usually referred as “serialization of polymorphic data types”.
You can try 3rd party serializers (not necessarily easy path) but if not then idea so far is to stop trying to serialize polymorphic (inheritance/generic/interfaces) types because they wont be serialized at all or their types will be flattened to base types.

In short: Stop using inheritance for serialization subjects and you probably wont escape writing something along the lines of “if biome==forest, b=new Forest()'”.
Remember that you can separate biome data class from biome runtime class (runtime types can all be polymorphic where data type not at all).

On subject of modding. I don’t know how to extend this system so much so anyone can extend your codebase with new dll/cs types (and would love to hear from sb on that). But, if you let yourself imagine that maybe… maybe you don’t really need that (yet) - then all you need to make available for modders is those simple data fields to modify your built-in biomes/etc.

1 Like

Since I’m loading in class names, I don’t think an enum would really work. If the loaded string doesn’t match an existing class type, it will definitely throw an error.

Ah, good to know that I can’t store the class type itself; I can store it as a string, though, so I’ll have to take that approach. I can load in the biome data just fine, but I’m struggling to convert that string back into a class. I’ve tried creating a generic biome to store the data, then casting it using Type.GetType() on the string, which doesn’t seem to be working (I could be making a silly error, though, because I do that with some frequency). I’ve tried using Convert.ChangeType(Biome, Type), but that doesn’t seem to work even if I give it a specific type. I’ve been searching for other approaches, but can’t seem to find anything that can create an object of a variable type at runtime or convert a generic Biome into a different child type without defining the type in code.

The biome class defines how each biome is created (size and all that stuff) and has a generic ‘generate chunk’ function. Each child function has definitions for its unique traits as well as its own, individualized GenerateChunk, which handles terrain generation and creates biome-specific structures (trees in forest biomes, for example) whenever a new chunk of that biome is created. This makes it really quick and easy for me to add in new biomes and, if someone else wants to mod my game in the future, they just have to simply extend my biome class and add new subclasses. So while future modding IS a consideration, keeping the current structure as it is now and finding a way to recreate children of the Biome class from a saved world file will keep future coding much simpler, as well.

Is there a reason you aren’t using serialization?

Edit: I see you talk about it here
https://forum.unity.com/threads/how-to-script-items-abilities-for-a-nostalgic-rpg.449494/

It’s popular to use serialization for save files.
It’s popular to use ScriptableObjects for hard coded data that you need to access throughout your game.

If you want your items or abilities or whatever to be moddable at runtime, you could serialize those as well.

I don’t know about JSON specifically, but there are serializers that handle inheritance.

1 Like

Could you elaborate a little more?
Which kind of errors did you run into when you tried to get an actual System.Type from a string?

Objects can be created from a System.Type instance, but you should only do that when it’s really necessary. It’s very common for serializers to make use of that. Some of them use the System.Activator.CreateInstance method which will invoke the default constructor if available and accessible, some others even bypass constructors completely.

1 Like

I forgot all about that old thread that I started! I’m actually already using serialization to save not only my world data, but also my chunk and player data. I can store and access the data just fine, but, as Andrew pointed out, polymorphic data types are unserializable, and I can’t figure out a way to create a new object with a type determined by a variable string (without assigning each an if/then statement, which lacks efficiency and makes it annoying to expand upon).

I’ve got the loading in a try/catch, so I’m getting a stack trace, but it’s not giving me the actual error (I’m assuming I left something out of my catch; using them for debugging is a new thing for me, which is pretty sad considering how long I’ve been working with C languages. Is it ‘Exception e.message’ that gives me the actual error?) I’ll tinker with my error reporting tomorrow and get back to you on what the message is.

@richardmv I cracked this. This scheme serializes/deserializes (in limited capacity but still) polymorphic type biomes (no if/else anymore) and should work with external assemblies I think. @Suddoha was pointing you in good direction. Take a look:

Biome.cs

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

using UnityEngine;

namespace Vanilla
{
    public class Biome
    {
        #region FIELDS
 
        protected int[] data;

        #endregion
        #region CONSTRUCTORS

        public Biome ()
        {

        }
        protected Biome ( int[] data )
        {
            this.data = data;
        }

        #endregion
        #region PROTECTED METHODS

        protected virtual void OnRebuild ( Vector2Int pos )
        {

        }

        #endregion
        #region PUBLIC METHODS

        public void Rebuild ( Vector2Int pos )
        {
            OnRebuild( pos );
        }

        public Serialized Serialize ()
        {
            return new Serialized( this );
        }

        #endregion
        #region NESTED TYPES

        [System.Serializable]
        public class Serialized
        {
            //IMPORTANT: AFAIK, if your class will end up being in an assembly make sure it is reflected in this full name field:
            public string biomeTypeFullName;
            public int[] data;

            public Serialized ( Biome biome )
            {
                this.biomeTypeFullName = biome.GetType().FullName;
                this.data = biome.data;
            }

            public T Deserialize<T> () where T : Biome, new()
            {
                //get type:
                System.Type type = System.Type.GetType( this.biomeTypeFullName );
                if( type==null )
                {
                    throw new System.Exception( string.Format("System.Type.GetType( {0} ) returns null") );
                }

                //create object:
                System.Object obj = System.Activator.CreateInstance( type );
                if( obj==null )
                {
                    throw new System.Exception( string.Format("System.Activator.CreateInstance( {0} ) returns null",type));
                }
 
                //cast to biome type:
                var result = obj as T;
                if( result==null )
                {
                    throw new System.Exception( string.Format("Casting {0} to {1} returns null",obj.GetType(),typeof(T).ToString()));
                }

                return result;
            }
        }

        [System.Serializable]
        public class SerializedArray
        {
            #region fields
            public Serialized[] values;
            #endregion
            #region constructors
            public SerializedArray ( Serialized[] serialized )
            {
                this.values = serialized;
            }
            public SerializedArray ( Biome[] biomes )
            {
                int count = biomes.Length;
                this.values = new Serialized[ count ];
                for( int i=0 ; i<count ; i++ )
                {
                    this.values[i] = biomes[i].Serialize();
                }
            }
            public SerializedArray ( List<Biome> biomes )
            {
                int count = biomes.Count;
                this.values = new Serialized[ count ];
                for( int i=0 ; i<count ; i++ )
                {
                    this.values[i] = biomes[i].Serialize();
                }
            }
            #endregion
        }

        #endregion
    }
    public class Biome_Forest : Biome
    {
        //IMPORTANT: parameterless one or no constructors, required by  System.Activator.CreateInstance
        public Biome_Forest () {}
        public Biome_Forest ( int[] dat ) : base( dat )
        {
 
        }
        protected override void OnRebuild ( Vector2Int pos )
        {
 
        }
    }
}

Mod.cs

using UnityEngine;
namespace Mod
{
    public class Biome_MordorAndUnicorns : Vanilla.Biome
    {
        //IMPORTANT: parameterless one or no constructors, required by  System.Activator.CreateInstance
        public Biome_MordorAndUnicorns () {}
        public Biome_MordorAndUnicorns ( int[] dat ) : base( dat )
        {
 
        }
        protected override void OnRebuild ( Vector2Int pos )
        {

        }
    }
}

TestingPolymorphicSerializationScheme.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class TestingPolymorphicSerializationScheme : MonoBehaviour
{

    public List<Vanilla.Biome> biomes = new List<Vanilla.Biome>();

    public string json_file_somewhere;

#if UNITY_EDITOR
    [CustomEditor(typeof(TestingPolymorphicSerializationScheme))]
    class ThisEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            {
                var instance = (TestingPolymorphicSerializationScheme)target;
                {
                    EditorGUILayout.LabelField( "list of biomes runtime:" );
                    int count = instance.biomes.Count;
                    {
                        for( int i=0 ; i<count ; i++ )
                        {
                            var biome = instance.biomes[i];
                            EditorGUILayout.LabelField( string.Format("\t[{0}] {1}",i,biome.GetType()) );
                        }
                        if( count==0 )
                        {
                            EditorGUILayout.LabelField( string.Format("\t- none -") );
                        }
                    }
                }
                EditorGUILayout.BeginHorizontal();
                {
                    if( GUILayout.Button( "Serialize" ) )
                    {
                        var serializable = new Vanilla.Biome.SerializedArray( instance.biomes );
                        instance.json_file_somewhere = JsonUtility.ToJson( serializable );
                    }
                    if( GUILayout.Button( "Deserialize" ) )
                    {
                        var serArray = JsonUtility.FromJson<Vanilla.Biome.SerializedArray>( instance.json_file_somewhere );
                        instance.biomes.Clear();
                        foreach( var serBiome in serArray.values )
                        {
                           instance.biomes.Add( serBiome.Deserialize<Vanilla.Biome>() );
                        }
                    }
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.BeginHorizontal();
                {
                    if( GUILayout.Button( "Add Runtime Forest (Vanilla)") )
                    {
                        var dat = new int[]{ Random.Range(0,9) , Random.Range(0,9) , Random.Range(0,9) , Random.Range(0,9) };
                        instance.biomes.Add( new Vanilla.Biome_Forest( dat ) );
                    }
                    if( GUILayout.Button( "Add Runtime Mordor (Mod)") )
                    {
                        var dat = new int[]{ Random.Range(0,9) , Random.Range(0,9) , Random.Range(0,9) , Random.Range(0,9) };
                        instance.biomes.Add( new Mod.Biome_MordorAndUnicorns( dat ) );
                    }
                    if( GUILayout.Button( "Clear Runtime Biomes") )
                    {
                        instance.biomes.Clear();
                    }
                }
                EditorGUILayout.EndHorizontal();
            }
        }
    }
#endif

}

How to use tester:
Step 1: Create few runtime-only instances


Step 2: Click Serialize to serialize them all into single json string
Step 3: Click Clear Runtime Biomes

Step 4: Click Deserialize. If list was recreated = success. Fail otherwise.

TL;DR:

System.Activator.CreateInstance(
   System.Type.GetType(
      classInstance.GetType().FullName//<this line you serialize as string
   )
)
//This is just a base tho. Beware of details in implementation
2 Likes

IT WORKS!!! You guys are miracle workers and have made me so happy :slight_smile:

Thank you, thank you, thank you, thank you!!! My project still isn’t much to look at, but this is such a huge step closer to seeing it become something real.

You all put a lot of effort into helping me, especially Andrew; I hope someday I can return the favor somehow.

1 Like