unity-game-enginec-preprocessorconstantsglobalconditional-compilation

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


I know this was aksed and answered a a couple of times e.g. Solution-wide #define, Is There anyway to #define Constant on a Solution Basis? and How to define a constant globally in C# (like DEBUG).

But in my case I can not use any of the suggested methods:

I'm writing on different "modules" (or plugins if you want so) for UnityProjects (kind of a package providing a certain functionality). The idea is that a developer can load a certain "module" to use in his project by importing a UnityPackage with all scripts and resources in it.

But some of these modules themselves depend on other modules. So what I tried so far was having a class Constants in each module with seperated namespaces and preprocessor definitions.

Module 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
        }
    }

Module 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

But ofcourse this cannot work like this since #defines are not global but only in the current file.

Problem

For this whole idea of modules and a simple "load your modules" I can not ask the user to first make changes to the project or solution settings how e.g. suggested by this answer but instead have to use only the (c#) resources that come imported with the UnityPackage (at least with my current know-how).

Is there any way to somehow set/define those constants for the entire Unity-Project by only importing the module's UnityPackage?


Edit:

I could find a solution for 1 definition in Unity using Assets/msc.rsp. But this still wouldn't work for multiple modules since they would have to write into the same file.


Solution

  • After a lot of searches I've finally been able to put together a surprisingly simple solution I'ld like to share with you:


    InitializeOnLoadMethod

    Unity has an attribute [InitializeOnLoadMethodAttribute]. It tells Unity to run a method as soon as


    Scripting Define Symbols

    Further Unity actually has project wide defines in the PlayerSettings.

    enter image description here

    And the good part is: We also have access to them via scripting API:


    So what I did now is the following

    Module A

    This module has no dependencies but just defines a "global define" in the PlayerSettings. I placed this script somewhere e.g. in Assets/ModuleA/Editor (important is the last folder's name).

    #if !MODULE_A
        using System.Linq;
        using UnityEditor;
        
        namespace ModuleA
        {
            // Will be initialized on load or recompiling
            internal static class Startup
            {
                [InitializeOnLoadMethod]
                private static void Init()
                {
                    // 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);
                }
            }
        }
    #endif
    

    Module B

    This module depends on Module A. So itself defines a "global define" (so later Modules can check their dependecies on Module B) but additionally it checks first, if Module A is imported. If Module A is missing, it prints an error to the Debug Console.

    (You could as well throw a compiler error using #error SOME TEXT, but for some reason this is not capable of printing out the URL correctly so I decided for the Debug.LogError)

    I placed this script somewhere e.g. in Assets/ModuleB/Editor

    #if !MODULE_B
        using UnityEditor;
    #if MODULE_A
        using System.Linq;
    #esle
        using UnityEngine;
    #endif
        
        namespace ModuleB
        {
            internal static class Startup
            {
                [InitializeOnLoadMethod]
                private static void Init()
                {
    #if !MODULE_A
                    Debug.LogError($"! Missing Module Dependency !\nThe module MODULE_B depends on the module MODULE_A.\n\nDownload it from 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
                }
            }
        }
    #endif
    

    So later in other scripts of Module B I have two options (both do basically the same)


    On this way I can completely check all dependencies between certain modules which is awesome. The only two flaws I saw until now are:

    But well - which solution is perfect?