<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Game Dev Notes (Korea)</title>
    <description>The latest articles on DEV Community by Game Dev Notes (Korea) (@gamedevnotes).</description>
    <link>https://dev.to/gamedevnotes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946379%2Fccc40d7e-1681-4ad6-bad4-2be54a5f3b7e.png</url>
      <title>DEV Community: Game Dev Notes (Korea)</title>
      <link>https://dev.to/gamedevnotes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gamedevnotes"/>
    <language>en</language>
    <item>
      <title>Unity vs Unreal: 5 Things I Had to Relearn the Hard Way</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:00:02 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-4gdj</link>
      <guid>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-4gdj</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Going 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.&lt;/p&gt;

&lt;p&gt;I've shipped work in both engines at this point, and the honest truth is that the syntax barrier (C# vs C++) is the &lt;em&gt;easy&lt;/em&gt; 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 &lt;em&gt;shape&lt;/em&gt; of the solution is the same. It isn't.&lt;/p&gt;

&lt;p&gt;Here 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Garbage Collection Lives in Different Universes
&lt;/h2&gt;

&lt;p&gt;Unity 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.&lt;/p&gt;

&lt;p&gt;In 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 &lt;code&gt;Update&lt;/code&gt; = GC spikes), but the &lt;em&gt;correctness&lt;/em&gt; model is straightforward. Everything's an object, everything's tracked.&lt;/p&gt;

&lt;p&gt;In Unreal, the GC only tracks &lt;code&gt;UObject&lt;/code&gt; subclasses, and only through references it can see — meaning references marked with &lt;code&gt;UPROPERTY()&lt;/code&gt;. If you store a &lt;code&gt;UObject*&lt;/code&gt; as 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Safe: GC knows you reference this&lt;/span&gt;
    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;EquippedWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Time bomb: GC will happily delete this&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SecretWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental rewire: in Unity, the GC is a &lt;em&gt;performance&lt;/em&gt; concern. In Unreal, the GC is a &lt;em&gt;correctness&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The flip side: Unity devs jumping to Unreal often over-use &lt;code&gt;TWeakObjectPtr&lt;/code&gt; because they're scared of the GC. Don't. Just use &lt;code&gt;UPROPERTY()&lt;/code&gt; and stop overthinking it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tick Is Not Update (And Lifecycle Is Not Awake)
&lt;/h2&gt;

&lt;p&gt;On paper, &lt;code&gt;MonoBehaviour.Update()&lt;/code&gt; and &lt;code&gt;AActor::Tick()&lt;/code&gt; look 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.&lt;/p&gt;

&lt;p&gt;Unity's &lt;code&gt;MonoBehaviour&lt;/code&gt; has a fairly chatty lifecycle (&lt;code&gt;Awake&lt;/code&gt;, &lt;code&gt;OnEnable&lt;/code&gt;, &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;LateUpdate&lt;/code&gt;, &lt;code&gt;OnDisable&lt;/code&gt;, &lt;code&gt;OnDestroy&lt;/code&gt;) and most of those fire reliably in the editor too. You can lean on &lt;code&gt;Start&lt;/code&gt; for cheap initialization and forget about it.&lt;/p&gt;

&lt;p&gt;Unreal splits actor lifetime across &lt;code&gt;PostInitializeComponents&lt;/code&gt;, &lt;code&gt;BeginPlay&lt;/code&gt;, &lt;code&gt;Tick&lt;/code&gt;, &lt;code&gt;EndPlay&lt;/code&gt;, with components having their own &lt;code&gt;OnRegister&lt;/code&gt; / &lt;code&gt;InitializeComponent&lt;/code&gt; flow. &lt;code&gt;BeginPlay&lt;/code&gt; only 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor runs at CDO creation. World is NOT valid here.&lt;/span&gt;
    &lt;span class="n"&gt;PrimaryActorTick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bCanEverTick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Super&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Now the world exists. Do gameplay setup here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then there's the other direction: Unreal devs in Unity assume &lt;code&gt;Update&lt;/code&gt; runs at a consistent rate. It does not. Variable frame time is the default; you want &lt;code&gt;FixedUpdate&lt;/code&gt; for physics-coupled logic, and the difference between the two is one of those things you only learn after your character starts vibrating at high frame rates.&lt;/p&gt;

&lt;p&gt;The rewire: in Unity, lifecycle is &lt;em&gt;flat&lt;/em&gt; and most things fire on a single timeline. In Unreal, lifecycle is &lt;em&gt;staged&lt;/em&gt;: constructor for defaults, &lt;code&gt;BeginPlay&lt;/code&gt; for runtime, and &lt;code&gt;Tick&lt;/code&gt; is opt-in (you have to enable it explicitly, which is honestly a feature, not a bug). Treat them as different shaped tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reflection Is the Whole Engine
&lt;/h2&gt;

