Calling a derived class (Serialized in a Scriptable Object) returns the super class.

Greetings!

There is a large chunk of source code below, maybe its to much and someone who potentially has a useful input can be scared away so: My problem is the following, when I use the TestX(:MonoBehaviour) script everything works fine, I get the correct types back - but the EditorWindow fails to do so and returns IDatabaseType (Base Class) for the derived classes! I suspect that it is related to that TestX.cs calls everything from a monobehaviour while my database is using an ScriptableObject its base.

Below you see the class which the database types will be derived from, the classes have been marked as serializable for a reason (Storing together with a Scriptable Object.)

[Serializable]
public class IDatabaseType
{
    [SerializeField]
    private object value = new object();

    [SerializeField]
    public virtual object GetValue()
    {
        return null;
    }

    [SerializeField]
    public virtual void SetValue(object newValue)
    {

    }

}

Here is one of the four derived classes (All four are identical ignoring the minor differences of variable types.)

    [Serializable]
    public class DBString : IDatabaseType
    {
        [SerializeField]
        private string value;

        public DBString(string value)
        {
            SetValue(value);
        }

        public override object GetValue()
        {
            return value;
        }

        public override void SetValue(object newValue)
        {
            value = (string)newValue;
        }
    }

Using these five (IDatabaseType + 4 Derivations) classes I have no problems, I can properly access to the derived classes by calling them like this.

public class Testx : MonoBehaviour {

	// Use this for initialization
	void Start () {
        List<IDatabaseType> x = new List<IDatabaseType>();
        x.Add(new Unity.Database.DBBoolean(false));
        x.Add(new Unity.Database.DBFloat(0f));
        x.Add(new Unity.Database.DBInt(0));
        x.Add(new Unity.Database.DBString(""));

        IDatabaseType DBBoolean = new Unity.Database.DBBoolean(false);
        IDatabaseType DBFloat = new Unity.Database.DBFloat(0f);

        Debug.Log("(1) one by one: " + x[0].GetType());
        Debug.Log("(2) one by one: " + x[1].GetType());
        Debug.Log("(3) one by one: " + x[2].GetType());
        Debug.Log("(4) one by one: " + x[3].GetType());

        foreach(IDatabaseType IDT in x)
        {
            Debug.Log("foreach " + IDT.GetType());
        }

        Debug.Log("Unique: " + DBBoolean.GetType());
        Debug.Log("Unique: " + DBFloat.GetType());
    }

}

However! it gets tricky when the Scriptable Object comes into the game, while the database gets properly serialized after the serialization it fails to properly identify the classes! It no longer returns the derived class but the base class!

The Database (I removed some of the method bodies to rise visibility.) here.

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

[Serializable]
public class UnityDatabase : ScriptableObject {

    [SerializeField]
    private string DatabaseName;
    [SerializeField]
    private int DatabaseID;
    [SerializeField]
    public IDatabaseType[] Header;
    [SerializeField]
    public string[] HeaderLabel = new string[0];
    [SerializeField]
    public List<DatabaseRow> data = new List<DatabaseRow>();
    
    /// <summary>
    /// Setup new Database
    /// </summary>
    /// <param name="Header">Database Type Setup (Headline)</param>
    public UnityDatabase(IDatabaseType[] _Header, string[] _HeaderLabel)
    {
        Header = _Header;
        HeaderLabel = _HeaderLabel;
    }

    /// <summary>
    /// Create new Entry
    /// </summary>
    /// <param name="withValues">Values to use</param>
    public void CreateNewEntry(params IDatabaseType[] withValues)
    {
        // Verify given values
        if(!verifyRow(withValues))
        {
            return;
        }

        // Create Database Entry
        data.Add(new DatabaseRow(withValues));

        //Debug.Log("[SYSTEM]: Created new Row.");
    }

    /// <summary>
    /// Get Specified Entry
    /// </summary>
    /// <returns></returns>
    public IDatabaseType GetValue(int row, int column)
    {
        return data[row].data[column];
    }

    /// <summary>
    /// Get Specified Row
    /// </summary>
    /// <returns></returns>
    public DatabaseRow GetValue(int row)
    {
        return data[row];
    }

    /// <summary>
    /// Overwrite Targetted Field
    /// </summary>
    /// <param name="newValue">New Value</param>
    /// <param name="row">Row to target</param>
    /// <param name="column">Column to target</param>
    public void SetValue(IDatabaseType newValue, int row, int column)
    {
        // Verify if target point exists
        try
        {
            IDatabaseType x = data[row].data[column];
        } catch
        {
            Debug.Log("Outside Index Range.");
            return;
        }

        if(verifyEntry(newValue, data[row].data[column]))
        {
            data[row].data[column] = newValue;
        }
    }
    
