Hi everyone,
In Unity 6.6, we’re deprecating the UNITY_64 and DEVELOPMENT_BUILD scripting symbols and introducing a new Player Setting, Managed Code Variant, to take the latter’s job. If you’ve ever guarded code with #if UNITY_64 or #if DEVELOPMENT_BUILD, you’ll need to update it eventually. In this post I’ll explain why these symbols were never as reliable as they looked, what’s replacing them, and how to migrate your code, including a behavior change that affects you even if you’ve never typed either symbol yourself.
Why we’re doing this
Here’s the uncomfortable truth: UNITY_64 and DEVELOPMENT_BUILD have never been reliable C# defines.
Whether a build is 32-bit or 64-bit, and whether it is a development or non-development build, is decided solely by the native engine binary (for example, UnityPlayer.dll on Windows or libunity.so on Android). Unity compiles your C# once when you build your project, but the resulting managed assemblies can then be reused across several different native binaries. A few examples:
- On Android, the 32-bit and 64-bit builds are built from the same managed code, compiled once.
UNITY_64simply loses its meaning. - With the Generate Visual Studio Solution build setting on Windows, you choose development/non-development configurations and x86/x64/Arm64 architectures later, inside Visual Studio. At the point you build your project from Unity, it hasn’t even been decided yet what kind of build(s) you’ll end up with.
- Modders routinely drop the development
UnityPlayer.dllinside shipped games to attach a profiler/debugger, or to get extra diagnostic information. Same managed code, suddenly a “development” build.
On top of that, UNITY_64 was simply never defined on iOS, Android, UWP, QNX, Embedded Linux (and a few others), so any code relying on it was already broken on more than half of the platforms we support. And DEVELOPMENT_BUILD was overloaded: some code used it for extra safety checks, some for profiler instrumentation, and some to (incorrectly) detect a development native binary. One symbol was being asked to mean three different things.
What’s changing
1. A new “Managed Code Variant” Player Setting (landed in 6000.6.0a5)
You can find it in Player Settings -> Other Settings -> Managed Code Variant, right below Managed Stripping Level. It decides which diagnostic symbols get baked into your C#, completely independently of the native binary. There are four levels, and each one is inclusive of the next:
| Variant | Defines | Optimized? |
|---|---|---|
| Debug | DEBUG, UNITY_ASSERTIONS, UNITY_ENABLE_CHECKS, UNITY_INCLUDE_INSTRUMENTATION, ENABLE_PROFILER |
No |
| Checked | UNITY_ASSERTIONS, UNITY_ENABLE_CHECKS, UNITY_INCLUDE_INSTRUMENTATION, ENABLE_PROFILER |
Yes |
| Instrumented | UNITY_INCLUDE_INSTRUMENTATION, ENABLE_PROFILER |
Yes |
| Release | (none of the above) | Yes |
The way to think about it, this splits the old DEVELOPMENT_BUILD into two clearer ideas:
UNITY_ENABLE_CHECKS-> heavyweight safety checks, assertions and validation (the “Checked” bucket).UNITY_INCLUDE_INSTRUMENTATION-> lightweight profiling, instrumentation and diagnostic logging (the “Instrumented” bucket).
You can also read and set the variant from build scripts via PlayerSettings.SetManagedCodeVariant(...).
2. UNITY_64 and DEVELOPMENT_BUILD are now deprecated (warnings landed in 6000.6.0a6)
Using either one in an #if or a [Conditional(...)] attribute now produces a Roslyn analyzer warning (UAC0008 for UNITY_64 and UAC0009 for DEVELOPMENT_BUILD) in the Console and in your IDE. In 6.6 the symbols still work, you’ve just got warnings nudging you to migrate. If you compile with warnings-as-errors, though, you’ll need to update the code now.
3. Coming in Unity 6.8: full removal. The symbols will stop being emitted and using them will become a hard compile error (we’re not dead set on this last step: depending on your feedback, we might reconsider and keep them as warnings even after removing them). Either way, 6.8 is the real deadline, so it’s worth fixing your code during the 6.6/6.7 window while the old and new symbols still coexist.
Documentation:
This affects you even if you never used these symbols
A lot of Unity’s own packages used DEVELOPMENT_BUILD internally, and we’ve moved all of them onto the new variant symbols. So their availability is now controlled by the Managed Code Variant, not by the Development Build checkbox, and Managed Code Variant defaults to Release, which turns all of them off. Affected engine features include:
- Scriptable Render Pipeline (URP/HDRP/Core): debug overlays, the Rendering Debugger runtime resources, Render Graph Viewer, render-graph validation, the magenta “incompatible objects” pass, Frame Debugger support, and the Volume panel are now gated by
UNITY_ENABLE_CHECKS(Debug/Checked). Render Graph profiling samplers, URP’s per-passScriptableRenderPass.profilingSampler, and HDRP’s dynamic-resolution overlay are gated byUNITY_INCLUDE_INSTRUMENTATION(Debug/Checked/Instrumented). Build-time stripping of debug shaders and rendering-debugger resources now follows the variant too, so a non-development Checked build keeps them and a Release build strips them. - Unity Physics: simulation integrity checks are now compiled in for the Debug/Checked variants (still overridable with
UNITY_PHYSICS_DISABLE_INTEGRITY_CHECKS). - Entities: Entities Journaling (the data behind the Entities Journaling window) is now compiled in for Debug/Checked/Instrumented variants (still overridable with
DISABLE_ENTITIES_JOURNALING). - Adaptive Performance: Apple and Android provider logging is now available in Debug/Checked/Instrumented variants.
Packages that ship independently of the Unity editor change the same way on Unity 6.6 and newer:
- AR Foundation: provider-returned trackable-change validation and misconfiguration warnings (for example, the warning shown when no reference image library is assigned) are now compiled in Debug/Checked variants.
- PolySpatial: assertions and the runtime scene validator are compiled in Debug/Checked variants, while statistics, debug object names, and verbose logging are compiled in Debug/Checked/Instrumented variants.
So if you make a Development Build and leave the variant at the default Release, you’ll be missing diagnostics you used to get for free. To get the old behavior back, set Managed Code Variant -> Checked in Player Settings; Checked turns on both the check and the instrumentation paths. Pick Debug instead if you also want DEBUG and unoptimized code for stepping through a debugger, or Instrumented if you want only informational logging or are trying to profile the builds.
There’s a small upside to the same change, too: non-development Release builds no longer drag in some URP debug-display shader variants they used to, so you may notice slightly smaller builds and lower shader-variant counts.
How to fix your code
Let’s start with UNITY_64. Since the bitness isn’t known when your scripts are compiled, the fix is to stop asking at compile time and check it at runtime instead, with IntPtr.Size:
// Before
#if UNITY_64
UseWidePointers();
#endif
// After
if (IntPtr.Size == 8)
UseWidePointers();
DEVELOPMENT_BUILD takes a little more thought, because it was doing more than one job. The first step is to work out which of its meanings your code actually relied on. If you were gating heavyweight safety checks, validation or assertions, that’s the “Checked” bucket, so use UNITY_ENABLE_CHECKS. If you were gating profiler instrumentation or diagnostic logging, that’s the “Instrumented” bucket, so use UNITY_INCLUDE_INSTRUMENTATION (or put it on a [Conditional] method):
// Safety checks / assertions / validation:
#if UNITY_ENABLE_CHECKS
SafetyChecks.Validate(data);
#endif
// Profiler instrumentation / debug logging:
#if UNITY_INCLUDE_INSTRUMENTATION
Profiler.BeginSample("MyThing");
#endif
// ...or on a method:
[Conditional("UNITY_INCLUDE_INSTRUMENTATION")]
static void LogDiagnostic(string msg) { ... }
There’s a third case worth calling out. Sometimes you genuinely want to know, at runtime, whether you’re running in a development build and here the variant symbols are not a substitute. Remember that a non-development builds can still be built with the Checked variant, so a #if UNITY_ENABLE_CHECKS block could end up compiling something into a shipping build that you never wanted there (logging that leaks auth tokens is the classic example). For that, reach for the runtime property Debug.isDebugBuild:
// Before
#if DEVELOPMENT_BUILD
Debug.LogError($"Rejecting connection: {payload}");
#endif
// After
if (Debug.isDebugBuild)
Debug.LogError($"Rejecting connection: {payload}");
Finally, if you were using BuildOptions.ForceEnableAssertions, that’s deprecated too: replace it with PlayerSettings.SetManagedCodeVariant(target, ManagedCodeVariant.Checked).
Package / Asset Store authors
If your code has to compile against Unity versions older than 6.6, the new symbols won’t always exist, so you can’t rely on them unconditionally. The pattern we used across our own packages is to gate on UNITY_6000_6_OR_NEWER and fall back to a runtime Debug.isDebugBuild check on older versions, keeping the diagnostic body written exactly once:
#if !UNITY_6000_6_OR_NEWER || UNITY_ENABLE_CHECKS
#if !UNITY_6000_6_OR_NEWER
if (Debug.isDebugBuild)
#endif
{
if (library == null)
Debug.LogWarning("No library assigned.");
}
#endif
And for [Conditional] methods, pick the attribute per version:
#if UNITY_6000_6_OR_NEWER
[Conditional("UNITY_INCLUDE_INSTRUMENTATION")]
#else
[Conditional("DEVELOPMENT_BUILD")]
#endif
static void LogDiagnostic(...) { ... }
TL;DR
UNITY_64andDEVELOPMENT_BUILDare deprecated now (6.6) and will be removed in 6.8.- Replace
UNITY_64with a runtimeIntPtr.Sizecheck. - Replace
DEVELOPMENT_BUILDwithUNITY_ENABLE_CHECKS(checks),UNITY_INCLUDE_INSTRUMENTATION(profiling/logging), orDebug.isDebugBuildwhen you really need a runtime development-build test. - The new Managed Code Variant setting drives all of this, and it defaults to Release so set it to Checked to get development-build-style diagnostics back.
I’m eager to hear your feedback and questions on this!
