UI Editor - Polymorphism

Hey All,

I’ve been working on creating a dynamic UI. Here’s an image for a bit of an idea on my project:
6093906--662148--unknown (1).png

As you can see, I’ll be able to select from a dropdown menu with a range of “Gun Types”. Each selection would have its own set of functions. The idea is for it to be polymorphed.

I’ve been working on this for several hours now. I’m very close, however, I haven’t completely solved it just yet.

Essentially, I have a base class with derived classes. This is for my item types (or gun types).

//Base class
public class item : MonoBehaviour
{
    //Function has no purpose yet
    public void performAction() {...}
}

public class auto : item
{
    public float damage = 10f;
    public float firerate = 5f;
    public int clipSize = 1;
    public int bulletCount = 1;
}
public class trajectory : item
{
    public float damage = 5f;
    public float firerate = 2f;
    public int clipSize = 1;
    public int bulletCount = 1;
}
[...]

I’ve been figuring out a method to do this to appear on the editor effectively. I’ve used an enum to select the class to use.

public enum Type
{
    automatic,
    semiAutomatic,
    utility,
    trajectory,
    melee
}

public Type gunType = Type.automatic;

Then, to get these classes to show on the Editor UI (like in the image above)

public void OnBeforeSerialize()
{
    if (!hasSelected) {
        switch (gunType) {
            case Type.automatic:
                itemType = gameObject.AddComponent<auto>();
            break;

            case Type.semiAutomatic:
                itemType = gameObject.AddComponent<semi>();
            break;
            [...]
         }
         hasSelected = true;
    }
}

Then to remove the component, the usual response would be to

public void OnAfterDeserialize()
{
    Destroy(itemType); //or DestroyImmediate(itemType);
    hasSelected = false;
}

However, both options fail to destroy the component, as indicated in the error message:

I tried to do this with structs to get by attaching components (which would be preferable), however, I cannot use inheritance (unlike c++).

Within minutes of posting this, I found [ExecuteInEditMode].
With the use of a flag, I was able to solve this problem.

If someone has a better suggestion/approach to this (especially without the use of adding/removing components), I’m all ears.

Edit:
Values seem to fail to retain after hitting “Play”
Polymorphism seems to not work the way I expected it to work like in C++
Edit2: Nevermind, I was missing the appropriate keywords

Adding/Destroying a component in the serialization events is probably a bad way to go about this since those will run at runtime. And this appears to be something that’s all editor for you.

Instead I’d just create a custom editor for ‘Item Stats’, draw the dropdown there (you don’t even need an enum then), and add/remove componets that way.

Something like:

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ItemStats))]
public class ItemStatsInspector : Editor
{
 
    private static System.Type[] _weaponTypes = new System.Type[] { typeof(auto), typeof(semiauto), typeof(utility), typeof(trajectory), typeof(melee) };
    private static string[] _weaponTypeDisplayNames = new string[] { "Automatic", "Semi-Auto", "Utility", "Trajectory", "Melee" };
    private int _lastSelected;
 
    void OnEnable()
    {
        //on start get the component that is on the gameobject and make sure we have the correct thing displayed
        var comp = (this.target as ItemStats).gameObject.GetComponent<item>();
        _lastSelected = comp != null ? System.Array.IndexOf(_weaponTypes, comp.GetType()) : -1;
    }
 
    public override void OnInspectorGUI()
    {
        //draws the default inspector for ItemStats
        DrawDefaultInspector();
  
        //now our popup that adds/removes the component we want
        if (Application.isPlaying) return; //probably don't want to do this if the game is playing

        EditorGUI.BeginChangeCheck(); //we want to see if the popup had a selection made
        _lastSelected = EditorGUILayout.Popup("Gun Type", _lastSelected, _weaponTypeDisplayNames);
        if(EditorGUI.EndChangeCheck() && _lastSelected >= 0)
        {
            var go = (this.target as ItemStats).gameObject;
            foreach(var c in go.GetComponents<item>())
            {
                UnityEngine.Object.DestroyImmediate(c);
            }
      
            go.AddComponent(_weaponTypes[_lastSelected]);
        }
  
    }
 
}

Note, I did not test this code. I just wrote it here in the web editor. I probably have typos or other errors. It’s intended for example purpose only, you can edit it to your specific needs.

1 Like

Hey, thanks for the reply!

This seems like a far better approach as I’ve just run into an issue where the Serializer events like to trigger often (causing data not to carry across when I run the game). A great solution compared to my forthcoming hacky approach.

I’ll give it a go when I wake up!

Ok, I’ve put it together and it works great. Thank you again!

It’s very much like the image above, and it’s persistent!

I’d like to extend this further (by compacting it into a single component). How could I tell the editor to view the child objects?
I’ve tried [CustomEditor((typeof(Item))]
And with a few alterations with the code, I get:
6095772--662499--upload_2020-7-16_15-55-36.png

Then when I go to select an ItemType, the dropdown box disappears:
6095772--662496--upload_2020-7-16_15-54-30.png

I’ve tried to apply multiple CustomEditor attributes or inputting the array of the child objects, but it seems to not accept either of those.

CustomEditor has a 2nd paramter called ‘editorForChildClasses’:

[CustomEditor(typeof(Item), true)]

This will tell Unity to use the editor for Item, and all subclasses of Item.

1 Like

IMVHO, it’d be better for you just to use the native (since 2019.3 release) [SerializeReference] attribute Unity - Scripting API: SerializeReference