我可以在没有项目设置的情况下在 c# 代码中 #define 一个常量解决方案吗?

Can I #define a constant solutionwide within c# code without project settings?

我知道有人问过这个问题并回答了几次,例如 Solution-wide #define, Is There anyway to #define Constant on a Solution Basis? and How to define a constant globally in C# (like DEBUG).

但就我而言,我无法使用任何建议的方法:

我正在为 UnityProjects(一种提供特定功能的包)编写不同的 "modules"(或插件,如果你愿意的话)。这个想法是,开发人员可以通过导入一个包含所有脚本和资源的 UnityPackage 来加载某个 "module" 以在他的项目中使用。

但是其中一些模块本身依赖于其他模块。所以到目前为止我尝试的是在每个模块中有一个 class Constants,具有单独的名称空间和预处理器定义。

模块 A

#if !MODULE_A
#define MODULE_A   // BUT I WOULD NEED THIS GLOBAL NOT ONLY HERE
#endif

    namespace Module_A
    {
        public static class Constants
        {
            // some constants for this namespace here
        }
    }

模块 B

#if !MODULE_B
#define MODULE_B    // BUT I WOULD NEED THIS GLOBAL NOT ONLY HERE
#endif

#if !MODULE_A    // WILL BE NOT DEFINED OFCOURSE SINCE #define IS NOT GLOBAL
#error Module A missing!
#else

    namespace Module_B
    {
        public static class Constants
        {
            // some constants for this namespace here
        }

        // and other code that might require Module A
    }
#endif

但是当然这不能像这样工作,因为 #defines 不是全局的,而是只在当前文件中。

问题

对于整个模块的想法和一个简单的 "load your modules" 我不能要求用户首先对项目或解决方案设置进行更改,例如this answer 建议,但必须仅使用随 UnityPackage 导入的 (c#) 资源(至少以我目前的专业知识)。

有没有办法通过只导入模块的 UnityPackage 以某种方式 set/define 整个 Unity 项目的这些常量?


编辑:

我可以使用 Assets/msc.rsp 在 Unity 中找到 1 定义的解决方案。但这仍然不适用于多个模块,因为它们必须写入同一个文件。

一般来说,我在 C# 中解决这个问题的方法是定义所有模块都包含的一组通用接口。我 认为 您可以通过将每个模块的文件放在同一位置来使用 Unity 来做到这一点,从而允许以后的安装覆盖那些相同的文件(显然,具有相同的内容)。然后,您可以放置​​公开属性的编辑器控件来保存这些接口的实例,然后将它们连接到 UI 中。您将测试这些属性的值 null 以确定缺少哪些属性。

Common.cs:

public interface IModuleA {}
public interface IModuleB {}

ModuleA.cs

public class ModuleA : IModuleA {}

ModuleB.cs

public class ModuleB : IModuleB
{
    public IModuleA ModuleAInstance {get; set;}

    private bool IsModuleAPresent() 
    {
        return !ModuleAInstance == null;
    }
}

解决它的理想方法是使用包管理器和适当的依赖注入,但使用 Unity 做到这一点并不简单。

经过大量搜索,我终于能够整理出一个非常简单的解决方案,我想与您分享:


InitializeOnLoad

Unity 有一个属性 [InitializeOnLoad]。它告诉 Unity 在

时根据 class 进行初始化
  • Unity 启动
  • 重新编译脚本后 => 同样在导入带有脚本的新 unitypackage 后

静态构造函数

在他们的 Running Editor Code On Launch 示例中,他们展示了如何将其与 static 构造函数相结合。

来自 static-constructors:

A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.

虽然通常您仍然需要创建 class 的实例,但当 class 初始化时,静态构造函数立即 "instanciated/executed",我们使用 [InitializeOnLoad]属性。


脚本定义符号

进一步的 Unity 实际上 PlayerSettings.

中有 项目范围的定义

好的部分是:我们也可以通过脚本访问它们 API:


所以我现在做的是下面的

模块 A

此模块没有依赖项,只是在 PlayerSettings 中定义了一个 "global define"。我把这个脚本放在某个地方,例如在 Assets/ModuleA/Editor 中(重要的是最后一个文件夹的名称)。

using System.Linq;
using UnityEditor;

namespace ModuleA
{
    // Will be initialized on load or recompiling
    [InitializeOnLoad]
    public static class Startup
    {
        // static constructor is called as soon as class is initialized
        static Startup()
        {
            #region Add Compiler Define

            // Get the current defines
            // returns a string like "DEFINE_1;DEFINE_2;DEFINE_3"
            var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
            // split into list just to check if my define is already there
            var define = defines.Split(';').ToList();

            if (!define.Contains("MODULE_A")
            {
                // if not there already add my define 
                defines += ";MODULE_A";
            }

            // and write back the new defines
            PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines);

            #endregion
        }
    }
}

模块 B

此模块依赖于 Module A。所以它自己定义了一个 "global define"(这样以后模块可以检查它们对 Module B 的依赖)但是另外它首先检查,如果模块 A 被导入。如果缺少 Module A,它会向调试控制台打印错误。

(您也可以使用 #error SOME TEXT 引发编译器错误,但由于某些原因,这无法正确打印出 URL,所以我决定使用 Debug.LogError

我把这个脚本放在某个地方,例如在 Assets/ModuleB/Editor

#if MODULE_A
using System.Linq;
#endif
using UnityEditor;
#if !MODULE_A
using UnityEngine;
#endif

namespace ModuleB
{
    // Will be initialized on load or recompiling
    [InitializeOnLoad]
    public static class Startup
    {
        // static constructor is called as soon as class is initialized
        static Startup()
        {
#if !MODULE_A
            Debug.LogErrorFormat("! Missing Module Dependency !" +
                                 "\nThe module {0} depends on the module {1}." +
                                 "\n\nDownload it from {2} \n",
                "MODULE_B",
                "MODULE_A",
                "https://Some.page.where./to.find.it/MyModules/ModuleA.unitypackage"
                );
#else
            // Add Compiler Define

            var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
            var define = defines.Split(';').ToList();

            if (!define.Contains("MODULE_B"))
            {
                defines += ";MODULE_B";
            }

            PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines);

#endif
        }
    }
}

所以稍后在 模块 B 的其他脚本中 我有两个选择(两者基本相同)

  • 我可以到处检查 #if MODULE_A 以准确检查此脚本所依赖的模块

  • 或者我可以改为检查 #if MODULE_B 而不是用一行检查 所有 依赖项是否满足,否则我不定义 MODULE_B.


通过这种方式,我可以完全检查某些模块之间的所有依赖关系,这太棒了。到目前为止我看到的唯一两个缺陷是:

  • 我们必须知道每个模块的定义(例如MODULE_A)看起来如何,如果将来更改它必须更改所有依赖模块
  • 如果模块从项目中删除,"global define" 不会被删除

但是 - 哪个解决方案是完美的?