<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>cooking on stillcooking.dev</title>
    <link>https://stillcooking.dev/en/tags/cooking/</link>
    <description>Personal portfolio and technical notes</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    
    <copyright>© 2026 stillcooking.dev — CC BY 4.0, https://creativecommons.org/licenses/by/4.0/</copyright>
    <lastBuildDate>Tue, 11 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://stillcooking.dev/en/tags/cooking/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>A Blueprint subsystem does not get created in a packaged build</title>
      <link>https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build/</link>
      <pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate>
      
      <guid isPermaLink="true">https://stillcooking.dev/en/topics/unreal-engine/gameplay-framework/blueprint-subsystem-missing-in-packaged-build/</guid>
      <description>The subsystem collection scans for subclasses once, when it is initialized, and sees only classes already in memory. In a packaged build, a Blueprint class that has not been loaded by then is missed entirely: its CDO is never asked whether to create an instance. The Blueprint logic never runs, and the missing class produces no error or log entry. In the editor, the Content Browser can keep the class loaded and hide the problem.</description>
      <content:encoded><![CDATA[<p>A Blueprint child of a subsystem can go an entire session in a packaged build without ever being created. Nothing is wrong with the code. It is the same code that worked in PIE a minute earlier. The problem lies earlier in the sequence: by the time the engine looks for subclasses, the Blueprint class is not in memory, so it never makes the list and its CDO is never queried.</p>
<h2 id="the-symptom-nothing-to-look-for">The symptom: nothing to look for</h2>
<p>No compile errors. No warnings. No lines in the log. <code>GetSubsystem&lt;T&gt;()</code> does not return <code>nullptr</code> either, because as long as the parent C++ class does not refuse to be created, an instance is created normally. What gets created is the parent. The Blueprint implementation that was supposed to replace it never runs, and the pointer looks perfectly valid.</p>
<p>There is a harsher variant, and it happens when the parent refuses on purpose. That is the case in <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">the pattern where the C++ class returns <code>false</code> to make room for a Blueprint implementation</a>. Then <strong>nothing</strong> is created at all: the parent refused, and nobody asked the child.</p>
<h2 id="two-runs-that-settle-it">Two runs that settle it</h2>
<p>The test project uses UE 5.7. It contains <code>UMyTestSubsystem : UGameInstanceSubsystem</code>, marked <code>UCLASS(Blueprintable)</code>, which logs a single line in <code>Initialize</code>. It also contains a Blueprint child, <code>BP_MyTestSubsystem</code>, with a <code>PrintString</code> in its graph. The build is packaged in the Development configuration.</p>
<p>First run, with nothing in the project referencing the Blueprint class:</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line ue-log__line--verbose"><span class="ue-log__cat">LogSubsystemCollection</span>: <span class="ue-log__sev ue-log__sev--verbose">Verbose</span>: <span class="ue-log__msg">Initializing subsystem collection for BP_GameInstance_Test_C_2147482547 with type GameInstanceSubsystem</span>
</span><span class="ue-log__line ue-log__line--verbose"><span class="ue-log__cat">LogSubsystemCollection</span>: <span class="ue-log__sev ue-log__sev--veryverbose">VeryVerbose</span>: <span class="ue-log__msg">Subsystem does not exist, but CDO choose to not create (MyTestSubsystem)</span>
</span></code></pre>
</div>
<p>The parent made it onto the list and refused. The child is not on that list at all. There is no second <code>choose to not create</code> line, and no line from <code>Initialize</code> either, which the child inherits from C++ and would have run had it been created. Counting objects confirms this independently:</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line">Obj List: class=MyTestSubsystem
</span><span class="ue-log__line">Objects:
</span><span class="ue-log__line">
</span><span class="ue-log__line">0 Objects (Total: 0.000M / Max: 0.000M / Res: 0.000M | ...)
</span></code></pre>
</div>
<p>Second run, after adding a single variable of type <code>MyTestSubsystem Class Reference</code> to the GameInstance class, with its default value pointing at the Blueprint child. The variable is never used anywhere. It exists for one reason: so that the asset holds a reference.</p>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line">                    Class    Count
</span><span class="ue-log__line">     BP_MyTestSubsystem_C        1
</span><span class="ue-log__line">
</span><span class="ue-log__line">1 Objects
</span></code></pre>
</div>
<div class="highlight" data-lang="log">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><pre tabindex="0" class="ue-log"><code><span class="ue-log__line"><span class="ue-log__cat">LogTemp</span>: <span class="ue-log__msg">UMyTestSubsystem initialized.</span>
</span><span class="ue-log__line"><span class="ue-log__cat">LogBlueprintUserMessages</span>: <span class="ue-log__msg">[None] PostInitialize(): Test Subsystem (BP_MyTestSubsystem)</span>
</span></code></pre>
</div>
<p>The instance exists, it is of the Blueprint class, and both the inherited C++ <code>Initialize</code> method and the Blueprint graph ran. The only difference between the two runs is an unused variable.</p>
<h2 id="where-it-disappears-the-scan-happens-once">Where it disappears: the scan happens once</h2>
<p><code>FSubsystemCollectionBase::Initialize</code> builds the class list in a single call, at the moment the collection is created (<code>Engine/Private/Subsystems/SubsystemCollection.cpp:209</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">TArray</span><span class="o">&lt;</span><span class="n">UClass</span><span class="o">*&gt;</span> <span class="n">SubsystemClasses</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">GetDerivedClasses</span><span class="p">(</span><span class="n">BaseType</span><span class="p">,</span> <span class="n">SubsystemClasses</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">UClass</span><span class="o">*</span> <span class="nl">SubsystemClass</span> <span class="p">:</span> <span class="n">SubsystemClasses</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">AddAndInitializeSubsystem</span><span class="p">(</span><span class="n">SubsystemClass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>GetDerivedClasses</code> sweeps the class hierarchy <strong>in memory</strong>. A class that nobody has loaded exists only as a file on disk: the asset registry knows about it, the type system does not. Since it never lands in <code>SubsystemClasses</code>, <code>AddAndInitializeSubsystem</code> never receives it, and <code>ShouldCreateSubsystem</code> has nothing to run on. A class missing from the scan produces no CDO decision entry. A class that is found but refuses creation produces the <code>CDO choose to not create</code> line at <code>VeryVerbose</code> verbosity.</p>
<p>That loop is in fact the <code>else</code> branch. The first branch handles a completely different case (<code>SubsystemCollection.cpp:194</code>):</p>
<div class="highlight" data-lang="cpp">
  <button type="button" class="code-copy" data-code-copy data-label="Copy" data-copied="Copied!" aria-label="Copy code to clipboard">Copy</button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">BaseType</span><span class="o">-&gt;</span><span class="n">IsChildOf</span><span class="p">(</span><span class="n">UDynamicSubsystem</span><span class="o">::</span><span class="n">StaticClass</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">TPair</span><span class="o">&lt;</span><span class="n">FName</span><span class="p">,</span> <span class="n">TArray</span><span class="o">&lt;</span><span class="n">TSubclassOf</span><span class="o">&lt;</span><span class="n">UDynamicSubsystem</span><span class="o">&gt;&gt;&gt;&amp;</span> <span class="nl">SubsystemClasses</span> <span class="p">:</span> <span class="n">GlobalDynamicSystemModuleMap</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// GetDerivedClasses, once
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div></div>
<p><code>UDynamicSubsystem</code> gets incremental registration. <code>FSubsystemModuleWatcher</code> listens for module loads and calls <code>AddAllInstances</code> for classes that have just appeared (<code>SubsystemCollection.cpp:524</code>). <code>UGameInstanceSubsystem</code> does not belong to that branch. For it, the scan runs once and nothing repeats it, so a class loaded one second after the collection is initialized is too late for that collection.</p>
<div class="callout callout--warning">
  <div class="callout__title">Warning</div>
  Timing is the only criterion here. One thing matters: whether the class is in memory <strong>before</strong> the subsystem collection is created. Having the asset in the pak does nothing on its own. An asset that is packaged but referenced by nothing is, as far as this scan is concerned, absent.
</div>

<h2 id="why-the-editor-can-hide-this">Why the editor can hide this</h2>
<p>In the editor, the Content Browser keeps the Blueprint class in memory. Opening the asset once is enough, and so is having it sit in a visible folder. By the time PIE starts, the class is already loaded, <code>GetDerivedClasses</code> sees it, and everything behaves exactly as designed.</p>
<p>In a real project the packaged build usually works too, but for a different reason: the reference exists by accident. A <code>Get Subsystem</code> node with the Blueprint class selected embeds a hard reference in the Blueprint that calls it, and that Blueprint ships with the level or with the GameInstance class. A reference from a Blueprint hides the problem if that Blueprint loads the subsystem class <strong>before</strong> the collection is initialized.</p>
<p>That is the whole trap. The mechanism breaks in exactly the place nobody tests it: when the subsystem is called from C++ and nowhere else.</p>
<h2 id="the-fix">The fix</h2>
<p>Anything that loads the class before the collection is initialized. The cheapest option, and the one that does not disappear when somebody rewires a graph, is a variable of type <code>&lt;Subsystem&gt; Class Reference</code> on an object that loads early, with its default value set to the Blueprint class. The GameInstance class is the natural place for the variable, because the GameInstance itself is created before its subsystem collection.</p>
<p>The variable does not have to be used for anything. What matters is that the GameInstance asset holds a hard reference to the Blueprint class, so loading one pulls in the other.</p>
<div class="callout callout--tip">
  <div class="callout__title">Tip</div>
  Leave a comment on the variable where it is declared. An unused <code>Class Reference</code> variable looks like a leftover from a refactor and is the first thing anyone deletes during cleanup, and deleting it breaks nothing in the editor.
</div>

<p>Getting the class loaded in time is the only real fix. How badly the failure hurts when nothing loads it is a separate question, and the answer depends on how the C++ class steps aside for the Blueprint. Overriding <code>ShouldCreateSubsystem</code> with a condition that checks for subclasses, rather than with a flag set in the Blueprint defaults, turns “zero instances” into “a C++ instance without the Blueprint logic”: a reduced system rather than a missing one. I wrote up the mechanism itself, and its limits, in <a href="/en/topics/unreal-engine/gameplay-framework/should-create-subsystem-picks-the-class">ShouldCreateSubsystem and the subsystem class hierarchy</a>.</p>
<h2 id="checking-this-in-your-own-project">Checking this in your own project</h2>
<div class="section-panel section-panel--checklist">
  <div class="section-panel__label">Checklist</div>
  
<p>What to check when a subsystem works in PIE and its behavior in a packaged build is unknown:</p>
<ul class="checklist">
  <li>Package in the <strong>Development</strong> configuration rather than Shipping. A Shipping build compiles <code>Verbose</code> and <code>VeryVerbose</code> out, so raising the log level cannot bring them back.</li>
  <li>Run it with <code>&lt;Game&gt;.exe -log "-ExecCmds=obj list class=&lt;Subsystem&gt;"</code>. A query for the base class covers derived classes too, which is visible in the second run above: the answer to a question about the C++ class is an instance of the Blueprint class.</li>
  <li>Check the <strong>class</strong>, not only the object count. <code>1 Objects</code> of the parent class while a child exists is the same failure, only quieter.</li>
  <li>For each CDO decision separately, use <code>-LogCmds="LogSubsystemCollection VeryVerbose"</code>. A <code>CDO choose to not create</code> line means “asked and refused.” Its absence, alongside a missing instance, means “never asked.”</li>
</ul>

</div>

<p>Check two things separately: whether the instance exists, and whether the log contains a CDO decision for that class. A missing instance together with a missing CDO decision points to loading as the source of the problem. Neither observation is enough on its own.</p>
<h2 id="when-this-matters">When this matters</h2>
<p>As long as a Blueprint reference gets the class loaded in time, the trap stays asleep. The reference appears on its own and nobody finds out it was ever needed. I reproduced it in two setups: one where the subsystem serves C++ code only, and one where a Blueprint child was added to override a few default values and nothing called it afterwards.</p>
<p>A third setup follows from the mechanism, but I did not reproduce it. Removing the last <code>Get Subsystem</code> call from Blueprints during a refactor can remove the only reference to the class. If that happens, this is the worst of the three cases, because nothing obviously connects the regression to the commit that triggered it. A node disappears from one Blueprint, the subsystem stops existing in the packaged game, and everything still works in the editor.</p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
