Unity Works in the Editor but Not in the Build? Here's Where to Look
The bug report that ages you fastest in this industry is four words long: “works fine in editor.”
Mine arrived on a Monday. We’d shipped a puzzle game on Friday, and over the weekend the daily-reward popup had simply stopped existing on Android. Not crashing. Not erroring. Just absent, as if we’d never built it. In the editor it appeared every single time, which meant the one tool I use to debug things was the one place the bug refused to live.
The cause turned out to be IL2CPP code stripping (more on that in a minute), but the deeper lesson stuck with me: the editor and the built player are different programs, and treating editor behavior as proof of build behavior is how these bugs get shipped. This post is the checklist I now run, in the order I run it.
The editor and the player are different programs
In the editor your code runs on Mono with JIT compilation, every assembly fully loaded, every asset in the project available on demand, on a file system (Windows or macOS) that forgives you for getting the casing wrong.
In an IL2CPP player, your code was compiled ahead of time, aggressively stripped of anything the linker considered unreachable, with only the packed assets present, often on a file system (Android, Linux) that is strictly case-sensitive.
Every difference in that paragraph is a category of bug. Let’s go through them.
Check 1: IL2CPP stripped your code
Managed stripping walks the call graph from your entry points and deletes whatever it can’t prove is used. The catch: anything reached only through reflection is invisible to that analysis. Serialization libraries are the classic victim, since JSON deserializers populate your model classes via reflection. The linker sees no direct calls, strips the setters or the whole type, and your config deserializes into a default object. Silently.
My reward popup died exactly this way: the UI flow was instantiated through reflection from a config string, so the linker threw the class away.
Symptoms to watch for: features that are simply absent in builds, MissingMethodException in the logs, or deserialized objects full of default values. The fix is a link.xml at the root of Assets, telling the linker what to leave alone:
<linker>
<assembly fullname="Assembly-CSharp">
<type fullname="MyGame.Rewards.*" preserve="all"/>
</assembly>
</linker>
For individual members, the [Preserve] attribute from UnityEngine.Scripting does the same job closer to the code. And if you’re not sure stripping is the culprit, temporarily drop Managed Stripping Level to Minimal in Player Settings and rebuild; if the bug vanishes, you have your answer.
Check 2: case sensitivity
Resources.Load("UI/Popup") when the folder is actually named popup works perfectly on Windows and macOS and returns null on Android. Nothing logs, because Unity considers “asset not found” a valid answer, and your null check (you have one, right?) quietly takes the fallback path.
The same trap covers StreamingAssets paths and any load-by-string system. The cheap insurance is a convention: every asset addressed by string gets an all-lowercase name, enforced in review. While you’re in there, remember that on Android StreamingAssets lives inside the APK, so File.IO can’t read it; you need UnityWebRequest or Addressables.
Check 3: Build Settings drift
Build Settings is a list of file paths, and nothing keeps it honest. Someone deletes or renames a scene; the stale entry stays. Someone adds a new scene but forgets the list entirely, and nobody notices because pressing Play in the editor runs whatever scene is open, masking the problem for weeks.
Then SceneManager.LoadScene("Boss") throws at runtime, on the one code path QA didn’t walk. I’ve watched this survive all the way to a store submission.
A related trap: loading scenes by build index. Reorder the list and LoadScene(3) now loads a different scene, with no error at all. Load by name or by reference, and validate the scene list before every build rather than after.
Check 4: editor-only code leaked into your logic
Anything in an Editor folder or behind #if UNITY_EDITOR doesn’t exist in the player. That’s the point of it, but it goes wrong quietly when runtime code develops a dependency on an editor-only side effect. A utility that touches AssetDatabase, a debug path someone “temporarily” routed real logic through, an Application.dataPath write that works on desktop and fails on device where the data folder is read-only (use Application.persistentDataPath for anything you write).
The tell is a feature that works in the editor and in nobody else’s mental model. Grep your runtime assemblies for UnityEditor and #if UNITY_EDITOR and read what you find with suspicion.
Learn to read the actual logs
Every one of these failures explains itself in a log almost nobody opens.
On Android: adb logcat -s Unity gives you just Unity’s output, stack traces included. On iOS, the Xcode console while the device runs. Desktop players write to ~/Library/Logs/<Company>/<Product>/Player.log on macOS and %USERPROFILE%\AppData\LocalLow\<Company>\<Product>\Player.log on Windows.
Build with Development Build and Script Debugging enabled while you’re hunting, and you’ll get full managed stack traces instead of stripped ones. And make your first device build in week one of the project, not week eight. Every check in this post gets cheaper the earlier it runs.
Catch the detectable half before you ever build
Some of these bugs genuinely need a device to reproduce; stripping is the big one. But a surprising share of “broken in the build” reports trace back to things that were statically detectable the whole time: deleted scenes still in Build Settings, missing scripts, serialized references pointing at deleted assets, broken prefabs. Those don’t need a build to be found. They need someone (or something) to look.
That something is why I built RefSafe Pro, which scans the whole project for that class of breakage and can run as a pre-build hook, so a stale Build Settings entry stops a build instead of a release. This post covers one of five recurring time sinks I’ve written about; the other four are in the Unity problems that eat entire weeks.
Common questions
Why does my Unity game work in the editor but not in the build?
Because the editor and the built player are different programs: the editor runs Mono with every assembly loaded and every asset available, while an IL2CPP build is ahead-of-time compiled, stripped of unreachable code, and limited to packed assets. Check IL2CPP stripping, path casing, the Build Settings scene list, and editor-only code, in that order.
How do I stop IL2CPP from stripping my code?
Add a link.xml file at the root of Assets that tells the linker which assemblies or types to preserve, or mark individual members with the Preserve attribute from UnityEngine.Scripting. To confirm stripping is the culprit first, drop Managed Stripping Level to Minimal in Player Settings and rebuild; if the bug disappears, you have your answer.
Why does Resources.Load return null in my build but work in the editor?
Most often it is path casing. Windows and macOS file systems forgive wrong-case paths, but Android and Linux are strictly case-sensitive, so a path like UI/Popup fails when the folder is actually named popup. Unity treats a missing asset as a valid null result, so nothing gets logged.
Where do I find Unity player logs to debug a build?
On Android, run adb logcat -s Unity to see just Unity's output with stack traces. On iOS, watch the Xcode console while the device runs. Desktop players write a Player.log file under the Library/Logs folder on macOS and the AppData LocalLow folder on Windows, named after your company and product.
Why does SceneManager.LoadScene fail only in a build?
The scene is probably missing from the Build Settings list, or the list has drifted after a rename or reorder. Pressing Play in the editor runs whatever scene is open, which masks the problem for weeks. Load scenes by name rather than build index, and validate the scene list before every build.