Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

UI Editor - Polymorphism

Discussion in 'Scripting' started by Bingdom, Jul 15, 2020.

  1. Bingdom

    Bingdom

    Joined:
    Feb 4, 2017
    Posts:
    4
    Hey All,

    I've been working on creating a dynamic UI. Here's an image for a bit of an idea on my project:
    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).
    Code (CSharp):
    1. //Base class
    2. public class item : MonoBehaviour
    3. {
    4.     //Function has no purpose yet
    5.     public void performAction() {...}
    6. }
    7.  
    8. public class auto : item
    9. {
    10.     public float damage = 10f;
    11.     public float firerate = 5f;
    12.     public int clipSize = 1;
    13.     public int bulletCount = 1;
    14. }
    15. public class trajectory : item
    16. {
    17.     public float damage = 5f;
    18.     public float firerate = 2f;
    19.     public int clipSize = 1;
    20.     public int bulletCount = 1;
    21. }
    22. [...]
    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.
    Code (CSharp):
    1. public enum Type
    2. {
    3.     automatic,
    4.     semiAutomatic,
    5.     utility,
    6.     trajectory,
    7.     melee
    8. }
    9.  
    10. public Type gunType = Type.automatic;
    Then, to get these classes to show on the Editor UI (like in the image above)
    Code (CSharp):
    1. public void OnBeforeSerialize()
    2. {
    3.     if (!hasSelected) {
    4.         switch (gunType) {
    5.             case Type.automatic:
    6.                 itemType = gameObject.AddComponent<auto>();
    7.             break;
    8.  
    9.             case Type.semiAutomatic:
    10.                 itemType = gameObject.AddComponent<semi>();
    11.             break;
    12.             [...]
    13.          }
    14.          hasSelected = true;
    15.     }
    16. }
    Then to remove the component, the usual response would be to
    Code (CSharp):
    1. public void OnAfterDeserialize()
    2. {
    3.     Destroy(itemType); //or DestroyImmediate(itemType);
    4.     hasSelected = false;
    5. }
    However, both options fail to destroy the component, as indicated in the error message:
    upload_2020-7-16_4-6-18.png

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

    Bingdom

    Joined:
    Feb 4, 2017
    Posts:
    4
    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
     
    Last edited: Jul 15, 2020
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    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:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. [CustomEditor(typeof(ItemStats))]
    6. public class ItemStatsInspector : Editor
    7. {
    8.  
    9.     private static System.Type[] _weaponTypes = new System.Type[] { typeof(auto), typeof(semiauto), typeof(utility), typeof(trajectory), typeof(melee) };
    10.     private static string[] _weaponTypeDisplayNames = new string[] { "Automatic", "Semi-Auto", "Utility", "Trajectory", "Melee" };
    11.     private int _lastSelected;
    12.  
    13.     void OnEnable()
    14.     {
    15.         //on start get the component that is on the gameobject and make sure we have the correct thing displayed
    16.         var comp = (this.target as ItemStats).gameObject.GetComponent<item>();
    17.         _lastSelected = comp != null ? System.Array.IndexOf(_weaponTypes, comp.GetType()) : -1;
    18.     }
    19.  
    20.     public override void OnInspectorGUI()
    21.     {
    22.         //draws the default inspector for ItemStats
    23.         DrawDefaultInspector();
    24.  
    25.         //now our popup that adds/removes the component we want
    26.         if (Application.isPlaying) return; //probably don't want to do this if the game is playing
    27.  
    28.         EditorGUI.BeginChangeCheck(); //we want to see if the popup had a selection made
    29.         _lastSelected = EditorGUILayout.Popup("Gun Type", _lastSelected, _weaponTypeDisplayNames);
    30.         if(EditorGUI.EndChangeCheck() && _lastSelected >= 0)
    31.         {
    32.             var go = (this.target as ItemStats).gameObject;
    33.             foreach(var c in go.GetComponents<item>())
    34.             {
    35.                 UnityEngine.Object.DestroyImmediate(c);
    36.             }
    37.      
    38.             go.AddComponent(_weaponTypes[_lastSelected]);
    39.         }
    40.  
    41.     }
    42.  
    43. }
    44.  
    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.
     
    Bingdom likes this.
  4. Bingdom

    Bingdom

    Joined:
    Feb 4, 2017
    Posts:
    4
    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!
     
  5. Bingdom

    Bingdom

    Joined:
    Feb 4, 2017
    Posts:
    4
    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:
    upload_2020-7-16_15-55-36.png

    Then when I go to select an ItemType, the dropdown box disappears:
    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.
     
    Last edited: Jul 16, 2020
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Bingdom likes this.
  7. Deleted User

    Deleted User

    Guest