    /// <summary>
    /// Verify if given data is compatible with this databases header
    /// </summary>
    /// <param name="needVerification">Data to Verify</param>
    /// <returns></returns>
    private bool verifyRow(IDatabaseType[] needVerification)
    {
        // Verify Length
        if (Header.Count() != needVerification.Count())
        {
            //Debug.Log("[SYSTEM]: Length Failure.");
            return false;
        }

        // Verify Types
        bool typeSuccess = true;

        for (int i = 0; i < Header.Length; i++)
        {
            // Check if TypeOf is equal
            if (!verifyEntry(needVerification_, Header*))*_

{
//Debug.Log(“[SYSTEM]: Type Failure.”);
typeSuccess = false;
}
}

return typeSuccess;
}

private bool verifyEntry(IDatabaseType first, IDatabaseType second)
{
if(first.GetType() != second.GetType())
Debug.Log(“Warning: The given Entry is not compatible with the Signature!”);

return first.GetType() == second.GetType();
}

}

[Serializable]
public class DatabaseRow
{
[SerializeField]
public List data = new List();

public DatabaseRow(params IDatabaseType[] RowSetup)
{
data = RowSetup.ToList();
}
}
The Database is read from a EditorWindow.
public class EditorDatabaseManager : EditorWindow
{

[MenuItem(“Database/Database Editor”)]
public static void ShowWindow()
{
EditorWindow.GetWindow(typeof(EditorDatabaseManager));
}

private UnityDatabase db;

void OnGUI()
{
db = (UnityDatabase)EditorGUILayout.ObjectField(db, typeof(UnityDatabase), true);

if (db == null)
return;

if (GUILayout.Button(“Create new Row”))
{
db.CreateNewEntry(
db.Header
);
}

if (db.data == null)
{
EditorGUILayout.HelpBox(“Data Null.”, MessageType.Warning);
return;
}

int x = 0;

foreach (DatabaseRow DR in db.data)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(“” + x++, GUILayout.Width(50f));

for (int i = 1; i < DR.data.Count; i++)
{
Type t = db.Header[i - 1].GetType().GetElementType();

IDatabaseType IDT = db.Header[i - 1];
EditorGUILayout.LabelField(IDT.GetType().ToString());
}

EditorGUILayout.EndHorizontal();
}

}
}
To create the asset object you would also need this fellow here:

public class CreateDatabaseManager : EditorWindow
{

[MenuItem(“Database/Database Creator”)]
public static void ShowWindow()
{
EditorWindow.GetWindow(typeof(CreateDatabaseManager));
}

private Unity.Database.DBTypes[] header = new Unity.Database.DBTypes[0];
private string[] labels = new string[0];
private int headerLength = 0;
private string dbname = “Undefined”;

void OnGUI()
{
// Verify header length
if (header.Length != headerLength || labels.Length != headerLength) {
header = new Unity.Database.DBTypes[headerLength];
labels = new string[headerLength];

for(int i = 0; i < headerLength; i++)
{
labels = “” + i;
}

OnGUI();
return;
}

// Parameters
EditorGUILayout.BeginHorizontal();
headerLength = EditorGUILayout.IntField("Header Index Length: ", headerLength);
dbname = EditorGUILayout.TextField(dbname);
EditorGUILayout.EndHorizontal();

EditorGUILayout.Space();

// Label Definition
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < labels.Length; i++)
{
labels = EditorGUILayout.TextField(labels*);*
}
EditorGUILayout.EndHorizontal();

// Type Definition
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < headerLength; i++)
{
header = (Unity.Database.DBTypes)EditorGUILayout.EnumPopup(header*);*
}
EditorGUILayout.EndHorizontal();

EditorGUILayout.Space();

// Create on Click
if (GUILayout.Button(“Create new Table (” + dbname + “)”))
{
// Prepare Object
UnityDatabase obj = new UnityDatabase(ConvertToHeader(header), labels);

dbname = (dbname.Equals(“”)) ? “Undefined” : dbname;

AssetDatabase.CreateAsset(obj, AssetDatabase.GenerateUniqueAssetPath(“Assets/” + dbname + “.asset”));
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.FocusProjectWindow();
Selection.activeObject = obj;

header = new Unity.Database.DBTypes[0];
headerLength = 0;
dbname = “Undefined”;
}
}

IDatabaseType[] ConvertToHeader(Unity.Database.DBTypes[] target)
{
List db = new List();

foreach(Unity.Database.DBTypes DBT in target)
{
switch(DBT)
{
case Unity.Database.DBTypes.Bool:
db.Add(new Unity.Database.DBBoolean(false));
break;
case Unity.Database.DBTypes.Float:
db.Add(new Unity.Database.DBFloat(0f));
break;
case Unity.Database.DBTypes.Int:
db.Add(new Unity.Database.DBInt(0));
break;
case Unity.Database.DBTypes.String:
db.Add(new Unity.Database.DBString(“”));
break;
}
}

return db.ToArray();
}
}

Now my problem is the following, when I use the TestX.cs script everything works fine, I get the correct types back - but the editorwindow fails to do so and returns IDatabaseType for the derived classes! I suspect that it is related to that TestX.cs calls everything from a monobehaviour while my database is using an ScriptableObject as a base.
Edit: The namespaces you should use with this are the following:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
using System.Linq;
using System.Collections.Generic;

I answered the same question a couple of times now, so just read them over here:

http://answers.unity3d.com/answers/735548/view.html

http://answers.unity3d.com/answers/245769/view.html

http://answers.unity3d.com/answers/1107926/view.html

http://answers.unity3d.com/answers/406546/view.html