0

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.

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • 1
    What is the problem you are trying to solve? Are global constants the requirement, or are they a "solution"? From your example it looks like at least one requirement is that Module B depends on Module A and should throw an error if A is not provided. – Colin Young Sep 26 '18 at 12:21
  • Sorry if my explanation is hard to understand. I modificated my example snippets a little. But yes as you say it: If I have imported Module B instead of throwing all compiler errors from code where Module A would be required I want to disable compiling of the entire Module B and instead show the error that Module A has to be imported. – derHugo Sep 26 '18 at 13:25
  • This is a design-time solution you're looking for? What about something in the editor? e.g. a property that looks for the dependencies, and displays a list of the missing ones? – Colin Young Sep 26 '18 at 18:07
  • I actually tried something like that, too but the problem was that the code for this extra window/dialog to pop up didn't even compile because the compiler stopped already at the errors of missing classes from the required module ... Is there a way to check if a certain class is somehwere defined without causing an compiler error? – derHugo Sep 27 '18 at 09:07

2 Answers2

3

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


InitializeOnLoad

Unity has an attribute [InitializeOnLoad]. It tells Unity to initialize according class as soon as

  • Unity is launched
  • After any re-compiling of scripts => also after importing a new unitypackage with scripts

static Constructor

In their Running Editor Code On Launch example, they show, how to combine this with a static constructor.

From static-constructors:

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

While usually you still would have to create an instance of the class, the static constructor is "instanciated/executed" instantly when the class is initliazed, which we force using the [InitializeOnLoad] attribute.


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).

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

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

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

  • I can either check everywhere #if MODULE_A to check exactly the module this script relies on

  • or I can instead check #if MODULE_B to rather check with one line if all dependecies are fulfilled since otherwise I don't define MODULE_B.


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

  • We have to know how the define (e.g. MODULE_A) looks like for every module and if it is changed in the future it has to be changed in all depending modules as well
  • The "global define" isn't getting removed in case the module is deleted from the project

But well - which solution is perfect?

derHugo
  • 83,094
  • 9
  • 75
  • 115
1

In general, the way I would solve this problem in C# is by defining a common set of interfaces that all your modules would contain. I think you can do this with Unity by placing the files from each module in the same location, thus allowing later installations to overwrite those same files (with, obviously, the same content). You would then put editor controls that expose properties to hold instances of those interfaces and then wire them up in the UI. You would test those properties for a value of null to determine which ones are missing.

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

The ideal way to solve it would be with a package manager and proper dependency injection, but doing that with Unity is not straightforward.

Colin Young
  • 3,018
  • 1
  • 22
  • 46
  • Oh I get your idea and I think it sounds interesting. Well it would be necessary to update all modules if there is a new module (in order to have that same file in all of them) but maybe I can live with that. I'll test that approach asap but you get my upvote now ;) – derHugo Sep 28 '18 at 20:40
  • If you structure your source so that all module packages are built to pull the common files from the same location, that should simplify things. You're getting into the world of build engineering and software configuration management now... – Colin Young Sep 29 '18 at 00:17
  • hey check [my answer](https://stackoverflow.com/a/53002526/7111561)! I was able to come up with a complete relyable solution with a bit less maintainance. It is only Unity specific but works like a charm. However thanks for your efforts – derHugo Nov 06 '18 at 07:02