Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert GameObject back to generic type <T>

I am implementing a deep object copier for Unity.

I found this great serialization/deserialization method here: https://stackoverflow.com/a/78612/3324388

However I hit a snag with MonoBehaviour objects. If the type is a GameObject, I need to use Instantiate instead of the serialization. So I've add a check:

if (typeof(T) == typeof(GameObject))
{
    GameObject clone = Instantiate(source as GameObject);
    T returnClone = clone as T;
    return returnClone;
}

I am able to cast the source as a GameObject (using as) but when I try to do it in reverse it fails with

The type parameter T cannot be used with the as parameter because it does not have a class type constraint nor a 'class' constraint.

If I try just casting it like:

if (typeof(T) == typeof(GameObject))
{
    GameObject clone = Instantiate(source as GameObject);
    T returnClone = (T)clone;
    return returnClone;
}

Cannot convert GameObject to type T

I feel I'm close but I can't quite get the casting right. Do you know what I am missing to get this to work?

If I cast the type to conform the error still persists: enter image description here

like image 705
Aggressor Avatar asked Jan 01 '18 16:01

Aggressor


2 Answers

Using as T in the return statement seems to do the trick. In the following test code attached to a game object in the scene I see a clone of Test and the console shows me different values for Count:

public class Test : MonoBehaviour
{
    private static bool _cloned = false;

    public static T Clone<T>(T source) where T : class 
    {
        if (typeof(T) == typeof(GameObject))
        {
            GameObject clone = Instantiate(source as GameObject);
            return clone as T;
        }
        else if (typeof(T) == typeof(PlainType))
        {
            PlainType p = new PlainType();
            // clone code
            return p as T;
        }
        return null;
    }

    public class PlainType
    {
        private static int _counter = 0;
        public int Count = ++_counter;
        public string Text = "Counter = " + _counter;
    }

    public PlainType MyPlainType = new PlainType();

    void Update ()
    {
        if (!_cloned)
        {
            _cloned = true;
            Clone(gameObject);
            PlainType plainClone = Clone(MyPlainType);
            Debug.Log("Org = " + MyPlainType.Count + " Clone = " + plainClone.Count);
        }
    }

}
like image 73
Kay Avatar answered Oct 03 '22 04:10

Kay


It’s not pretty but you can force the compiler doing a previous reference conversion to object:

 public static T Clone<T>(T source)
 {
      if (source is GameObject)
      {
          return (T)(object)Instantiate((GameObject)(object)source);  
      }
      else ...
  }

Yes, it is a bit of hack but sometimes you can’t avoid it. As a general rule, when you start mixing generics with runtime type checks things tend to get messy, a sure sign that you probably shouldn’t be using generics to begin with. Sometimes though, it can be justified but ugly code tends to crop up.

like image 25
InBetween Avatar answered Oct 03 '22 06:10

InBetween