Eugene Paul
Eugene Paul

Reputation: 39

UNITY, How can I get all properties from another C# class and put them into an enum

I have two classes:

public class Stats : MonoBehaviour
{
    // Primary Stats
    public int strength;
    public int agility;
    public int intellect;
    public int stamina;
    public int spirit;
}

and

public class EquipmentProperties : ItemProperties
{
   public Stats stats;
}

public enum Stats
{//variables from "Stats" class to be in this enum
}

I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them..

Upvotes: 1

Views: 1781

Answers (2)

derHugo
derHugo

Reputation: 90639

Prolog

This is of course not what you are asking directly since it is not automatic but I would suggest a Dictionary<Stats, int> and do e.g.

public class StatsComponent : MonoBehaviour
{
    // Make these only assignable via the Inspector
    [SerializeField] private int strength;
    [SerializeField] private int agility;
    [SerializeField] private int intellect;
    [SerializeField] private int stamina;
    [SerializeField] private int spirit;

    public readonly Dictionary<Stats, int> stats = new Dictionary<Stats, int>();

    private void Awake ()
    {
        // Initialize once with values from the Inspector
        stats.Add(Stats.Strength, strength);
        stats.Add(Stats.Agility, agility);
        stats.Add(Stats.Intellect, intellect);
        stats.Add(Stats.Stamina, stamina);
        stats.Add(Stats.Spirit, spirit);
    }
}

public enum Stats
{
    Strength,
    Agility,
    Intellect,
    Stamina,
    Spirit
}

Of course there are ways to automatize that via reflection but I'm sure it will bring you more headaches and issues then it is solving - that's only an opinion of course.


Intermediate Solution

If you don't want to type things twice you could instead of an enum rather go by index or strings e.g. using SerializedDictionary you could simply have a

public SerializedDictionary<string, int> stats;

and fill it in the Inspector and not have your fields at all.


Enum Generator Window

However, if you still want at least automation with a minimal effort to build on top of this answer I made this a tool EditorWindow you can directly use within Unity.

Just place this script anywhere in your project.

#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

public class EnumGeneratorWindow : EditorWindow
{
    // This is of course optional but I thought it makes sense to filer for a specific field type
    private enum FieldType
    {
        Int,
        Float,
        Bool,

        // more could of course be added
    }

    private MonoScript sourceScript;
    private MonoScript targetScript;
    private FieldType fieldType;

    private Type GetFieldType()
    {
        return fieldType switch
        {
            FieldType.Int => typeof(int),
            FieldType.Float => typeof(float),
            FieldType.Bool => typeof(bool),

            // according to the enum add more cases
            _ => null
        };
    }

    [MenuItem("Window/ENUM GENERATOR")]
    private static void Init()
    {
        var window = GetWindow<EnumGeneratorWindow>();
        window.Show();
    }

    private void OnGUI()
    {
        EditorGUILayout.LabelField("ENUM GENERATOR", EditorStyles.boldLabel);

        sourceScript = EditorGUILayout.ObjectField("Source", sourceScript, typeof(MonoScript), false) as MonoScript;

        if (!sourceScript)
        {
            EditorGUILayout.HelpBox("Reference the script where to fetch the fields from", MessageType.None, true);
            return;
        }

        var sourceType = sourceScript.GetClass();

        if (sourceType == null)
        {
            EditorGUILayout.HelpBox("Could not get Type from source file!", MessageType.Error, true);
            return;
        }

        targetScript = EditorGUILayout.ObjectField("Target", targetScript, typeof(MonoScript), false) as MonoScript;

        if (!targetScript)
        {
            EditorGUILayout.HelpBox("Reference the script where write the generated enum to", MessageType.None, true);
            return;
        }

        if (targetScript == sourceScript)
        {
            EditorGUILayout.HelpBox("The source and target script should probably rather not be the same file ;)", MessageType.Error, true);
            return;
        }

        var targetType = targetScript.GetClass();

        if (targetType == null)
        {
            EditorGUILayout.HelpBox("Could not get Type from target file!", MessageType.Error, true);
            return;
        }

        fieldType = (FieldType)EditorGUILayout.EnumPopup("Field Type", fieldType);

        EditorGUILayout.Space();

        EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);

        var fields = sourceType.GetFields().Where(f => f.FieldType == GetFieldType()).Select(f => f.Name).ToArray();

        var fileContent = new StringBuilder("public enum ").Append(targetType.Name).Append(" { ");
        for (var i = 0; i < fields.Length; i++)
        {
            if (i != 0)
            {
                fileContent.Append(", ");
            }

            fileContent.Append(fields[i]);
        }

        fileContent.Append(" }");

        EditorGUILayout.LabelField(fileContent.ToString());