&lt;p&gt;This is the one I underestimated the longest.&lt;/p&gt;

&lt;p&gt;Unity uses &lt;code&gt;[SerializeField]&lt;/code&gt; and friends to expose fields to the inspector and serialization. It's lightweight, attribute-driven, and mostly stays out of your way. You can write a class with zero attributes and Unity will still happily serialize public fields by default.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;UPROPERTY()&lt;/code&gt;, &lt;code&gt;UFUNCTION()&lt;/code&gt;, and &lt;code&gt;UCLASS()&lt;/code&gt; macros 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 &lt;code&gt;UPROPERTY&lt;/code&gt; is invisible to all of it. A function without &lt;code&gt;UFUNCTION&lt;/code&gt; can't be called from Blueprint or RPCs. A class without &lt;code&gt;UCLASS&lt;/code&gt; doesn't exist to the engine at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Blueprintable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditAnywhere&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlueprintReadWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;UFUNCTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlueprintCallable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;TakeHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Damage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each meta specifier (&lt;code&gt;EditAnywhere&lt;/code&gt;, &lt;code&gt;BlueprintReadWrite&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Replicated&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;first&lt;/em&gt;, the field second.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Editor Extension Is a Different Sport
&lt;/h2&gt;

&lt;p&gt;Both engines let you extend the editor. Both will let you build custom inspectors, tool windows, asset processors. But the &lt;em&gt;shape&lt;/em&gt; of how you do it diverges so hard that your tooling knowledge basically doesn't carry over.&lt;/p&gt;

&lt;p&gt;Unity editor scripting is mostly C# in an &lt;code&gt;Editor/&lt;/code&gt; folder, using &lt;code&gt;UnityEditor&lt;/code&gt; APIs, &lt;code&gt;EditorWindow&lt;/code&gt;, &lt;code&gt;CustomEditor&lt;/code&gt;, and IMGUI (or UI Toolkit if you've gone modern). 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 &lt;code&gt;EditorWindow&lt;/code&gt; someone wrote during lunch.&lt;/p&gt;

