簡體   English   中英

UNITY,我如何從另一個 C# class 獲取所有屬性並將它們放入枚舉

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

我有兩節課:

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

public class EquipmentProperties : ItemProperties
{
   public Stats stats;
}

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

我正在嘗試從 Stats class 中獲取所有變量,而無需手動輸入它們。

“我正在嘗試從 Stats class 中獲取所有變量,而無需手動輸入它們”

枚舉必須在編譯時指定,您不能在運行時動態添加枚舉。 如果您想使用 class 變量動態建立枚舉字段,請猜測因為Stats class 可能會隨着應用程序的開發而改變,您需要將該枚舉存儲在某處,因為如果不是,您需要訪問以下字段動態枚舉根據設置枚舉的通用方式,以一種沒有多大意義的元編程模板方式。

因此,隨着您的問題而來的是我猜如何存儲該枚舉以供以后使用的問題。 為此,您可以檢查EnumBuilder class

擴展該示例,您可以根據特定的Stats class 構建枚舉,如下所示:

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

Prolog

這當然不是您直接要求的,因為它不是自動的,但我建議使用Dictionary<Stats, int>並執行例如

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
}

當然,有一些方法可以通過反射實現自動化,但我相信它會給你帶來更多的頭痛和問題,然后它正在解決——這當然只是一種觀點。


中間解決方案

如果您不想輸入兩次內容,則可以通過索引或字符串代替枚舉而不是 go,例如使用SerializedDictionary您可以簡單地擁有一個

public SerializedDictionary<string, int> stats;

並將其填寫在 Inspector 中,並且根本沒有您的字段。


枚舉生成器 Window

但是,如果您仍然希望在此答案之上以最少的努力實現自動化,我將其作為一個工具 EditorWindow,您可以直接在 Unity 中使用。

只需將此腳本放在項目中的任何位置即可。

#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

這個怎么運作:

  • 通過 header 菜單打開 window -> Window -> ENUM GENERATOR
  • MonoBehaviour腳本拖入“Source”
  • 使用您喜歡的枚舉名稱創建一個新的空腳本
  • 將您的枚舉目標腳本拖入“目標”
  • Select 我們要查找的字段類型
  • 最后點擊生成

這是一個小演示;)

我剛開始使用Example.cs

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

ExampleEnum.cs

public enum ExampleEnum
{

}

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM