我可以在没有项目设置的情况下在 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
但是当然这不能像这样工作,因为 #define
s 不是全局的,而是只在当前文件中。
问题
对于整个模块的想法和一个简单的 "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
构造函数相结合。
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" 不会被删除
但是 - 哪个解决方案是完美的?
我知道有人问过这个问题并回答了几次,例如 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
但是当然这不能像这样工作,因为 #define
s 不是全局的,而是只在当前文件中。
问题
对于整个模块的想法和一个简单的 "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 在
- Unity 启动
- 重新编译脚本后 => 同样在导入带有脚本的新 unitypackage 后
静态构造函数
在他们的 Running Editor Code On Launch 示例中,他们展示了如何将其与 static
构造函数相结合。
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" 不会被删除
但是 - 哪个解决方案是完美的?