&lt;p&gt;Unreal splits editor code into separate modules (you'll be editing &lt;code&gt;.Build.cs&lt;/code&gt; files), 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Slate looks like this. Yes, really.&lt;/span&gt;
&lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STextBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, tools"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You 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.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Asset Pipeline Has Opinions
&lt;/h2&gt;

&lt;p&gt;The thing nobody tells you: your project structure isn't just organization. It's a contract with the build system.&lt;/p&gt;

&lt;p&gt;Unity treats anything in &lt;code&gt;Assets/&lt;/code&gt; as fair game. You move things around freely, the &lt;code&gt;.meta&lt;/code&gt; files 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 &lt;code&gt;Assets/&lt;/code&gt; folders that looked like a teenager's desktop.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;Content/&lt;/code&gt; directory is a more structured place. Every asset is a &lt;code&gt;.uasset&lt;/code&gt; with 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Unity                  // Unreal
Assets/                   Content/
  Scripts/                  Blueprints/
  Prefabs/                  Maps/
  Scenes/                   Materials/
  Materials/                Characters/
  Resources/                ...
                          (Cooked output → Saved/Cooked/&amp;lt;Platform&amp;gt;/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two specific traps worth flagging early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity's &lt;code&gt;Resources/&lt;/code&gt; folder is convenient and almost always the wrong answer at scale. Anything in there ships in your build whether you reference it or not. Use Addressables for anything non-trivial.&lt;/li&gt;
&lt;li&gt;Unreal's hard-vs-soft references (&lt;code&gt;UPROPERTY()&lt;/code&gt; of &lt;code&gt;UObject*&lt;/code&gt; vs &lt;code&gt;TSoftObjectPtr&amp;lt;&amp;gt;&lt;/code&gt;) determine what gets pulled into memory when. Get this wrong on a mid-size project and your initial map load time will quietly balloon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The 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 &lt;em&gt;philosophies&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;one&lt;/em&gt; thing that's structurally different from what I'm used to, before I touch any code. Saves a lot of grief.&lt;/p&gt;

&lt;p&gt;If 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.&lt;/p&gt;

&lt;p&gt;More notes from the studio coming soon. This blog's going to be a running journal of the small, unglamorous things we actually learn shipping games, so if that's your thing, stick around.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Just One More Bug</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Tue, 09 Jun 2026 00:30:55 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/just-one-more-bug-3d9k</link>
      <guid>https://dev.to/gamedevnotes/just-one-more-bug-3d9k</guid>
      <description>&lt;p&gt;The lie game devs tell most often at night. To themselves.&lt;/p&gt;

&lt;p&gt;Fix one bug, two are born. The cans pile up, the dark circles set in, and before you know it the window is bright again.&lt;/p&gt;

&lt;p&gt;"This is really the last one" usually wasn't. Hope you actually get some sleep tonight. 🌙&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>gamedev</category>
      <category>mentalhealth</category>
      <category>programming</category>
    </item>
    <item>
      <title>Unity vs Unreal: 5 Things I Had to Relearn the Hard Way</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Mon, 08 Jun 2026 16:00:02 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-3e7k</link>
      <guid>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-3e7k</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Going 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.&lt;/p&gt;

&lt;p&gt;I've shipped work in both engines at this point, and the honest truth is that the syntax barrier (C# vs C++) is the &lt;em&gt;easy&lt;/em&gt; 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 &lt;em&gt;shape&lt;/em&gt; of the solution is the same. It isn't.&lt;/p&gt;

&lt;p&gt;Here 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Garbage Collection Lives in Different Universes
&lt;/h2&gt;

&lt;p&gt;Unity 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.&lt;/p&gt;

&lt;p&gt;In 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 &lt;code&gt;Update&lt;/code&gt; = GC spikes), but the &lt;em&gt;correctness&lt;/em&gt; model is straightforward. Everything's an object, everything's tracked.&lt;/p&gt;

&lt;p&gt;In Unreal, the GC only tracks &lt;code&gt;UObject&lt;/code&gt; subclasses, and only through references it can see — meaning references marked with &lt;code&gt;UPROPERTY()&lt;/code&gt;. If you store a &lt;code&gt;UObject*&lt;/code&gt; as 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Safe: GC knows you reference this&lt;/span&gt;
    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;EquippedWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Time bomb: GC will happily delete this&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SecretWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental rewire: in Unity, the GC is a &lt;em&gt;performance&lt;/em&gt; concern. In Unreal, the GC is a &lt;em&gt;correctness&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The flip side: Unity devs jumping to Unreal often reach for &lt;code&gt;TWeakObjectPtr&lt;/code&gt; reflexively because they're scared of the GC. Most of the time you just want a &lt;code&gt;UPROPERTY()&lt;/code&gt;-marked pointer — it keeps the object alive &lt;em&gt;and&lt;/em&gt; tracked. Save &lt;code&gt;TWeakObjectPtr&lt;/code&gt; for 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).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tick Is Not Update (And Lifecycle Is Not Awake)
&lt;/h2&gt;

&lt;p&gt;On paper, &lt;code&gt;MonoBehaviour.Update()&lt;/code&gt; and &lt;code&gt;AActor::Tick()&lt;/code&gt; look 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.&lt;/p&gt;

&lt;p&gt;Unity's &lt;code&gt;MonoBehaviour&lt;/code&gt; has a fairly chatty lifecycle (&lt;code&gt;Awake&lt;/code&gt;, &lt;code&gt;OnEnable&lt;/code&gt;, &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;LateUpdate&lt;/code&gt;, &lt;code&gt;OnDisable&lt;/code&gt;, &lt;code&gt;OnDestroy&lt;/code&gt;) and most of those fire reliably in the editor too. You can lean on &lt;code&gt;Start&lt;/code&gt; for cheap initialization and forget about it.&lt;/p&gt;

&lt;p&gt;Unreal splits actor lifetime across &lt;code&gt;PostInitializeComponents&lt;/code&gt;, &lt;code&gt;BeginPlay&lt;/code&gt;, &lt;code&gt;Tick&lt;/code&gt;, &lt;code&gt;EndPlay&lt;/code&gt;, with components having their own &lt;code&gt;OnRegister&lt;/code&gt; / &lt;code&gt;InitializeComponent&lt;/code&gt; flow. &lt;code&gt;BeginPlay&lt;/code&gt; only 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor runs for the CDO at engine init, and again for every instance.&lt;/span&gt;
    &lt;span class="c1"&gt;// In both cases the world isn't valid — no gameplay state, no other actors.&lt;/span&gt;
    &lt;span class="c1"&gt;// Defaults only; gameplay setup goes in BeginPlay.&lt;/span&gt;
    &lt;span class="n"&gt;PrimaryActorTick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bCanEverTick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Super&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Now the world exists. Do gameplay setup here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And 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. &lt;code&gt;Awake&lt;/code&gt; runs 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 &lt;code&gt;Start&lt;/code&gt;, not &lt;code&gt;Awake&lt;/code&gt;. &lt;code&gt;Start&lt;/code&gt; itself doesn't fire when you &lt;code&gt;Instantiate&lt;/code&gt; the object; it waits for the first frame the object is &lt;em&gt;active&lt;/em&gt;. &lt;code&gt;OnEnable&lt;/code&gt; and &lt;code&gt;OnDisable&lt;/code&gt; fire 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, &lt;code&gt;Update&lt;/code&gt; runs at a variable rate; physics-coupled logic belongs in &lt;code&gt;FixedUpdate&lt;/code&gt;, and that distinction is one of those things you only learn after your character starts vibrating at high frame rates.&lt;/p&gt;

&lt;p&gt;The rewire: in Unity, lifecycle is &lt;em&gt;flat&lt;/em&gt; and most things fire on a single timeline. In Unreal, lifecycle is &lt;em&gt;staged&lt;/em&gt;: constructor for defaults, &lt;code&gt;BeginPlay&lt;/code&gt; for runtime, and &lt;code&gt;Tick&lt;/code&gt; is opt-in (you have to enable it explicitly, which is honestly a feature, not a bug). Treat them as different shaped tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reflection Is the Whole Engine
&lt;/h2&gt;

&lt;p&gt;This is the one I underestimated the longest.&lt;/p&gt;

&lt;p&gt;Unity uses &lt;code&gt;[SerializeField]&lt;/code&gt;, &lt;code&gt;[Serializable]&lt;/code&gt;, and &lt;code&gt;[SerializeReference]&lt;/code&gt; to 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 &lt;code&gt;[SerializeReference]&lt;/code&gt;, custom classes need &lt;code&gt;[Serializable]&lt;/code&gt; to 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 &lt;code&gt;[Preserve]&lt;/code&gt;. It's lower-ceremony than Unreal, but it's not free.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;UPROPERTY()&lt;/code&gt;, &lt;code&gt;UFUNCTION()&lt;/code&gt;, and &lt;code&gt;UCLASS()&lt;/code&gt; macros 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 &lt;code&gt;UPROPERTY&lt;/code&gt; is invisible to all of it. A function without &lt;code&gt;UFUNCTION&lt;/code&gt; can't be called from Blueprint or RPCs. A class without &lt;code&gt;UCLASS&lt;/code&gt; doesn't exist to the engine at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Blueprintable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditAnywhere&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlueprintReadWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;UFUNCTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlueprintCallable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;TakeHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Damage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each meta specifier (&lt;code&gt;EditAnywhere&lt;/code&gt;, &lt;code&gt;BlueprintReadWrite&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Replicated&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;first&lt;/em&gt;, the field second.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Editor Extension Is a Different Sport
&lt;/h2&gt;

&lt;p&gt;Both engines let you extend the editor. Both will let you build custom inspectors, tool windows, asset processors. But the &lt;em&gt;shape&lt;/em&gt; of how you do it diverges so hard that your tooling knowledge basically doesn't carry over.&lt;/p&gt;

&lt;p&gt;Unity editor scripting is mostly C# in an &lt;code&gt;Editor/&lt;/code&gt; folder, using &lt;code&gt;UnityEditor&lt;/code&gt; APIs, &lt;code&gt;EditorWindow&lt;/code&gt;, &lt;code&gt;CustomEditor&lt;/code&gt;, 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 &lt;code&gt;EditorWindow&lt;/code&gt; someone wrote during lunch.&lt;/p&gt;

&lt;p&gt;Unreal splits editor code into separate modules (you'll be editing &lt;code&gt;.Build.cs&lt;/code&gt; files), 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Slate looks like this. Yes, really.&lt;/span&gt;
&lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STextBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, tools"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You 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.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Asset Pipeline Has Opinions
&lt;/h2&gt;

&lt;p&gt;The thing nobody tells you: your project structure isn't just organization. It's a contract with the build system.&lt;/p&gt;

&lt;p&gt;Unity treats anything in &lt;code&gt;Assets/&lt;/code&gt; as fair game. You move things around freely, the &lt;code&gt;.meta&lt;/code&gt; files 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 &lt;code&gt;Assets/&lt;/code&gt; folders that looked like a teenager's desktop.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;Content/&lt;/code&gt; directory is a more structured place. Every asset is a &lt;code&gt;.uasset&lt;/code&gt; with 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Unity                  // Unreal
Assets/                   Content/
  Scripts/                  Blueprints/
  Prefabs/                  Maps/
  Scenes/                   Materials/
  Materials/                Characters/
  Resources/                ...
                          (Cooked output → Saved/Cooked/&amp;lt;Platform&amp;gt;/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two specific traps worth flagging early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity's &lt;code&gt;Resources/&lt;/code&gt; folder 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).&lt;/li&gt;
&lt;li&gt;Unreal's hard-vs-soft references (&lt;code&gt;UPROPERTY()&lt;/code&gt; of &lt;code&gt;UObject*&lt;/code&gt; vs &lt;code&gt;TSoftObjectPtr&amp;lt;&amp;gt;&lt;/code&gt;) determine what gets pulled into memory when. Get this wrong on a mid-size project and your initial map load time will quietly balloon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The 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 &lt;em&gt;philosophies&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;one&lt;/em&gt; thing that's structurally different from what I'm used to, before I touch any code. Saves a lot of grief.&lt;/p&gt;

&lt;p&gt;If 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.&lt;/p&gt;

&lt;p&gt;This 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.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>learning</category>
      <category>softwaredevelopment</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Just One More Bug</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Fri, 05 Jun 2026 00:48:54 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/just-one-more-bug-53ak</link>
      <guid>https://dev.to/gamedevnotes/just-one-more-bug-53ak</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vx9ctcm8et9scr8jcrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vx9ctcm8et9scr8jcrv.png" alt="Dev Comic — Just One More Bug" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'll just squash this one and head to bed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The lie game devs tell most often at night. To themselves.&lt;/p&gt;

&lt;p&gt;Fix one bug, two are born. The cans pile up, the dark circles set in, and before you know it the window is bright again.&lt;/p&gt;

&lt;p&gt;"This is really the last one" usually wasn't. Hope you actually get some sleep tonight. 🌙&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The ScriptableObject Trap: Why Your Game Data Won't Reset After You Quit Play Mode</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Sat, 30 May 2026 00:04:23 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/the-scriptableobject-trap-why-your-game-data-wont-reset-after-you-quit-play-mode-5f6e</link>
      <guid>https://dev.to/gamedevnotes/the-scriptableobject-trap-why-your-game-data-wont-reset-after-you-quit-play-mode-5f6e</guid>
      <description>&lt;p&gt;We had a bug that only made sense if the laws of physics had quietly changed overnight. A designer would playtest a build, tweak nothing, close the editor, come back the next morning, and the boss enemy would start the fight with 40 HP instead of 400. Nobody had touched the data. Git showed no changes to any script. And yet the asset on disk had quietly rewritten itself.&lt;/p&gt;

&lt;p&gt;The culprit was a ScriptableObject. Specifically, it was us not understanding what a ScriptableObject &lt;em&gt;is&lt;/em&gt;. If you've ever wondered why a value you changed in play mode is still changed after you stop, this is the post I wish someone had handed me two years ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  ScriptableObjects are assets, not instances
&lt;/h2&gt;

&lt;p&gt;Here is the one sentence that fixes 90% of ScriptableObject confusion: a ScriptableObject is a single shared asset, and every object that references it points at the &lt;em&gt;same instance in memory&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When you make a &lt;code&gt;MonoBehaviour&lt;/code&gt; prefab and drop three copies into a scene, you get three independent objects. Change one's health, the other two don't care. People assume ScriptableObjects work the same way. They do not. If ten enemies all reference the same &lt;code&gt;EnemyStats&lt;/code&gt; asset, they are all reading and writing the &lt;em&gt;exact same object&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CreateAssetMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menuName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Game/Enemy Stats"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnemyStats&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ScriptableObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxHealth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;moveSpeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3.5f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Enemy&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;EnemyStats&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// a reference to the shared asset&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;currentHealth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentHealth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxHealth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TakeDamage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fine: we mutate a local copy of the number&lt;/span&gt;
        &lt;span class="n"&gt;currentHealth&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// DISASTER: this rewrites the shared asset for everyone, forever&lt;/span&gt;
        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxHealth&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second line is the whole bug in miniature. &lt;code&gt;currentHealth&lt;/code&gt; is a private field on the component, so mutating it is safe and per-instance. &lt;code&gt;stats.maxHealth&lt;/code&gt; is a field on the shared asset. Subtract from it and you've just changed the starting health of every enemy that uses this stats asset, in this run and every run after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the change survives play mode
&lt;/h2&gt;

&lt;p&gt;Here's the part that turns a confusing bug into a genuinely scary one. In the editor, ScriptableObject assets are live objects loaded from disk. When you enter play mode and mutate one, you are editing the loaded asset directly. Unity doesn't snapshot it for you. When you exit play mode, the in-memory object stays modified, and the next time Unity serializes that asset (saving the project, reimporting, or just Unity deciding to flush), your play-mode mutation gets written to the &lt;code&gt;.asset&lt;/code&gt; file on disk.&lt;/p&gt;

&lt;p&gt;That's why our boss "lost" 360 HP overnight. A playtest had been chipping away at &lt;code&gt;maxHealth&lt;/code&gt; via that exact bug, play mode ended, and at some point Unity serialized the now-corrupted asset back to disk. Git eventually noticed, but by then the damage looked like it came from nowhere.&lt;/p&gt;

&lt;p&gt;In a build it's slightly different but just as bad: there's no serialization back to disk, but the mutation persists for the entire session because the asset is loaded once and shared. So a value you "reset" in &lt;code&gt;Start&lt;/code&gt; only resets because you happened to copy it into a local field. Anything you mutated &lt;em&gt;on the asset itself&lt;/em&gt; stays mutated until the app closes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: treat SO data as read-only at runtime
&lt;/h2&gt;

&lt;p&gt;The rule we adopted and now enforce in review: &lt;strong&gt;ScriptableObject fields are read-only at runtime.&lt;/strong&gt; Runtime state lives on the component (or in a plain C# state object), seeded from the SO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Enemy&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;EnemyStats&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// read-only template&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;currentHealth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// mutable runtime state&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentHealth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxHealth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// copy, never alias&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TakeDamage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentHealth&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// touch the copy only&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a value is a number or a struct, copying it into a local field is enough — value types copy by default. The trap reopens the moment your SO holds a &lt;em&gt;reference type&lt;/em&gt; you intend to mutate, like a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; or a nested class. Copying the reference copies the pointer, not the data, so you're right back to mutating the shared asset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CreateAssetMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menuName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Game/Loadout"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Loadout&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ScriptableObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"sword"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"potion"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// WRONG: runtimeItems points at the SO's own list&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runtimeItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;runtimeItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bomb"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// the asset now permanently has a bomb&lt;/span&gt;

&lt;span class="c1"&gt;// RIGHT: make your own list&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runtimeItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loadout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Catching it before it ships
&lt;/h2&gt;

&lt;p&gt;Two guardrails have saved us repeatedly.&lt;/p&gt;

&lt;p&gt;First, make accidental mutation impossible to &lt;em&gt;write&lt;/em&gt;. Expose SO data through read-only properties backed by &lt;code&gt;[SerializeField] private&lt;/code&gt; fields, instead of public fields. Designers still edit them in the inspector; gameplay code physically cannot assign to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CreateAssetMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menuName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Game/Enemy Stats"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnemyStats&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ScriptableObject&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SerializeField&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxHealth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SerializeField&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;moveSpeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3.5f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MaxHealth&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxHealth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// gameplay can read&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;MoveSpeed&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;moveSpeed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// gameplay cannot write&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, when you genuinely &lt;em&gt;want&lt;/em&gt; a runtime-mutable copy of an SO (handy for per-enemy modifiers, buffs, procedural variation), be explicit about it with &lt;code&gt;Instantiate&lt;/code&gt;. Calling &lt;code&gt;Instantiate&lt;/code&gt; on a ScriptableObject gives you a fresh, independent clone you can mutate freely without ever touching the source asset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A per-enemy mutable copy, fully isolated from the shared asset&lt;/span&gt;
&lt;span class="n"&gt;EnemyStats&lt;/span&gt; &lt;span class="n"&gt;runtimeStats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Instantiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;runtimeStats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyModifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eliteBuff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// safe: this is our own object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just remember to clean those clones up — &lt;code&gt;Instantiate&lt;/code&gt;d ScriptableObjects are objects you now own, and they won't be garbage collected as long as something references them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it comes down to
&lt;/h2&gt;

&lt;p&gt;A ScriptableObject is shared, persistent, mutable state pretending to be a humble data file. The mental shift is to stop thinking of it as "a struct on disk" and start thinking of it as "a singleton everyone has a pointer to." Once you treat its fields as read-only templates, copy primitives, deep-copy reference types, and reach for &lt;code&gt;Instantiate&lt;/code&gt; when you truly need a mutable variant, the whole class of "my data changed itself" bugs disappears. The data only ever changes when &lt;em&gt;you&lt;/em&gt; decide it should, on a copy that's yours.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>data</category>
      <category>gamedev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>It Worked When I Closed the Laptop. I Swear.</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Fri, 22 May 2026 17:24:08 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/it-worked-when-i-closed-the-laptop-i-swear-537p</link>
      <guid>https://dev.to/gamedevnotes/it-worked-when-i-closed-the-laptop-i-swear-537p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dqvgpns3jhn3qc2jbtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dqvgpns3jhn3qc2jbtw.png" alt="Dev Comics #1 — It Worked Yesterday" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last night the build was clean. This morning, five red errors before the coffee was even warm.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git log&lt;/code&gt; shows the only commit in the last 11 hours is &lt;em&gt;yours&lt;/em&gt;. &lt;code&gt;"Refactor player movement"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You hold the cold coffee. You stare at the screen.&lt;/p&gt;

&lt;p&gt;If you've shipped any game ever, you've been here. Welcome to &lt;strong&gt;Dev Comics #1&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;This is a new weekly-ish series from a small Korean indie game studio — short comics about the unglamorous moments of making games. New comics most Tuesdays and Saturdays.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming up&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;#2 Shader Compile Coffee&lt;/strong&gt; — when your Unreal shader compile outlives your coffee&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#3 Cat.exe Has Stopped Responding&lt;/strong&gt; — the cat owns the keyboard now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this hit close to home, drop a 🦖 reaction or share the worst "11 hours ago, by me" commit you've ever seen.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>gamedev</category>
      <category>git</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Unity vs Unreal: 5 Things I Had to Relearn the Hard Way</title>
      <dc:creator>Game Dev Notes (Korea)</dc:creator>
      <pubDate>Fri, 22 May 2026 15:42:47 +0000</pubDate>
      <link>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-1kej</link>
      <guid>https://dev.to/gamedevnotes/unity-vs-unreal-5-things-i-had-to-relearn-the-hard-way-1kej</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Going 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.&lt;/p&gt;

&lt;p&gt;I've shipped work in both engines at this point, and the honest truth is that the syntax barrier (C# vs C++) is the &lt;em&gt;easy&lt;/em&gt; 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 &lt;em&gt;shape&lt;/em&gt; of the solution is the same. It isn't.&lt;/p&gt;

&lt;p&gt;Here 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Garbage Collection Lives in Different Universes
&lt;/h2&gt;

&lt;p&gt;Unity 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.&lt;/p&gt;

&lt;p&gt;In 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 &lt;code&gt;Update&lt;/code&gt; = GC spikes), but the &lt;em&gt;correctness&lt;/em&gt; model is straightforward. Everything's an object, everything's tracked.&lt;/p&gt;

&lt;p&gt;In Unreal, the GC only tracks &lt;code&gt;UObject&lt;/code&gt; subclasses, and only through references it can see — meaning references marked with &lt;code&gt;UPROPERTY()&lt;/code&gt;. If you store a &lt;code&gt;UObject*&lt;/code&gt; as 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Safe: GC knows you reference this&lt;/span&gt;
    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;EquippedWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Time bomb: GC will happily delete this&lt;/span&gt;
    &lt;span class="n"&gt;UWeapon&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SecretWeapon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental rewire: in Unity, the GC is a &lt;em&gt;performance&lt;/em&gt; concern. In Unreal, the GC is a &lt;em&gt;correctness&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The flip side: Unity devs jumping to Unreal often reach for &lt;code&gt;TWeakObjectPtr&lt;/code&gt; reflexively because they're scared of the GC. Most of the time you just want a &lt;code&gt;UPROPERTY()&lt;/code&gt;-marked pointer — it keeps the object alive &lt;em&gt;and&lt;/em&gt; tracked. Save &lt;code&gt;TWeakObjectPtr&lt;/code&gt; for 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).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tick Is Not Update (And Lifecycle Is Not Awake)
&lt;/h2&gt;

&lt;p&gt;On paper, &lt;code&gt;MonoBehaviour.Update()&lt;/code&gt; and &lt;code&gt;AActor::Tick()&lt;/code&gt; look 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.&lt;/p&gt;

&lt;p&gt;Unity's &lt;code&gt;MonoBehaviour&lt;/code&gt; has a fairly chatty lifecycle (&lt;code&gt;Awake&lt;/code&gt;, &lt;code&gt;OnEnable&lt;/code&gt;, &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;LateUpdate&lt;/code&gt;, &lt;code&gt;OnDisable&lt;/code&gt;, &lt;code&gt;OnDestroy&lt;/code&gt;) and most of those fire reliably in the editor too. You can lean on &lt;code&gt;Start&lt;/code&gt; for cheap initialization and forget about it.&lt;/p&gt;

&lt;p&gt;Unreal splits actor lifetime across &lt;code&gt;PostInitializeComponents&lt;/code&gt;, &lt;code&gt;BeginPlay&lt;/code&gt;, &lt;code&gt;Tick&lt;/code&gt;, &lt;code&gt;EndPlay&lt;/code&gt;, with components having their own &lt;code&gt;OnRegister&lt;/code&gt; / &lt;code&gt;InitializeComponent&lt;/code&gt; flow. &lt;code&gt;BeginPlay&lt;/code&gt; only 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor runs for the CDO at engine init, and again for every instance.&lt;/span&gt;
    &lt;span class="c1"&gt;// In both cases the world isn't valid — no gameplay state, no other actors.&lt;/span&gt;
    &lt;span class="c1"&gt;// Defaults only; gameplay setup goes in BeginPlay.&lt;/span&gt;
    &lt;span class="n"&gt;PrimaryActorTick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bCanEverTick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AEnemy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Super&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BeginPlay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Now the world exists. Do gameplay setup here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And 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. &lt;code&gt;Awake&lt;/code&gt; runs 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 &lt;code&gt;Start&lt;/code&gt;, not &lt;code&gt;Awake&lt;/code&gt;. &lt;code&gt;Start&lt;/code&gt; itself doesn't fire when you &lt;code&gt;Instantiate&lt;/code&gt; the object; it waits for the first frame the object is &lt;em&gt;active&lt;/em&gt;. &lt;code&gt;OnEnable&lt;/code&gt; and &lt;code&gt;OnDisable&lt;/code&gt; fire 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, &lt;code&gt;Update&lt;/code&gt; runs at a variable rate; physics-coupled logic belongs in &lt;code&gt;FixedUpdate&lt;/code&gt;, and that distinction is one of those things you only learn after your character starts vibrating at high frame rates.&lt;/p&gt;

&lt;p&gt;The rewire: in Unity, lifecycle is &lt;em&gt;flat&lt;/em&gt; and most things fire on a single timeline. In Unreal, lifecycle is &lt;em&gt;staged&lt;/em&gt;: constructor for defaults, &lt;code&gt;BeginPlay&lt;/code&gt; for runtime, and &lt;code&gt;Tick&lt;/code&gt; is opt-in (you have to enable it explicitly, which is honestly a feature, not a bug). Treat them as different shaped tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reflection Is the Whole Engine
&lt;/h2&gt;

&lt;p&gt;This is the one I underestimated the longest.&lt;/p&gt;

&lt;p&gt;Unity uses &lt;code&gt;[SerializeField]&lt;/code&gt;, &lt;code&gt;[Serializable]&lt;/code&gt;, and &lt;code&gt;[SerializeReference]&lt;/code&gt; to 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 &lt;code&gt;[SerializeReference]&lt;/code&gt;, custom classes need &lt;code&gt;[Serializable]&lt;/code&gt; to 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 &lt;code&gt;[Preserve]&lt;/code&gt;. It's lower-ceremony than Unreal, but it's not free.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;UPROPERTY()&lt;/code&gt;, &lt;code&gt;UFUNCTION()&lt;/code&gt;, and &lt;code&gt;UCLASS()&lt;/code&gt; macros 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 &lt;code&gt;UPROPERTY&lt;/code&gt; is invisible to all of it. A function without &lt;code&gt;UFUNCTION&lt;/code&gt; can't be called from Blueprint or RPCs. A class without &lt;code&gt;UCLASS&lt;/code&gt; doesn't exist to the engine at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;UCLASS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Blueprintable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AEnemy&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GENERATED_BODY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditAnywhere&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlueprintReadWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;UFUNCTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlueprintCallable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Combat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;TakeHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Damage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each meta specifier (&lt;code&gt;EditAnywhere&lt;/code&gt;, &lt;code&gt;BlueprintReadWrite&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Replicated&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;first&lt;/em&gt;, the field second.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Editor Extension Is a Different Sport
&lt;/h2&gt;

&lt;p&gt;Both engines let you extend the editor. Both will let you build custom inspectors, tool windows, asset processors. But the &lt;em&gt;shape&lt;/em&gt; of how you do it diverges so hard that your tooling knowledge basically doesn't carry over.&lt;/p&gt;

&lt;p&gt;Unity editor scripting is mostly C# in an &lt;code&gt;Editor/&lt;/code&gt; folder, using &lt;code&gt;UnityEditor&lt;/code&gt; APIs, &lt;code&gt;EditorWindow&lt;/code&gt;, &lt;code&gt;CustomEditor&lt;/code&gt;, 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 &lt;code&gt;EditorWindow&lt;/code&gt; someone wrote during lunch.&lt;/p&gt;

&lt;p&gt;Unreal splits editor code into separate modules (you'll be editing &lt;code&gt;.Build.cs&lt;/code&gt; files), 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Slate looks like this. Yes, really.&lt;/span&gt;
&lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SVerticalBox&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;SNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STextBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, tools"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You 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.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Asset Pipeline Has Opinions
&lt;/h2&gt;

&lt;p&gt;The thing nobody tells you: your project structure isn't just organization. It's a contract with the build system.&lt;/p&gt;

&lt;p&gt;Unity treats anything in &lt;code&gt;Assets/&lt;/code&gt; as fair game. You move things around freely, the &lt;code&gt;.meta&lt;/code&gt; files 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 &lt;code&gt;Assets/&lt;/code&gt; folders that looked like a teenager's desktop.&lt;/p&gt;

&lt;p&gt;Unreal's &lt;code&gt;Content/&lt;/code&gt; directory is a more structured place. Every asset is a &lt;code&gt;.uasset&lt;/code&gt; with 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Unity                  // Unreal
Assets/                   Content/
  Scripts/                  Blueprints/
  Prefabs/                  Maps/
  Scenes/                   Materials/
  Materials/                Characters/
  Resources/                ...
                          (Cooked output → Saved/Cooked/&amp;lt;Platform&amp;gt;/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two specific traps worth flagging early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity's &lt;code&gt;Resources/&lt;/code&gt; folder 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).&lt;/li&gt;
&lt;li&gt;Unreal's hard-vs-soft references (&lt;code&gt;UPROPERTY()&lt;/code&gt; of &lt;code&gt;UObject*&lt;/code&gt; vs &lt;code&gt;TSoftObjectPtr&amp;lt;&amp;gt;&lt;/code&gt;) determine what gets pulled into memory when. Get this wrong on a mid-size project and your initial map load time will quietly balloon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The 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 &lt;em&gt;philosophies&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;The 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 &lt;em&gt;one&lt;/em&gt; thing that's structurally different from what I'm used to, before I touch any code. Saves a lot of grief.&lt;/p&gt;

&lt;p&gt;If 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.&lt;/p&gt;

&lt;p&gt;This 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.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>gamedev</category>
      <category>learning</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