        var color = GUI.color;
        GUI.color = Color.red;
        GUILayout.BeginVertical();
        {
            EditorGUILayout.LabelField("! DANGER ZONE !", EditorStyles.boldLabel);

            EditorGUILayout.Space();

            if (GUILayout.Button("GENERATE ENUM"))
            {
                var targetID = targetScript.GetInstanceID();

                // e.g. Assets/SomeFolder/MyStats.cs
                var targetAssetPath = AssetDatabase.GetAssetPath(targetID);

                // just as a safety net 
                if (EditorUtility.DisplayDialog("Generate and overwrite with enum?", $"Attention\n\nThis will overwrite any content of {targetAssetPath} with the new content.\n\nAre you sure?", "Yes generate", "OMG NO! Cancel this!"))
                {
                    // a bit of a hack but we need to convert the Unity asset path into a valid system path by erasing one duplicate "Assets"
                    var pathParts = targetAssetPath.Split('/').ToArray();

                    // overwrite the "Assets" with the full path to Assets
                    pathParts[0] = Application.dataPath;

                    // re-combine all path parts but this time use the according system file path separator char
                    var targetSystemPath = Path.Combine(pathParts);

                    // Write the content into the file via the normal file IO
                    File.WriteAllText(targetSystemPath, fileContent.ToString());

                    // trigger a refresh so unity re-loads and re-compiles
                    AssetDatabase.Refresh();
                }
            }
        }
        GUILayout.EndVertical();
        GUI.color = color;
    }
}
#endif

How it works:

  • Open the window via the header menu -> Window -> ENUM GENERATOR
  • Drag in your MonoBehaviour script into "Source"
  • Create a new empty script with the enum name you like
  • Drag your enum target script into "Target"
  • Select the type of fields we are looking for
  • Finally hit generate

Here is a little demo ;)

I just start of using Example.cs

public class Example : MonoBehaviour
{
    public int someInt, anotherInt;
    public float someFloat, anotherFloat, andOnMore;
    public bool someBool, yetAnotherBool;
}

and ExampleEnum.cs

public enum ExampleEnum
{

}

enter image description here

Upvotes: 2

rustyBucketBay
rustyBucketBay

Reputation: 4561

"I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them"

Enums must be specified at compile time, you can't dynamically add enums during run-time. If you would like to stablish the fields of your enum dynamically with your class variables, guess that because the Stats class might change along the app's development, you would need to store that enum somewhere, because if not you would need to access the fields of the dynamic enum according to that generic way of setting the enumeration, in a kind of meta-programming templated way that would not make much sense.

So along with your question comes the question of how to store that enum to be used afterwards I guess. For that you can check EnumBuilder class.

Extending that example, you can build the enum according to the specific Stats class like this:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

public class Stats
{
    // Primary Stats
    public int strength;
    public int agility;
    public int intellect;
    public int stamina;
    public int spirit;
}

class Example
{
    public static List<string> getFields(Type type) {
        var propertyValues = type.GetFields();
        var result = new Stats[propertyValues.Length];
        var retStr = new List<string>();
        for (int i = 0; i < propertyValues.Length; i++) {
            retStr.Add(propertyValues[i].Name);
        }

        return retStr;
    }

    public static void Main() {
        // Get the current application domain for the current thread.
        AppDomain currentDomain = AppDomain.CurrentDomain;

        // Create a dynamic assembly in the current application domain,
        // and allow it to be executed and saved to disk.
        AssemblyName aName = new AssemblyName("TempAssembly");
        AssemblyBuilder ab = currentDomain.DefineDynamicAssembly(
            aName, AssemblyBuilderAccess.RunAndSave);

        // Define a dynamic module in "TempAssembly" assembly. For a single-
        // module assembly, the module has the same name as the assembly.
        ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

        // Define a public enumeration with the name "Elevation" and an
        // underlying type of Integer.
        EnumBuilder eb = mb.DefineEnum("Stats", TypeAttributes.Public, typeof(int));

        int fieldCount = 0;
        getProperties(typeof(Stats)).ForEach(field => {
            eb.DefineLiteral(field, fieldCount);
            fieldCount++;
        });

        // Define two members, "High" and "Low".
        //eb.DefineLiteral("Low", 0);
        //eb.DefineLiteral("High", 1);


        // Create the type and save the assembly.
        Type finished = eb.CreateType();
        ab.Save(aName.Name + ".dll");

        foreach (object o in Enum.GetValues(finished)) {
            Console.WriteLine("{0}.{1} = {2}", finished, o, ((int)o));
        }
        Console.ReadLine();
    }
}

Output:

Stats.strength = 0
Stats.agility = 1
Stats.intellect = 2
Stats.stamina = 3
Stats.spirit = 4

Upvotes: 2

Related Questions