Unions and showing different class members in the inspector

Hi all,

I’m relatively new to Unity and C#, and I have a C/C++ background, which made me want to try something like this:

I want to make available in the inspector a different variable depending on what element of an enum is chosen. On top of that I’m trying to have the members of the class to be the C# equivalent of an union.
No one understood that, so let’s have an example:

[System.Serializable]
class Giggiddy        //No it's NOT a MonoBehaviour
{
    public enum Type
    {
        TYPE_UINT,
        TYPE_STRING
    }

    //This is kind of like an union in C/C++    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
    public struct Data
    {
        [System.Runtime.InteropServices.FieldOffset( 0 )]
        uint m_uint;
        [System.Runtime.InteropServices.FieldOffset( 0 )]
        string m_string;
    };

    public Type m_type;
    public Data m_data;

}

What I want is, depending on what type is chosen in the inspector (TYPE_UINT or TYPE_STRING), I’d like either the variable m_data.m_uint or m_data.m_string to be accessible.

Currently this seems to confuse Unity, as all I can see accessible in the inspector is the enum type, and nothing else.

Thanks for any help (including a better way to do all this :))

Little update:
I changed my struct to a class and added the serializable property, made the class members public and changed my uint to an int:

[System.Serializable]
class Giggiddy        //No it's NOT a MonoBehaviour
{
    public enum Type
    {
        TYPE_INT,
        TYPE_STRING
    }

    //This is kind of like an union in C/C++
[System.Serializable][System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
    public class Data
    {
        [System.Runtime.InteropServices.FieldOffset( 0 )]
        public int m_int;
        [System.Runtime.InteropServices.FieldOffset( 0 )]
        public string m_string;
    };

    public Type m_type;
    public Data m_data;

}

Now m_int and m_string DO show up in the inspector, however m_int has a crazy value in it and if i fiddle around with the values, Unity crashes pretty easily (might be necessary to do a bug report about that actually)

OK, I get it, no one has a clue and everyone is confused about my union idea thingy.
That includes me.

So let’s forget about it, and make something much simpler:

[System.Serializable]
class Giggiddy        //No it's NOT a MonoBehaviour
{
    public enum Type
    {
        TYPE_INT,
        TYPE_STRING
    }

    public Type m_type;
    public int m_int;
    public string m_string;
}

My question remains the same:

How would I go about making it so that, in the Inspector, only the m_int variable (visible as Int in the Inspector) shows up when I choose TYPE_INT for m_type (visible as Type in the Inspector)? And the same with m_string and TYPE_STRING of course.

It’s unlikely that the built-in inspector will know what to do with something like this, but you can create a custom inspector using the Editor classes (check out Editor in particular).

Is there any particular reason why you need to use the union here, by the way?

Yes I had a look at those Editor classes, I couldn’t figure out how to use them while keeping my class accessible in game.

I’m willing to forget about the union thing altogether on this, but since you asked, it’s simply for saving memory:
For instance, I won’t use m_string AT ALL if m_type != TYPE_STRING, therefore it’s useless to carry it along with the Giggiddy object.
So an union structure would allow me to choose how is used the memory allocated for an instance of Giggiddy, without wasting it (too much).

In .NET, the string is actually just a pointer to an allocated string, and the enum variable is an integer. If you did without the enum and just put both a string and an int in the struct (without a union) then I think the memory usage would be the same as the union.

Thanks for the advice!

Although (;)) without the enum, I’d have no idea what the setup guy is up to. Indeed, this was just some example code with generic names, but would you replace

class Giggiddy by class Reward
TYPE_STRING by TYPE_ITEM_ID,
TYPE_INT by TYPE_SKILL_POINTS

then you get a better picture!

On top of that, if you start adding more and more Reward types
(from the top of my head: TYPE_CAR_NAME, TYPE_SLOT_SPACE, TYPE_DAMAGE_FACTOR…),
then you’ll quickly need more and more data types for storing the relevant data the designer puts in, meaning there’ll be more and more memory overhead for each Reward object instantiated.

But but but! as I said, I’m willing to forget about that altogether since I won’t have that many instances of Rewards going around, and simply hold one data type for each element of the enum.
It’s just in my nature to try using only what I need and no more, that’s why I was going towards the union idea.

No, my main concern in all this is really hiding access to irrelevant data holders from the designers, as we all know these guys are good at screwing things up if we give them the chance to.
(That’s right, I said it! Now come and get me!)

So I had a look at the Editor class, and if I get this kinda right, I need to create a sister class to my Reward class that would inherit from Editor instead of… nothing, and add
@CustomEditor(Reward)
before the class declaration. Unless that’s Java only. Which would make me very s/mad.

Thanks you!

This ain’t workin’ none!

using UnityEditor;
using System.Collections;

[CustomEditor(typeof(Reward))]
public class EDIT_Reward : Editor
{
    override public void OnInspectorGUI( )
    {
        EditorGUILayout.BeginHorizontal( );
        EditorGUILayout.PrefixLabel( "TESTING 1 2 3" );
        EditorGUILayout.EndHorizontal( );

    }
}

