<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://liuyisi.com/blog</id>
    <title>Yisi Liu Blog</title>
    <updated>2026-06-15T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://liuyisi.com/blog"/>
    <subtitle>Yisi Liu Blog</subtitle>
    <icon>https://liuyisi.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[I Optimized the Wrong Thing (A Profiling Story)]]></title>
        <id>https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters</id>
        <link href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters"/>
        <updated>2026-06-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Six hours. That's how long I spent rewriting Sharpshooters' bullet system before realizing it wasn't the problem.]]></summary>
        <content type="html"><![CDATA[<p>Six hours. That's how long I spent rewriting Sharpshooters' bullet system before realizing it wasn't the problem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-symptom">The Symptom<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#the-symptom" class="hash-link" aria-label="Direct link to The Symptom" title="Direct link to The Symptom" translate="no">​</a></h2>
<p>During wave 4, the frame rate would stutter every 3–4 seconds. Not a crash, not a freeze — a noticeable 20ms hike that made the game feel janky. I had 40 bullets on screen at peak. My first instinct: bullets are expensive.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-did-wrong">What I Did Wrong<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#what-i-did-wrong" class="hash-link" aria-label="Direct link to What I Did Wrong" title="Direct link to What I Did Wrong" translate="no">​</a></h2>
<p>I immediately rewrote the bullet system to use object pooling. Bullets no longer instantiate and destroy — they activate and deactivate from a pool of 100. It took most of a weekend.</p>
<p>The stutter didn't go away.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-did-right-eventually">What I Did Right (Eventually)<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#what-i-did-right-eventually" class="hash-link" aria-label="Direct link to What I Did Right (Eventually)" title="Direct link to What I Did Right (Eventually)" translate="no">​</a></h2>
<p>I opened Unity's Profiler. Five minutes after opening it, I found the actual culprit: the damage number UI.</p>
<p>Every hit spawned a <code>TextMeshPro</code> popup that displayed the damage value, animated upward, and then destroyed itself. At peak combat, that was 30–50 <code>Instantiate</code> calls per second — far more garbage than any bullet.</p>
<p>The fix took 45 minutes: a small pool of 20 text popups that get recycled instead of destroyed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-irony">The Irony<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#the-irony" class="hash-link" aria-label="Direct link to The Irony" title="Direct link to The Irony" translate="no">​</a></h2>
<p>The bullet pooling I spent the weekend on was still a good idea — it'll matter at higher bullet counts. But it was <strong>not</strong> the source of the problem I was trying to fix.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rule-im-taking-forward">Rule I'm Taking Forward<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#rule-im-taking-forward" class="hash-link" aria-label="Direct link to Rule I'm Taking Forward" title="Direct link to Rule I'm Taking Forward" translate="no">​</a></h2>
<p><strong>Profile before you optimize.</strong> This is advice I'd heard a hundred times. Now I've paid the tuition.</p>
<p>My new process: reproduce the problem, open the Profiler, find the actual spike, <em>then</em> write code. Not before.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-profiler-showed">What the Profiler Showed<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#what-the-profiler-showed" class="hash-link" aria-label="Direct link to What the Profiler Showed" title="Direct link to What the Profiler Showed" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Frame time: 32ms (frame 1847)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  GC.Collect          12.4ms  ← here</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  TextMeshPro.Update   6.1ms</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Physics.Simulate     4.8ms</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Render               5.3ms</span><br></div></code></pre></div></div>
<p>The <code>GC.Collect</code> call is the giveaway — that's the garbage collector running because something is allocating heavily. Bullets after pooling: 0 allocations. Text popups before pooling: ~800 bytes per popup × 50 popups/sec = ~40KB/sec of garbage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="takeaway-for-anyone-new-to-unity-performance">Takeaway for Anyone New to Unity Performance<a href="https://liuyisi.com/blog/2026/06/15/profiling-sharpshooters#takeaway-for-anyone-new-to-unity-performance" class="hash-link" aria-label="Direct link to Takeaway for Anyone New to Unity Performance" title="Direct link to Takeaway for Anyone New to Unity Performance" translate="no">​</a></h2>
<p>Three things generate GC pressure more than anything else in my experience:</p>
<ol>
<li class=""><strong>Instantiate/Destroy</strong> — use pooling for anything that spawns frequently</li>
<li class=""><strong>String operations in <code>Update()</code></strong> — <code>$"Score: {score}"</code> creates a new string every frame</li>
<li class=""><strong>LINQ in hot paths</strong> — <code>.Where()</code>, <code>.Select()</code> allocate enumerators</li>
</ol>
<p>Profile first. Know which of the three you're actually hitting before you rewrite anything.</p>
<hr>
<p><em>Sharpshooters dev log. Follow along as I build a 3D shooter from scratch.</em></p>]]></content>
        <author>
            <name>Yisi Liu</name>
            <uri>https://github.com/yourusername</uri>
        </author>
        <category label="unity" term="unity"/>
        <category label="optimization" term="optimization"/>
        <category label="profiling" term="profiling"/>
        <category label="sharpshooters" term="sharpshooters"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What Surprised Me About Switching from Unity to Unreal]]></title>
        <id>https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity</id>
        <link href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity"/>
        <updated>2026-05-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I'd been using Unity for two years when I decided to learn Unreal Engine for the Infinite Runner project. Here's what I expected, and what actually surprised me.]]></summary>
        <content type="html"><![CDATA[<p>I'd been using Unity for two years when I decided to learn Unreal Engine for the Infinite Runner project. Here's what I expected, and what actually surprised me.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-expected">What I Expected<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#what-i-expected" class="hash-link" aria-label="Direct link to What I Expected" title="Direct link to What I Expected" translate="no">​</a></h2>
<ul>
<li class="">A harder learning curve</li>
<li class="">"Better" graphics automatically</li>
<li class="">Everything to be different</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-actually-happened">What Actually Happened<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#what-actually-happened" class="hash-link" aria-label="Direct link to What Actually Happened" title="Direct link to What Actually Happened" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-actor-system-is-basically-a-monobehaviour">The Actor System Is Basically a MonoBehaviour<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#the-actor-system-is-basically-a-monobehaviour" class="hash-link" aria-label="Direct link to The Actor System Is Basically a MonoBehaviour" title="Direct link to The Actor System Is Basically a MonoBehaviour" translate="no">​</a></h3>
<p>Unity has <code>MonoBehaviour</code>. Unreal has <code>AActor</code>. Both are components attached to game objects that run every frame. The mental model transferred almost directly.</p>
<p>What <em>is</em> different: Unreal's ownership model. In Unity, game objects get destroyed and the garbage collector cleans up. In Unreal, you manage lifetime more carefully — <code>TObjectPtr&lt;&gt;</code>, weak references, and understanding when the engine owns vs. you own.</p>
<p>I hit my first crash because I held a raw pointer to an actor that got destroyed. Unity never taught me to think about this.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="blueprints-first-was-the-right-call">Blueprints First Was the Right Call<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#blueprints-first-was-the-right-call" class="hash-link" aria-label="Direct link to Blueprints First Was the Right Call" title="Direct link to Blueprints First Was the Right Call" translate="no">​</a></h3>
<p>Every guide I read said "learn C++ first, Blueprints are a crutch." I disagree — at least for a complete beginner to Unreal.</p>
<p>I prototyped the entire Infinite Runner movement system in Blueprints in a week. Then I ported it to C++ over the next week. Having a working Blueprint reference made the C++ translation much cleaner — I wasn't learning the engine API and solving gameplay design problems simultaneously.</p>
<p>Blueprints are also excellent for tweaking values. I kept the difficulty curve tuning in Blueprints (exposed variables) so I could adjust without recompiling.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="compile-times-are-real">Compile Times Are Real<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#compile-times-are-real" class="hash-link" aria-label="Direct link to Compile Times Are Real" title="Direct link to Compile Times Are Real" translate="no">​</a></h3>
<p>Unity is near-instant iteration. Unreal C++ builds take 30–120 seconds depending on change scope. This changes how you work:</p>
<ul>
<li class="">Batch changes before compiling</li>
<li class="">Use hot reload where possible</li>
<li class="">Keep gameplay-tuning values in Blueprints or DataAssets so you're not compiling for value tweaks</li>
</ul>
<p>After two weeks I stopped noticing the wait. But the first week was painful.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-documentation-is-different">The Documentation Is... Different<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#the-documentation-is-different" class="hash-link" aria-label="Direct link to The Documentation Is... Different" title="Direct link to The Documentation Is... Different" translate="no">​</a></h3>
<p>Unity's documentation is clearly written for people learning the engine. Unreal's documentation assumes you already know what you're looking for.</p>
<p>The community around Unreal is great, but it's weighted toward visual/Blueprint content. Finding C++ answers sometimes required reading source code or asking on the Unreal forums directly.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="six-months-in">Six Months In<a href="https://liuyisi.com/blog/2026/05/02/learning-unreal-from-unity#six-months-in" class="hash-link" aria-label="Direct link to Six Months In" title="Direct link to Six Months In" translate="no">​</a></h2>
<p>I'm comfortable in both engines now. The things that felt alien in Unreal — the actor lifecycle, the build system, the C++ conventions — are now just normal.</p>
<p>The bigger lesson: <strong>the fundamentals transfer.</strong> State machines, object pooling, event systems, data separation — none of that is engine-specific. Understanding <em>why</em> patterns work makes picking up new engines much faster.</p>
<hr>
<p><em>Yisi Liu — building the Infinite Runner in Unreal Engine 5.</em></p>]]></content>
        <author>
            <name>Yisi Liu</name>
            <uri>https://github.com/yourusername</uri>
        </author>
        <category label="unreal" term="unreal"/>
        <category label="unity" term="unity"/>
        <category label="learning" term="learning"/>
        <category label="infinite-runner" term="infinite-runner"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What My First Game Jam Taught Me About Finishing]]></title>
        <id>https://liuyisi.com/blog/2026/04/10/first-game-jam</id>
        <link href="https://liuyisi.com/blog/2026/04/10/first-game-jam"/>
        <updated>2026-04-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[48 hours. One theme revealed at the start. You build something. You ship it.]]></summary>
        <content type="html"><![CDATA[<p>48 hours. One theme revealed at the start. You build something. You ship it.</p>
<p>My first game jam was in early 2024. Here's what I learned — most of it the hard way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hour-14-overscoping">Hour 1–4: Overscoping<a href="https://liuyisi.com/blog/2026/04/10/first-game-jam#hour-14-overscoping" class="hash-link" aria-label="Direct link to Hour 1–4: Overscoping" title="Direct link to Hour 1–4: Overscoping" translate="no">​</a></h2>
<p>The theme dropped: <strong>"One Room."</strong></p>
<p>My first idea: a mystery game where the player explores a single room across different time periods, past and future versions overlapping. Dialogue system. Multiple endings. Inventory. 48 hours.</p>
<p>I started building the dialogue system.</p>
<p>Two hours in, a friend looked at my project and said: "Can you finish that in 46 hours?"</p>
<p>I couldn't. I scrapped it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hour-412-finding-the-actual-scope">Hour 4–12: Finding the Actual Scope<a href="https://liuyisi.com/blog/2026/04/10/first-game-jam#hour-412-finding-the-actual-scope" class="hash-link" aria-label="Direct link to Hour 4–12: Finding the Actual Scope" title="Direct link to Hour 4–12: Finding the Actual Scope" translate="no">​</a></h2>
<p>I made myself answer three questions:</p>
<ol>
<li class="">What is the <em>one thing</em> the player does?</li>
<li class="">What does <em>done</em> look like?</li>
<li class="">Can I build that in 36 hours?</li>
</ol>
<p>New idea: a single-screen puzzle where the player moves a box around a room to reach a door. Simple. Clearly scoped. Done = puzzle solved, fade to credits.</p>
<p>I started building it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hour-1236-building-under-constraint">Hour 12–36: Building Under Constraint<a href="https://liuyisi.com/blog/2026/04/10/first-game-jam#hour-1236-building-under-constraint" class="hash-link" aria-label="Direct link to Hour 12–36: Building Under Constraint" title="Direct link to Hour 12–36: Building Under Constraint" translate="no">​</a></h2>
<p>No polish until core is working. No effects until the mechanic works. No music until I can complete the puzzle.</p>
<p>This felt wrong. I'm used to iterating on feel as I go. But under a deadline, working ugly beats pretty-but-broken.</p>
<p>By hour 24, the puzzle was completable. The box moved, the physics felt okay, you could reach the door and win.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hour-3648-the-finish-line">Hour 36–48: The Finish Line<a href="https://liuyisi.com/blog/2026/04/10/first-game-jam#hour-3648-the-finish-line" class="hash-link" aria-label="Direct link to Hour 36–48: The Finish Line" title="Direct link to Hour 36–48: The Finish Line" translate="no">​</a></h2>
<p>Polish. Music (a free CC0 track). Sound effects. Title screen. "You Win" screen. Build it. Upload it.</p>
<p>I submitted with 20 minutes to spare. The game had 8 levels and took about 12 minutes to complete.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-learned">What I Learned<a href="https://liuyisi.com/blog/2026/04/10/first-game-jam#what-i-learned" class="hash-link" aria-label="Direct link to What I Learned" title="Direct link to What I Learned" translate="no">​</a></h2>
<p><strong>Finishing is a skill.</strong> It sounds obvious but I hadn't practiced it. Every project before the jam had always been "in progress." The jam forced me to make the hard call: this is what ships, everything else is cut.</p>
<p><strong>Scope is a prediction.</strong> You don't know if something fits until you try. The dialogue system felt totally reasonable until the clock made it concrete. Now when I plan projects, I ask: can I build the core mechanic in one weekend? If not, the design is probably too complex for the time I have.</p>
<p><strong>The game doesn't have to be good — it has to be done.</strong> My jam game is not impressive. The puzzles are easy and the graphics are programmer art. But it's a finished thing that exists in the world, and that feels completely different from an unfinished project that exists only on my hard drive.</p>
<hr>
<p>I've done two more jams since. Each time, I scope smaller than I think I should, and each time I'm glad I did.</p>
<hr>
<p><em>Yisi Liu — learning to ship, one game at a time.</em></p>]]></content>
        <author>
            <name>Yisi Liu</name>
            <uri>https://github.com/yourusername</uri>
        </author>
        <category label="game-jam" term="game-jam"/>
        <category label="learning" term="learning"/>
        <category label="reflection" term="reflection"/>
    </entry>
</feed>