{"slug": "unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way", "title": "Unity vs Unreal: 5 Things I Had to Relearn the Hard Way", "summary": "The article compares the key differences between Unity and Unreal Engine that developers must mentally adjust to when switching between them. It highlights that while the syntax difference (C# vs C++) is manageable, the real challenge lies in fundamental mismatches in garbage collection models and lifecycle management. Specifically, Unity's GC is a performance concern while Unreal's is a correctness concern, and their per-frame update functions (MonoBehaviour.Update vs AActor::Tick) have different lifecycle rules that can cause bugs if habits are directly ported.", "body_md": "The first time I opened Unreal after years of living in Unity, I sat there for a full ten minutes trying to figure out where the play button was. Not because I couldn't see it. Because I didn't trust that pressing it would do what I expected. Every muscle memory I'd built up — drag a script onto a GameObject, hit play, see the thing wiggle — was suddenly worthless. The viewport looked similar enough to lull me into a false sense of familiarity, and then five seconds later I was fighting the editor over what \"save\" even means.\n\nGoing the other direction is just as bad. Unreal devs who jump into Unity often spend their first week asking why the engine keeps eating their references on hot reload, or why the Inspector won't show their perfectly reasonable struct.\n\nI've shipped work in both engines at this point, and the honest truth is that the syntax barrier (C# vs C++) is the *easy* part. What actually slows you down is the dozens of small mental model mismatches that nobody warns you about until you've already lost a weekend to them. The engines look like they solve the same problems, so you assume the *shape* of the solution is the same. It isn't.\n\nHere are five things our studio had to genuinely rewire our brains around. Not engine trivia — the stuff that quietly costs you days if you don't catch it early.\n\n## 1. Garbage Collection Lives in Different Universes\n\nUnity gives you C# and the .NET GC. Unreal gives you C++ and a custom mark-and-sweep GC that only knows about objects you've explicitly told it about. These sound similar. They are not.\n\nIn Unity, you write a class, you new it up, and the GC takes care of it whenever it feels like. The classic gotcha is performance (allocations in `Update`\n\n= GC spikes), but the *correctness* model is straightforward. Everything's an object, everything's tracked.\n\nIn Unreal, the GC only tracks `UObject`\n\nsubclasses, and only through references it can see — meaning references marked with `UPROPERTY()`\n\n. If you store a `UObject*`\n\nas a raw pointer without that macro, the GC has no idea you care about it, and your pointer becomes a dangling reference the next sweep. The object literally gets collected out from under you.\n\n```\nUCLASS()\nclass AEnemy : public AActor {\n    GENERATED_BODY()\n\n    // Safe: GC knows you reference this\n    UPROPERTY()\n    UWeapon* EquippedWeapon;\n\n    // Time bomb: GC will happily delete this\n    UWeapon* SecretWeapon;\n};\n```\n\nThe mental rewire: in Unity, the GC is a *performance* concern. In Unreal, the GC is a *correctness* concern. You don't fight it for frame time, you fight it for object lifetime. Once that clicked, half of my \"why is my pointer null\" bugs disappeared.\n\nThe flip side: Unity devs jumping to Unreal often reach for `TWeakObjectPtr`\n\nreflexively because they're scared of the GC. Most of the time you just want a `UPROPERTY()`\n\n-marked pointer — it keeps the object alive *and* tracked. Save `TWeakObjectPtr`\n\nfor the case it's actually designed for: you want to reference something without preventing its destruction (a UI panel watching a target actor that might die first).\n\n## 2. Tick Is Not Update (And Lifecycle Is Not Awake)\n\nOn paper, `MonoBehaviour.Update()`\n\nand `AActor::Tick()`\n\nlook like the same idea. Per-frame callback, runs every frame, do your thing. In practice the lifecycle and scope rules around them are different enough that you can't just port habits across.\n\nUnity's `MonoBehaviour`\n\nhas a fairly chatty lifecycle (`Awake`\n\n, `OnEnable`\n\n, `Start`\n\n, `Update`\n\n, `LateUpdate`\n\n, `OnDisable`\n\n, `OnDestroy`\n\n) and most of those fire reliably in the editor too. You can lean on `Start`\n\nfor cheap initialization and forget about it.\n\nUnreal splits actor lifetime across `PostInitializeComponents`\n\n, `BeginPlay`\n\n, `Tick`\n\n, `EndPlay`\n\n, with components having their own `OnRegister`\n\n/ `InitializeComponent`\n\nflow. `BeginPlay`\n\nonly fires when the game actually starts, which catches a lot of Unity converts off guard the first time they try to do initialization in the constructor and wonder why nothing's wired up yet.\n\n```\nAEnemy::AEnemy() {\n    // Constructor runs for the CDO at engine init, and again for every instance.\n    // In both cases the world isn't valid — no gameplay state, no other actors.\n    // Defaults only; gameplay setup goes in BeginPlay.\n    PrimaryActorTick.bCanEverTick = true;\n}\n\nvoid AEnemy::BeginPlay() {\n    Super::BeginPlay();\n    // Now the world exists. Do gameplay setup here.\n}\n```\n\nAnd then there's the other direction, which bites just as hard. Unreal devs coming into Unity expect a staged lifecycle and instead get a flat one with a bunch of quietly-different rules. `Awake`\n\nruns early, sure, but it runs before the scene's other objects are guaranteed to be wired up — so anything that reaches across to a sibling MonoBehaviour belongs in `Start`\n\n, not `Awake`\n\n. `Start`\n\nitself doesn't fire when you `Instantiate`\n\nthe object; it waits for the first frame the object is *active*. `OnEnable`\n\nand `OnDisable`\n\nfire every time you toggle the GameObject, not just at creation — which is great for pooling and a trap if you treated them as one-shot setup hooks. And the C++ instinct to \"just put real init in the constructor\" has to be unlearned outright: Unity reserves the right to construct MonoBehaviours for serialization purposes when no scene exists, so gameplay code in the constructor is at best ignored, at worst crashes the editor on domain reload. On top of all that, `Update`\n\nruns at a variable rate; physics-coupled logic belongs in `FixedUpdate`\n\n, and that distinction is one of those things you only learn after your character starts vibrating at high frame rates.\n\nThe rewire: in Unity, lifecycle is *flat* and most things fire on a single timeline. In Unreal, lifecycle is *staged*: constructor for defaults, `BeginPlay`\n\nfor runtime, and `Tick`\n\nis opt-in (you have to enable it explicitly, which is honestly a feature, not a bug). Treat them as different shaped tools.\n\n## 3. Reflection Is the Whole Engine\n\nThis is the one I underestimated the longest.\n\nUnity uses `[SerializeField]`\n\n, `[Serializable]`\n\n, and `[SerializeReference]`\n\nto drive its inspector and YAML serializer. It feels lightweight — public fields serialize by default, private ones need a single attribute — but the rules quietly bite: polymorphic references need `[SerializeReference]`\n\n, custom classes need `[Serializable]`\n\nto appear in the inspector at all, and IL2CPP will happily strip any type only reached via reflection unless you preserve it with a link.xml or `[Preserve]`\n\n. It's lower-ceremony than Unreal, but it's not free.\n\nUnreal's `UPROPERTY()`\n\n, `UFUNCTION()`\n\n, and `UCLASS()`\n\nmacros look like the same idea but they're load-bearing structural pillars. They feed the reflection system that powers serialization, the editor, Blueprint exposure, network replication, GC tracking (see #1), and the property system. A field without `UPROPERTY`\n\nis invisible to all of it. A function without `UFUNCTION`\n\ncan't be called from Blueprint or RPCs. A class without `UCLASS`\n\ndoesn't exist to the engine at all.\n\n```\nUCLASS(Blueprintable)\nclass AEnemy : public AActor {\n    GENERATED_BODY()\n\n    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=\"Combat\")\n    float Health = 100.f;\n\n    UFUNCTION(BlueprintCallable, Category=\"Combat\")\n    void TakeHit(float Damage);\n};\n```\n\nEach meta specifier (`EditAnywhere`\n\n, `BlueprintReadWrite`\n\n, `Category`\n\n, `Replicated`\n\n, etc.) is a load-bearing instruction to a different subsystem. You'll find yourself reading the Unreal docs on property specifiers more than any other page.\n\nThe rewire: in Unity, attributes are sugar. In Unreal, macros are the API. When something doesn't show up in the editor, doesn't replicate, doesn't appear in Blueprint, or gets GC'd unexpectedly, 90% of the time it's because you forgot a specifier. Build the habit of writing the macro *first*, the field second.\n\n## 4. Editor Extension Is a Different Sport\n\nBoth engines let you extend the editor. Both will let you build custom inspectors, tool windows, asset processors. But the *shape* of how you do it diverges so hard that your tooling knowledge basically doesn't carry over.\n\nUnity editor scripting is mostly C# in an `Editor/`\n\nfolder, using `UnityEditor`\n\nAPIs, `EditorWindow`\n\n, `CustomEditor`\n\n, and either IMGUI for quick stuff or UI Toolkit (UXML/USS) for anything you'd actually want to maintain. It's the same language as your gameplay code, which makes it cheap to start writing. Half our internal tools at the studio started as a 50-line `EditorWindow`\n\nsomeone wrote during lunch.\n\nUnreal splits editor code into separate modules (you'll be editing `.Build.cs`\n\nfiles), and the UI is Slate, a declarative C++ DSL that looks like nothing else you've ever written. It's powerful, but the learning curve is real.\n\n```\n// Slate looks like this. Yes, really.\nSNew(SVerticalBox)\n+ SVerticalBox::Slot()\n  .AutoHeight()\n  [\n      SNew(STextBlock)\n      .Text(FText::FromString(\"Hello, tools\"))\n  ];\n```\n\nYou can avoid Slate for a while using Editor Utility Widgets (UMG in the editor) or Editor Utility Blueprints, which is honestly where I tell anyone starting out to live. Pure Slate is for when you need something that genuinely belongs in the editor chrome.\n\nThe rewire: in Unity, \"I'll write a quick editor tool\" is a fifteen-minute commitment. In Unreal, it's an afternoon if you go native, or fifteen minutes if you accept that Editor Utility Widgets are good enough for 80% of cases. Pick the right tier for the job. Don't try to write Slate for a button that just renames some assets.\n\n## 5. The Asset Pipeline Has Opinions\n\nThe thing nobody tells you: your project structure isn't just organization. It's a contract with the build system.\n\nUnity treats anything in `Assets/`\n\nas fair game. You move things around freely, the `.meta`\n\nfiles keep references intact, AssetBundles are an opt-in deployment thing you set up when you need streaming or DLC. The pipeline is forgiving. Maybe too forgiving. Most Unity projects I've inherited had `Assets/`\n\nfolders that looked like a teenager's desktop.\n\nUnreal's `Content/`\n\ndirectory is a more structured place. Every asset is a `.uasset`\n\nwith cooked variants, references are tracked by path, and moving things around outside the editor will absolutely break your references in ways that aren't always obvious until you try to package. The cook step at build time is a whole second pipeline you have to think about: what's referenced, what gets included, what gets stripped.\n\n```\n// Unity                  // Unreal\nAssets/                   Content/\n  Scripts/                  Blueprints/\n  Prefabs/                  Maps/\n  Scenes/                   Materials/\n  Materials/                Characters/\n  Resources/                ...\n                          (Cooked output → Saved/Cooked/<Platform>/)\n```\n\nTwo specific traps worth flagging early:\n\n- Unity's\n`Resources/`\n\nfolder is convenient and almost always the wrong answer at scale. Anything in there ships in your build whether you reference it or not. For anything beyond a prototype, move to Addressables (or whichever streaming asset system your Unity version blesses — the recommendation has shifted more than once). - Unreal's hard-vs-soft references (\n`UPROPERTY()`\n\nof`UObject*`\n\nvs`TSoftObjectPtr<>`\n\n) determine what gets pulled into memory when. Get this wrong on a mid-size project and your initial map load time will quietly balloon.\n\nThe rewire: Unity's pipeline lets you defer thinking about deployment until late. Unreal's pipeline forces you to think about it early. Neither approach is wrong, but pretending you're in the other engine's model is how you ship a 4 GB build that should've been 800 MB.\n\n## Closing Thoughts\n\nThe pattern across all five of these: the engines look like they solve the same problems with slightly different syntax, and that's the trap. They actually have different *philosophies* about who owns what — the GC, the editor, the build, the lifecycle, the reflection system. Once you stop trying to translate Unity habits into Unreal (or vice versa) and start learning each engine's actual mental model, everything gets faster.\n\nThe other thing this taught me: every time I assumed two systems were \"basically the same,\" I was about to lose a day. Now when I jump into something new, I make myself find the *one* thing that's structurally different from what I'm used to, before I touch any code. Saves a lot of grief.\n\nIf you've made the jump in either direction, I'd genuinely love to hear what tripped you up the worst. The five above are the ones that hit our studio hardest, but I'm sure there's a sixth waiting for the next person who switches engines. Probably something about input systems, knowing my luck.\n\nThis blog's where we plan to write down the small, unglamorous stuff we keep learning the hard way. No newsletter pitch — just more of these when we hit the next one.", "url": "https://wpnews.pro/news/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way", "canonical_source": "https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-1kej", "published_at": "2026-05-22 15:42:47+00:00", "updated_at": "2026-05-22 16:05:21.061344+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Unity", "Unreal"], "alternates": {"html": "https://wpnews.pro/news/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way", "markdown": "https://wpnews.pro/news/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way.md", "text": "https://wpnews.pro/news/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way.txt", "jsonld": "https://wpnews.pro/news/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way.jsonld"}}