I is sad!

I was expecting to see only the line ‘TESTING 1 2 3’, but I see the usual exposed variables…

I missed something (or even the point) didn’t I?

I don’t think you missed anything in the script you posted. I copy pasted it and it worked fine. However, from your earlier posts I guess that in your case Reward is not a MonoBehaviour, which won’t work.

OnInspectorGUI manages the inspector GUI for an entire MonoBehaviour, not for specific members of monobehaviours. In that light, it should probably print some error when you connect an Editor script to something that isn’t a MonoBehaviour.

Hi Taoa and welcome! :smile:

I have an idea you might be able to use. I’m just going to code in this post, so errors will be present.

[Edit] of course you can replace monobehaviour with serializable, but the overhead of using monobehaviour is almost non existing, especially if you do not use any of the methods Awake/Start/Update

class Quest : MonoBehaviour 
{ 
    public Reward rewardNormal;
    public Reward rewardHeroic;
}

class Reward : MonoBehaviour 
{ 
    public string name;
}

class Xp : Reward 
{ 
    public float amount;
}

class Item : Reward 
{ 
    public string id;
}

class Gold : Reward 
{ 
    public int amount;
}

This setup will allow you to drag a quest onto a GameObject. Then drag any number of rewards onto the GameObject, set them up in the inspector and then drag them onto the field Reward under Quest.

Hope you understand what I mean and it in some way helps :stuck_out_tongue: [/code]

To tomvds

You’re right, my class is merely a data container, and an array of rewards is member of another class, Level:

[System.Serializable]
public class Level
{
    //Some members...
    public Reward m_rewards[];
    //Some more members...
}

Level is itself some data container, and an array of levels is member of a ‘Block’ (designer stuff, don’t ask what it is):

public class Block : MonoBehaviour
{
    //Some members...
    public Level m_levels[];
    //Some more members...
}

Only Block is a MonoBehaviour and only Block is component of a Prefab.
When you create your Block prefab, you also put in all you level setup data, as well as your rewards data for each level.
All your members are directly accessible in the Block inspector, without having to create new prefabs or gameobjects of Levels or Rewards.
And that’s the way I wanted it to be, otherwise our Prefabs list or hierarchy will be even more overcrowded than it already is.
That is… unless there’s a way to make members of a monobehaviour class directly accessible in the inspector without having to create prefabs or gameobjects, but I don’t think that’s possible. And please correct me if I’m wrong on this.

One way to get that to work would be to rewrite the entire Inspector interface for everything inside a Block.
Joy.

To PaulUsul

More or less the same thing: I don’t want to have to create a prefab or gameobject of every different Reward (or level) we’ll need, it will become crazy very quickly. I really want to be able to have only ONE prefab for each ‘Block’ (containing its list of levels, themselves containing their list of rewards… amongst other things).

But concerning your idea of making inherited classes of Reward, that would work: the designer would select what version of Reward he wants from the scripts, and fill the corresponding data member. Too bad that would mean having to make of Reward a MonoBehaviour, therefore making it necessary to have a prefab/gameobject of it for each reward. (Again, correct if wrong…)

As far as I know, you are correct. Besides, it doesn’t make much sense for your Reward class to inherit from MonoBehaviour, as it is clearly a data structure, not GameObject behaviour.

I think you don’t have to rewrite everything. Just mark your array as [HideInInspector]. In your editor class call DrawDefaultInspector() in OnInspectorGUI(), which will render all fields of your Block except the array (as it is hidden). Then follow with the code for your custom viewer for the reward array.

That sounds good.
However I’d have trouble making sure the extra interface I add is at the right spot: the code specific to the Rewards must make sure this interface is placed inside the interface for each level:

(Attempt to reproduce the interface :roll:
V is an open structure and
> a close one)

V Block
V Levels
…Size 3
V Element 0
V Rewards
…Size 2
V Element 0
…Type TYPE_SKILL_POINTS
…Amount 3
V Element 1
…Type TYPE_ITEM_ID
…Name=“Gordon Freeman’s voice modulator”
> Element 1
> Element 2

The red stuff would be the hand-coded one.
I doubt it’s possible once the normal interface has already been rendered to put the extra stuff at the right place.

Bear in mind that I’ll have much more stuff inside Block than just an array of levels, as well as much more in Level than just an array of Reward. This was a striped down presentation :slight_smile:

Indeed, I’m afraid that in that case you’ll be forced to implement the whole level array view. I was under the impression that your array just contained the rewards.

:slight_smile: That’s what I thought! :slight_smile:

Thinking about it, if I do implement the whole Block Inspector interface (I didn’t, I’m way too lazy, especially concerning something that might not be used at all in the end, I’m just doing R&D right now), then I can go back to using my whole union idea.

And I guess, this aslo shows that anyone who wants/needs to get a union-type behaviour for something that should be accessible through the Inspector will have to make his own interface for it.