How to Detect If the Unity Engine Is Available
Sometimes we write code that’s meant to be run outside of the Unity engine. This could be anything from unit tests being run in MonoDevelop or Visual Studio to shared code that’s used on a multiplayer server. Regardless, the Unity engine isn’t available for use unless you’re running in the editor or a deployed build. This means you’ll have problems whenever you access the Unity engine via Debug.Log
, GameObject
, or MonoBehaviour
. Today’s article shares some quick tips that enable you to tweak your code so that you can detect whether the Unity engine is available for use. Read on to learn how!
Unity has a nice page documenting various preprocessor symbols that they’ve defined in various situations. For example, you might write some code like this:
class InputHandler { void Update() { #if UNITY_IOS || UNITY_ANDROID // ... do touchscreen handling #else // ... do keyboard and mouse handling #endif } }
These are really handy symbols as they allow us to detect what code should be compiled at compile time. There’s zero runtime overhead to #if
since the compiler evaluates it and strips out the irrelevant code. So when you compile the above script on iOS or Android it only compiles this:
class InputHandler { void Update() { // ... do touchscreen handling } }
The other code is simply removed, so it doesn’t add anything to your app size and doesn’t cost any runtime performance.
Ideally, we’d have something similar to detect if the Unity engine is available at all. For example, say we want to use Unity’s built-in JSON serializer JsonUtility
. Since it relies on the Unity engine, we can’t use it when we’re running code from outside the Unity editor or deployed builds. That means we’ll get errors like these:
System.MissingMethodException : Attempted to access a missing method System.Security.SecurityException was unhandled Message=ECall methods must be packaged into a system module.
There are plenty of other JSON libraries available that we can fall back to in these circumstances. So we naturally want to write some code like this:
public static class JsonLibrary { public static string ToJson(object obj) { #if UNITY_ENGINE_AVAILABLE return UnityEngine.JsonUtility.ToJson(obj); #else return LitJson.JsonMapper.ToJson(obj); #endif } public static T FromJson<T>(string json) { #if UNITY_ENGINE_AVAILABLE return UnityEngine.JsonUtility.FromJson<T>(json); #else return LitJson.JsonMapper.FromJson<T>(json); #endif } }
This JsonLibrary
class simply abstracts JsonUtility
when the Unity engine is available and LitJSON’s JsonMapper
when the Unity engine isn’t available.
Unfortunately, UNITY_ENGINE_AVAILABLE
isn’t defined by Unity. When you open the C# solution in an IDE like MonoDevelop or Visual Studio, Unity defines all of the symbols such as UNITY_EDITOR
and UNITY_ANDROID
in the compiler settings for the solution. So you can’t simply use UNITY_EDITOR
or any other such symbol as a proxy for the Unity engine being available since it’s always there.
One alternative would be to modify the C# solution that Unity creates for us. We could define a UNITY_ENGINE_NOT_AVAILABLE
symbol and reverse our #if
code, but we’d have to re-define the symbol every time Unity overwrote the solution. It’s an annoying manual step and we can do better.
We could also define the symbol in the “Scripting Define Symbols” section of “Player Settings”. This also isn’t quite what we want because all symbols defined here are added to the generated C# solution. We still wouldn’t have a way to know whether the Unity engine is available for use.
This leads to the real trick of today’s article. There are two files you can add to your Unity project’s Assets
directory: smcs.rsp
and gmcs.rsp
. These are text files where you can define extra parameters to pass to the C# compiler. That happens every time Unity compiles your project. Crucially, these parameters are not added to the C# solution that Unity generates.
So we now have two kinds of compilation. The first is Unity’s automatic compilation that uses smcs.rsp
and gmcs.rsp
. This is where the Unity engine is available. The second type of compilation is from an IDE that’s building the C# solution that Unity generates. This is where the Unity engine isn’t available.
The only step left is to define the UNITY_ENGINE_AVAILABLE
symbol in smcs.rsp
and gmcs.rsp
. Again, they’re just text files so create or modify them to include this one line:
-define:UNITY_ENGINE_AVAILABLE
With that in place the above code would work just fine. We’d have a JsonLibrary
that uses the Unity engine when available and falls back to alternatives when it’s not. You can use the same for logging since the Debug
class won’t work outside of Unity and many other types of code. Hopefully you’ll find this useful in your projects!
#1 by benjamin guihaire on December 5th, 2016 ·
great idea, and This allow to solve another problem: when you add unsafe code in Unity, you need to add -unsafe in smcs.rsp and gmcs.rsp,
The problem is when you debug with monodevelop, you have to manually enable “unsafe” each time the solution change in monodevelop, which is very annoying.
So we can use your trick to go around that: in smcs.rsp
-unsafe
-define:UNSAFE_ON
and in the C# code using unsafe:
#if UNSAFE_ON
unsafe void MyFunction()
{ //with some unsafe code
}
#else
void MyFunction()
{ //alternate implementation without unsafe, which will make mono develop happy
}
#endif
#2 by jackson on December 5th, 2016 ·
Good tip! You can also use these files to make the compiler treat warnings as errors. Just add this:
-warnaserror+
#3 by benjamin guihaire on September 7th, 2017 ·
awesome tip, thanks !