繁体   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