<?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: GameDevToolLab</title>
    <description>The latest articles on DEV Community by GameDevToolLab (@gamedevtoollab).</description>
    <link>https://dev.to/gamedevtoollab</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3992439%2F58833102-eb15-4c51-a035-e55b9d236f1e.png</url>
      <title>DEV Community: GameDevToolLab</title>
      <link>https://dev.to/gamedevtoollab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gamedevtoollab"/>
    <language>en</language>
    <item>
      <title>Real-Time Networking for 3D FPS Games in UE5: Replication, EOS, Dedicated Servers, and Pitfalls</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Fri, 11 Sep 2026 04:40:55 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/real-time-networking-for-3d-fps-games-in-ue5-replication-eos-dedicated-servers-and-pitfalls-1o0g</link>
      <guid>https://dev.to/gamedevtoollab/real-time-networking-for-3d-fps-games-in-ue5-replication-eos-dedicated-servers-and-pitfalls-1o0g</guid>
      <description>&lt;p&gt;Building an online FPS or TPS in Unreal Engine 5 is not a matter of choosing one "networking library." A production game usually has three networking layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gameplay networking&lt;/strong&gt; — movement, shooting, damage, ammo, projectiles, doors, objectives, and match state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online services&lt;/strong&gt; — authentication, friends, invites, lobbies, sessions, matchmaking, voice, P2P, and relay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated-server operations&lt;/strong&gt; — allocation, region selection, health checks, deployment, logs, scaling, draining, and shutdown.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most UE shooters, start with built-in Replication, RPCs, and &lt;code&gt;UCharacterMovementComponent&lt;/code&gt;. Use a Dedicated Server when fairness matters; consider a Listen Server for small co-op. Add Steam/EOS, GAS, Replication Graph, Iris, or external SDKs only when targets or measurements justify them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Technical baseline: August 31, 2026. The article uses UE 5.8 documentation and public UE 5.8.1/5.8.2 hotfix notes. The Network Emulation caveat below comes from source inspection of UE 5.8.0 CL &lt;code&gt;55116800&lt;/code&gt;; that engine build was not re-run while preparing this English edition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Think in three layers, not one library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gameplay networking
&lt;/h3&gt;

&lt;p&gt;This layer synchronizes gameplay under latency, packet loss, reordering, relevance filtering, and bandwidth limits. UE Replication is a strong default because Actor ownership, per-connection relevancy, priority, dormancy, RPCs, property replication, and Character Movement prediction all live in the same networking model [1-9].&lt;/p&gt;

&lt;h3&gt;
  
  
  Online services
&lt;/h3&gt;

&lt;p&gt;Steam and EOS answer questions such as "who is the player?", "which lobby are they in?", and "where should they connect?" They do not replace Actor replication after the connection is established. In UE 5.8, Online Services is still documented as Beta, so projects shipping soon should evaluate it against the established Online Subsystem path [17-21].&lt;/p&gt;

&lt;h3&gt;
  
  
  Server operations
&lt;/h3&gt;

&lt;p&gt;A Dedicated Server still needs to be started, allocated, monitored, updated, drained, and terminated. Amazon GameLift Servers, PlayFab Multiplayer Servers, and Agones help with this operational layer [24-27]. They do not replace Replication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Competitive FPS: start from a server-authoritative Dedicated Server
&lt;/h2&gt;

&lt;p&gt;Unreal multiplayer is fundamentally client-server, with the server holding the authoritative world state [1,2]. That maps naturally to competitive shooters.&lt;/p&gt;

&lt;p&gt;A Listen Server can be perfectly reasonable for a two-to-four-player co-op game, but the host has direct access to the authoritative process. In PvP, that becomes a fairness problem in addition to host departure, NAT, and hardware variance.&lt;/p&gt;

&lt;p&gt;A Dedicated Server costs more to operate, but gives you a cleaner authoritative process for validation and consistent simulation. Choose by measured Actors, AI, shot frequency, server frame time, and bandwidth—not player count alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put replicated state in the right gameplay class
&lt;/h2&gt;

&lt;p&gt;A common Dedicated Server migration bug is trying to read server-only objects from client UI.&lt;/p&gt;

&lt;p&gt;A useful division is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GameMode&lt;/code&gt;: server-only rules such as win conditions, spawn rules, admission rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GameState&lt;/code&gt;: public match state such as phase, team score, and round-end server time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PlayerState&lt;/code&gt;: public per-player data such as team, kills, and deaths.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PlayerController&lt;/code&gt;: input, owner-only UI, and owner-only notifications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Pawn&lt;/code&gt; / &lt;code&gt;Character&lt;/code&gt;: the current body, movement, and weapon interaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;GameMode&lt;/code&gt; does not exist on clients. &lt;code&gt;PlayerController&lt;/code&gt; is normally relevant only to its owning client, so it is also the wrong place for a scoreboard that everybody must see [31,32].&lt;/p&gt;

&lt;p&gt;For countdowns, replicate a &lt;strong&gt;round end server timestamp&lt;/strong&gt;, not "seconds remaining" every second. Clients can derive the display from &lt;code&gt;AGameStateBase::GetServerWorldTimeSeconds()&lt;/code&gt; [31,35], including after late join.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;UCharacterMovementComponent&lt;/code&gt; before inventing movement RPCs
&lt;/h2&gt;

&lt;p&gt;Do not start an FPS by sending your own transform or velocity RPC every frame.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UCharacterMovementComponent&lt;/code&gt; already implements local prediction, saved moves, server simulation, correction, and smoothing for remote Characters [5]. Extend movement modes and saved moves for sprinting, sliding, or similar mechanics instead of creating a second synchronization system on top of it.&lt;/p&gt;

&lt;p&gt;For unusual vehicles or physics, custom prediction or the Network Prediction plugin may be relevant, but the UE 5.8 API still marks that plugin Beta [12]. Prototype before depending on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate state from events
&lt;/h2&gt;

&lt;p&gt;The most useful rule in UE networking is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State that must be reconstructable&lt;/strong&gt; belongs in Replicated Properties.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transient events&lt;/strong&gt; may use RPCs when appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HP, ammo, equipped weapon, death state, doors, score, and match phase should survive packet loss, late join, and becoming relevant again. That makes them state.&lt;/p&gt;

&lt;p&gt;Muzzle flashes, impact particles, and one-shot sounds may fit Unreliable RPCs, but prefer deriving visuals from replicated state when possible. Reliable RPCs are for low-frequency operations retried while the connection and channel remain valid; overuse can block later reliable traffic [3].&lt;/p&gt;

&lt;p&gt;Also remember ownership: a client can normally send a Server RPC only through an Actor it owns. Input RPCs therefore belong on the owning &lt;code&gt;PlayerController&lt;/code&gt;, Pawn, or an owned Component/Weapon whose owner chain is correct [3,32].&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not turn RPC ordering into a game rule
&lt;/h2&gt;

&lt;p&gt;UE documents representative ordering for one Actor under specific send/bunch conditions [33]. Do &lt;strong&gt;not&lt;/strong&gt; generalize that into a gameplay protocol across Actors, Components, frames, loss, or reordering.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ERemoteFunctionSendPolicy::ForceSend&lt;/code&gt; also has important limits: it is supported by Replication Graph or Iris, and only sends immediately when invoked in the supported &lt;code&gt;NetDriver::PostTickDispatch&lt;/code&gt; / &lt;code&gt;NetWorldTickTime&lt;/code&gt; phase. Outside that phase it behaves like &lt;code&gt;Default&lt;/code&gt;. &lt;code&gt;ForceQueue&lt;/code&gt; is different: it serializes later in the net update if bandwidth remains [3,33].&lt;/p&gt;

&lt;p&gt;Use sequence numbers, generations/epochs, and idempotent application logic instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-authoritative shooting: separate intent from result
&lt;/h2&gt;

&lt;p&gt;The most dangerous shooter implementation is accepting &lt;code&gt;HitActor&lt;/code&gt;, bone, or damage values sent by the client.&lt;/p&gt;

&lt;p&gt;The client may send &lt;strong&gt;intent&lt;/strong&gt;: which weapon, aim direction, and a timestamp estimate. The server decides the result.&lt;/p&gt;

&lt;p&gt;The example below focuses on hitscan weapons. Slow projectiles, predicted projectiles, explosive overlap, and physics interaction need different designs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give every shot a lifecycle-aware key
&lt;/h3&gt;

&lt;p&gt;A plain &lt;code&gt;uint16 Sequence&lt;/code&gt; is not enough if the weapon respawns, ownership changes, or a sequence wraps. Use a server-assigned source plus an epoch:&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;USTRUCT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FShotKey&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;uint32&lt;/span&gt; &lt;span class="n"&gt;FireSourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// server assigned&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;uint16&lt;/span&gt; &lt;span class="n"&gt;FireEpoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// changes when the sequence space resets&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;uint16&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;USTRUCT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FShotRequest&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;FShotKey&lt;/span&gt; &lt;span class="n"&gt;Key&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;uint8&lt;/span&gt; &lt;span class="n"&gt;WeaponSlot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;FVector_NetQuantizeNormal&lt;/span&gt; &lt;span class="n"&gt;AimDirection&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;uint32&lt;/span&gt; &lt;span class="n"&gt;EstimatedServerTimeMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;Before calling the Server RPC, the owning client records &lt;code&gt;(FireSourceId, FireEpoch, Sequence)&lt;/code&gt; in a &lt;strong&gt;client-process prediction history&lt;/strong&gt; and plays predicted local FX once. Do not tie that history to the current possession state. Keep it long enough to cover your maximum network/reconciliation delay, and namespace or clear it across disconnect, Server Travel, and session changes.&lt;/p&gt;

&lt;p&gt;This matters because a delayed multicast may arrive after death, unpossess, repossess, or controller transfer. Looking only at &lt;code&gt;IsLocallyControlled()&lt;/code&gt; at receive time is not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate the caller separately from the shot stream
&lt;/h3&gt;

&lt;p&gt;There are two trust questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which network connection actually sent this Server RPC?&lt;/li&gt;
&lt;li&gt;Is the &lt;code&gt;FireSourceId&lt;/code&gt; / epoch / slot in the request a stream that the server assigned to that connection?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not mix them.&lt;/p&gt;

&lt;p&gt;A useful conceptual flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CaptureOriginalCallerContext()
  -&amp;gt; ResolveReconciliationComponent(OriginalConnection)
  -&amp;gt; ValidateAssignedShotStream(Request, Caller)
  -&amp;gt; obtain CanonicalKey
  -&amp;gt; validate ammo/rate/time/aim
  -&amp;gt; authoritative trace
  -&amp;gt; PublishCanonicalShotResolution(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only a &lt;strong&gt;canonical&lt;/strong&gt;, server-validated shot key should enter the authoritative result cache or owner-only reconciliation snapshot.&lt;/p&gt;

&lt;p&gt;Treat invalid keys differently from ordinary gameplay rejection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unassigned source, future epoch, or a source belonging to another connection: audit/strike/disconnect policy; do not mutate result state.&lt;/li&gt;
&lt;li&gt;A stale epoch caused by an allowed switching race: optionally return one transient rejection to the original connection, but do not advance the authoritative snapshot.&lt;/li&gt;
&lt;li&gt;Valid key but empty magazine, rate limit, invalid timing window, or excessive aim delta: normal rejection; cache it like an accepted shot so duplicates stay idempotent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keep the result route tied to the original connection
&lt;/h3&gt;

&lt;p&gt;Do not resolve the destination again from the Weapon's &lt;em&gt;current&lt;/em&gt; owner when the result is ready. Resolve the reconciliation component from the connection captured at RPC dispatch time, ideally on an object that outlives the Pawn such as the original &lt;code&gt;PlayerController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&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;UMyShotReconciliationComponent&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Reconciliation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;ResolveReconciliationComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OriginalConnection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TryGetCachedResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CanonicalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Cached&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Reconciliation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;PublishCanonicalShotResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ValidateGameplayShot&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Reconciliation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;PublishCanonicalShotResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;BuildResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CanonicalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ammo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RejectReason&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FServerShotResult&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RewindAndTraceOnServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ConsumeAmmoOnServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ApplyDamageOnServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Reconciliation&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;PublishCanonicalShotResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BuildResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CanonicalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bHitConfirmed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ammo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;MulticastPlayConfirmedFireFx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CanonicalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CosmeticData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duplicates return the cached result to the &lt;strong&gt;same original connection&lt;/strong&gt; without repeating ammo consumption, damage, snapshot mutation, or multicast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconciliation needs an immediate path and a state path
&lt;/h3&gt;

&lt;p&gt;An Unreliable Client RPC is useful for low-latency feedback, but it can be lost. Keep a compact owner-only replicated state as the convergence path.&lt;/p&gt;

&lt;p&gt;For example, track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FireSourceId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FireEpoch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LastContiguousResolvedSequence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a small recent accepted/rejected/hit detail window&lt;/li&gt;
&lt;li&gt;authoritative ammo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Register it as owner-only replication; the &lt;code&gt;UPROPERTY&lt;/code&gt; declaration alone does not do that:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;UMyShotReconciliationComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;GetLifetimeReplicatedProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FLifetimeProperty&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;OutLifetimeProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&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;GetLifetimeReplicatedProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OutLifetimeProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;DOREPLIFETIME_CONDITION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;UMyShotReconciliationComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ShotSyncState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;COND_OwnerOnly&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;If the reconciliation object is a replicated Component/Subobject, also configure its actual replication/registration path [4,16].&lt;/p&gt;

&lt;h3&gt;
  
  
  Predicted owner FX and confirmed remote FX are different responsibilities
&lt;/h3&gt;

&lt;p&gt;The owner already played the shot immediately. The multicast mainly exists for other clients.&lt;/p&gt;

&lt;p&gt;Use a client-process &lt;code&gt;PredictedFxHistory&lt;/code&gt; to suppress a confirmed FX key that this local process already predicted, and a separate &lt;code&gt;ConfirmedFxHistory&lt;/code&gt; to make remote FX idempotent. Perform that check in an application handler that does &lt;strong&gt;not&lt;/strong&gt; depend on current possession:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AMyWeapon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ApplyConfirmedFxIfNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;FShotKey&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FFireFxData&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;FxData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetClientShotFxRegistry&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;WasPredictedByAnyLocalPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ConfirmedFxHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RememberIfNew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;PlayConfirmedFireFx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FxData&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;This survives delayed multicast delivery across death, unpossess, repossess, and ownership transfer; non-predicting AI/weapons simply play confirmed FX once.&lt;/p&gt;

&lt;p&gt;A useful regression test delays each client's application-handler entry &lt;strong&gt;after network delivery but before the prediction-history check&lt;/strong&gt;, changes possession/ownership, then resumes. The history-based implementation should still produce one total FX per client. A mutation that replaces the history check with &lt;code&gt;IsLocallyControlled()&lt;/code&gt; should fail. Delaying only &lt;code&gt;PlayConfirmedFireFx()&lt;/code&gt; is too late to catch that bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  The handlers must be idempotent
&lt;/h3&gt;

&lt;p&gt;Your design should remain correct for representative paths such as:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Owner FX&lt;/th&gt;
&lt;th&gt;Result convergence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client RPC arrives first&lt;/td&gt;
&lt;td&gt;1 predicted FX&lt;/td&gt;
&lt;td&gt;Immediate result; later property is harmless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Property arrives first&lt;/td&gt;
&lt;td&gt;1 predicted FX&lt;/td&gt;
&lt;td&gt;Snapshot resolves; later RPC is harmless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multicast arrives first&lt;/td&gt;
&lt;td&gt;Still 1 predicted FX&lt;/td&gt;
&lt;td&gt;Later RPC/property resolves the shot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server RPC request is lost&lt;/td&gt;
&lt;td&gt;1 predicted FX&lt;/td&gt;
&lt;td&gt;Later sequence/heartbeat or owner timeout cancels provisional state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client result RPC is lost&lt;/td&gt;
&lt;td&gt;1 predicted FX&lt;/td&gt;
&lt;td&gt;Owner-only property converges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multicast is lost&lt;/td&gt;
&lt;td&gt;Owner still has predicted FX&lt;/td&gt;
&lt;td&gt;Authoritative damage/ammo remain correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same game handler injected twice&lt;/td&gt;
&lt;td&gt;At most one FX&lt;/td&gt;
&lt;td&gt;Ammo/damage/result apply once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Packet duplication does not prove the same gameplay RPC reached application code twice. Test idempotence by directly injecting the same key twice into authority, result, and FX handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aim validation and lag compensation
&lt;/h2&gt;

&lt;p&gt;Checking that an aim vector is normalized is not enough.&lt;/p&gt;

&lt;p&gt;Reconstruct the trace origin from server-known historical view/Pawn/weapon state, not a client position. Compare the submitted direction with the accepted server view using state-specific tolerance for hip fire, ADS, recoil, view switching, turrets, or vehicles; also validate range, spread, interval, ammo, reload, and equipment. This constrains impossible trajectories, not all aimbots.&lt;/p&gt;

&lt;p&gt;For lag compensation, keep only needed history. Player hitboxes are common, while doors, shields, lifts, and destructible cover may also require historical collision state. Prefer a &lt;strong&gt;non-replicated historical collision proxy&lt;/strong&gt; over moving live Actors. If real Components are rewound, use a scoped guard that restores transform/collision on every exit path and does not emit extra overlaps, gameplay events, physics wake-ups, or replication dirtiness.&lt;/p&gt;

&lt;p&gt;For timestamps, &lt;code&gt;GetServerWorldTimeSeconds()&lt;/code&gt; is a useful synchronization entry point, but do not blindly trust a client value. Account for RTT, jitter, outliers, reconnects, Server Travel, and long sessions [35]. A match-relative quantized integer tick or &lt;code&gt;double&lt;/code&gt; is safer than an ever-growing absolute &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic weapons: design failure modes, not only happy paths
&lt;/h2&gt;

&lt;p&gt;There is no single correct fire protocol.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Main failure mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reliable start/stop&lt;/td&gt;
&lt;td&gt;Low message count, easy server timer&lt;/td&gt;
&lt;td&gt;Stop can wait behind reliable traffic; add state number, max duration, and forced stop on death/weapon change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreliable input state + heartbeat&lt;/td&gt;
&lt;td&gt;Converges on later heartbeat&lt;/td&gt;
&lt;td&gt;Use sequence + timeout to discard stale input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreliable request per shot&lt;/td&gt;
&lt;td&gt;Easy per-shot aim/result validation&lt;/td&gt;
&lt;td&gt;Lost shot request means that shot never happened; high ROF increases traffic and reconciliation work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choose by fire rate, aim-update needs, packet-loss behavior, reliable-queue pressure, and cheat resistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  GAS, Replication Graph, and Iris solve different problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GAS
&lt;/h3&gt;

&lt;p&gt;Gameplay Ability System is valuable when abilities, attributes, effects, tags, cooldowns, costs, and prediction need one model [13,14]. A rifle-focused FPS may not need it everywhere; using normal gunplay Components plus GAS for complex abilities can be simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replication Graph
&lt;/h3&gt;

&lt;p&gt;Replication Graph targets large Actor/connection counts by reusing persistent graph nodes for candidate selection [10]. Add it when relevance selection is measured as a bottleneck, after fixing unnecessary replication, relevancy/cull distance, update frequency, dormancy, priority, and data layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iris
&lt;/h3&gt;

&lt;p&gt;UE 5.8 documentation is awkward here: the 5.8 release notes call Iris production-ready, while UE 5.8 Iris pages still display Experimental warnings. Iris also remains opt-in [11,34]. Treat the exact engine patch, platform, plugins, replicated Subobjects, serializers, RepNotify behavior, Fast Arrays, and owner-only conditions as a regression-test matrix.&lt;/p&gt;

&lt;p&gt;And one very important rule: &lt;strong&gt;Iris and Replication Graph are not used together on the same NetDriver.&lt;/strong&gt; Iris does not support Replication Graph; filtering and prioritization take over those responsibilities [37-39]. Compare a Generic Replication + Replication Graph configuration against an Iris configuration rather than "adding Iris on top."&lt;/p&gt;

&lt;h2&gt;
  
  
  Steam and EOS are the control plane, not gameplay replication
&lt;/h2&gt;

&lt;p&gt;For Steam-only PC games, Online Subsystem Steam can handle login/session/invite/presence, with Steam Sockets where appropriate [19,20]. For multi-store or cross-platform plans, EOS provides authentication, lobbies, sessions, stats, voice, and P2P-related services [17,18]. Keep vendor APIs behind a game-facing abstraction; Lyra's Common User plugin is a useful flow reference [21].&lt;/p&gt;

&lt;h2&gt;
  
  
  Photon Fusion for Unreal is a different authority model
&lt;/h2&gt;

&lt;p&gt;Photon Fusion for Unreal 3 uses Shared Authority. That can be attractive for some co-op or distributed-authority games, but it is not the same model as one authoritative UE Dedicated Server [22].&lt;/p&gt;

&lt;p&gt;Fusion still reuses replicated/RepNotify-style properties and UE Character Networking [22,40], but logic/tooling built around one server &lt;code&gt;ROLE_Authority&lt;/code&gt; needs deeper redesign. At the August 31, 2026 baseline, Fusion for Unreal 3.0.0 is Preview; Windows/Android are supported, macOS/iOS are insufficiently tested, and console/Linux support is planned [23].&lt;/p&gt;

&lt;h2&gt;
  
  
  Use HTTP, WebSocket, and gRPC for backend work
&lt;/h2&gt;

&lt;p&gt;Use HTTP, WebSocket, or gRPC for accounts, inventory, matchmaking, allocation, results, telemetry, and LiveOps—not per-frame Character Movement or shooting. Replacing NetDriver gameplay traffic means rebuilding UE networking behavior. Keep trusted databases/services behind server-side APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dedicated-server hosting choices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon GameLift Servers&lt;/strong&gt;: placement, process integration, health, and scaling [24].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PlayFab Multiplayer Servers&lt;/strong&gt;: PlayFab-friendly hosting with an Unreal GSDK in the server build [25].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agones&lt;/strong&gt;: Kubernetes-native GameServer/Fleet/Allocation; its Unreal Client SDK/plugin belongs in the Dedicated Server process [26,27].&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare regions, startup/warm pools, UDP/ports, rollout/rollback, autoscaling, DDoS boundaries, observability, crash handling, lock-in, and team expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize bandwidth by not sending data
&lt;/h2&gt;

&lt;p&gt;Before compression, reduce who receives what.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Relevancy&lt;/strong&gt; — owner relevancy, cull distance, conditional replication [4,6,9].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update frequency&lt;/strong&gt; — tune by Actor role; it is not the server simulation tick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dormancy&lt;/strong&gt; — wake/flush &lt;em&gt;before&lt;/em&gt; changing replicated state [7].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority&lt;/strong&gt; — relative scheduling under saturation, not total bandwidth [8].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Representation&lt;/strong&gt; — quantize data and send IDs/state instead of verbose payloads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fast Array Serializer helps with changing collections such as inventory/status effects [15]; do not treat replicated order as UI sort order. For replicated Subobjects, manage registration/removal carefully so replication never touches an invalid reference; Iris adds Subobject/fragment considerations [16,37].&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheat resistance starts at the Server RPC boundary
&lt;/h2&gt;

&lt;p&gt;Validate ownership, legal state transitions, ammo/rate/speed/distance, authoritative or historical spatial state, RPC frequency, and malformed values such as NaN, invalid enums, oversized arrays, and unknown IDs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WithValidation&lt;/code&gt; failure can disconnect the caller [3]. Here, structural/protocol abuse is separated from ordinary latency/state-race rejection; that is a project policy, not a universal UE rule. Never embed service, admin, or database secrets in the player build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat bad networks as a normal test environment
&lt;/h2&gt;

&lt;p&gt;PIE with two local windows is not a networking test plan.&lt;/p&gt;

&lt;p&gt;Also test separate processes, packaged Client/Server builds, different machines/VMs, real online-service accounts, travel/reconnect, and bot load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be careful with &lt;code&gt;DropUnreliableRPC&lt;/code&gt; in UE 5.8.0
&lt;/h3&gt;

&lt;p&gt;The official command reference documents &lt;code&gt;NetEmulation.DropUnreliableRPC &amp;lt;RPCName&amp;gt; &amp;lt;0-100&amp;gt;&lt;/code&gt; [36]. Source inspection of Launcher UE 5.8.0 CL &lt;code&gt;55116800&lt;/code&gt; found a contradictory percentage/block path, so &lt;code&gt;100&lt;/code&gt; should not be used as proof that 100% of the target RPC was dropped. The public UE 5.8.1/5.8.2 hotfix lists do not identify a fix for that command [41,42].&lt;/p&gt;

&lt;p&gt;Therefore, for deterministic acceptance tests, prefer a &lt;strong&gt;test-build-only project hook&lt;/strong&gt; that explicitly skips the target call.&lt;/p&gt;

&lt;p&gt;For example, a server-side test CVar can skip only &lt;code&gt;ClientResolveShot&lt;/code&gt; while still updating the owner-only snapshot:&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="cp"&gt;#if WITH_DEV_AUTOMATION_TESTS
&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;TAutoConsoleVariable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;int32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CVarSkipClientResolveShotForTest&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="s"&gt;"game.NetTest.SkipClientResolveShot"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="mi"&gt;0&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="s"&gt;"1 skips ClientResolveShot in test builds."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a property-only convergence test should prove that the client actually had predictions to resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 local predictions enqueued
Pending before snapshot apply == 100
Authority results == 100
ClientResolveShot received == 0
Snapshot source/epoch == test source/epoch
Snapshot contiguous prefix reaches the final issued sequence
Pending removed by snapshot == 100
Pending removed by timeout == 0
Pending after snapshot apply == 0
Displayed ammo == authoritative ammo
CVar/barrier/timeout settings restored during teardown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freeze prediction timeout while the snapshot is deliberately blocked, otherwise timeout removal can create a false positive.&lt;/p&gt;

&lt;p&gt;For transport degradation, &lt;code&gt;PktLoss&lt;/code&gt;, &lt;code&gt;PktIncomingLoss&lt;/code&gt;, &lt;code&gt;PktDup&lt;/code&gt;, and &lt;code&gt;PktOrder&lt;/code&gt; are useful [28], but inject duplicate keys directly to test gameplay-handler idempotence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Networking Insights before changing architecture
&lt;/h2&gt;

&lt;p&gt;Use Networking Insights/Network Profiler to identify expensive Actors, properties, and RPCs [29,30]. "Networking is heavy" is not actionable; "this UI array replicates every tick to every connection" is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical starting matrix
&lt;/h2&gt;

&lt;p&gt;These are &lt;strong&gt;evaluation starting points, not Unreal Engine limits&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Game shape&lt;/th&gt;
&lt;th&gt;Baseline to prototype&lt;/th&gt;
&lt;th&gt;Evaluate next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2-4 player co-op&lt;/td&gt;
&lt;td&gt;Built-in Replication + Character Movement + Listen Server&lt;/td&gt;
&lt;td&gt;Steam/EOS P2P/relay, whether host migration is worth it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8-16 player PvP&lt;/td&gt;
&lt;td&gt;Dedicated Server + built-in Replication&lt;/td&gt;
&lt;td&gt;GAS, bounded lag compensation, hosting platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32-100 players / larger maps&lt;/td&gt;
&lt;td&gt;Dedicated Server + spatial relevancy&lt;/td&gt;
&lt;td&gt;Generic + Replication Graph &lt;strong&gt;or&lt;/strong&gt; Iris, Fast Arrays, autoscaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Very large Actor counts&lt;/td&gt;
&lt;td&gt;Dedicated Server + bot load tests&lt;/td&gt;
&lt;td&gt;Compare Replication Graph and Iris as separate NetDriver configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Implementation order I would use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Connect two clients to a minimal Dedicated Server C++ project.&lt;/li&gt;
&lt;li&gt;Make movement, possession, death, respawn, HP, weapon, and ammo correct.&lt;/li&gt;
&lt;li&gt;Split shooting into local prediction and authoritative resolution; add ownership checks, validation, and rate limits.&lt;/li&gt;
&lt;li&gt;Fix late join and relevancy re-entry, then make latency/loss tests routine.&lt;/li&gt;
&lt;li&gt;Add Steam/EOS login, sessions, and invites.&lt;/li&gt;
&lt;li&gt;Run a packaged server remotely and load it with bots while measuring frame time and bandwidth.&lt;/li&gt;
&lt;li&gt;Evaluate GAS, Replication Graph, or Iris only for measured problems.&lt;/li&gt;
&lt;li&gt;Integrate allocation, health, and shutdown with the hosting platform.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start gameplay synchronization with UE Replication; treat Steam/EOS as online services and GameLift/PlayFab/Agones as server operations.&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;UCharacterMovementComponent&lt;/code&gt; handle normal Character prediction; replicate reconstructable state and use transient RPCs selectively.&lt;/li&gt;
&lt;li&gt;Make shooting server-authoritative, keyed by source/epoch/sequence, with an immediate owner result plus owner-only replicated convergence state.&lt;/li&gt;
&lt;li&gt;Keep predicted owner FX separate from confirmed remote FX, and make every entry point idempotent.&lt;/li&gt;
&lt;li&gt;Add GAS, Replication Graph, Iris, or external networking SDKs only after fixed-version load tests show what problem you are actually solving.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/networking-and-multiplayer-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Networking and Multiplayer in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/setting-up-dedicated-servers-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Setting Up Dedicated Servers in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/remote-procedure-calls-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Remote Procedure Calls in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/replicate-actor-properties-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Replicate Actor Properties in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Understanding Networked Movement in the Character Movement Component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/actor-relevancy-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Actor Relevancy in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/actor-network-dormancy-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Actor Network Dormancy in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/actor-priority-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Actor Priority in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/detailed-actor-replication-flow-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Detailed Actor Replication Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/replication-graph-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Replication Graph in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/iris-replication-system-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Iris Replication System in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/PluginIndex/NetworkPrediction?application_version=5.8" rel="noopener noreferrer"&gt;Network Prediction API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/using-gameplay-abilities-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Using Gameplay Abilities in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/abilities-in-lyra-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Abilities in Lyra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/NetCore/FFastArraySerializer?application_version=5.8" rel="noopener noreferrer"&gt;Fast Array Serializer API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/replicating-uobjects-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Replicating UObjects in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/overview-of-online-services-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Overview of Online Services in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/online-subsystem-eos-plugin-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Online Subsystem EOS Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/online-subsystem-steam-interface-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Online Subsystem Steam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/using-steam-sockets-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Using Steam Sockets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/common-user-plugin-in-unreal-engine-for-lyra-sample-game?application_version=5.8" rel="noopener noreferrer"&gt;Common User Plugin in Lyra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion-unreal/v3/fusion-intro" rel="noopener noreferrer"&gt;Photon Fusion for Unreal Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion-unreal/v3/getting-started/sdk-download" rel="noopener noreferrer"&gt;Photon Fusion for Unreal SDK Download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/gameliftservers/latest/developerguide/integration-engines-setup-unreal.html" rel="noopener noreferrer"&gt;Amazon GameLift Servers Unreal Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/xbox/playfab/multiplayer/servers/server-sdks/unreal-gsdk/" rel="noopener noreferrer"&gt;PlayFab Multiplayer Servers Unreal GSDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agones.dev/site/docs/overview/" rel="noopener noreferrer"&gt;Agones Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agones.dev/site/docs/guides/client-sdks/unreal/" rel="noopener noreferrer"&gt;Agones Unreal Client SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/using-network-emulation-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Using Network Emulation in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/networking-insights-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Networking Insights in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/using-the-network-profiler-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Using the Network Profiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/game-mode-and-game-state-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Game Mode and Game State in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/actor-owner-and-owning-connection-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Actor Owner and Owning Connection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/replicated-object-execution-order-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Replicated Object Execution Order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/unreal-engine-5-8-release-notes?application_version=5.8" rel="noopener noreferrer"&gt;Unreal Engine 5.8 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/BlueprintAPI/GameState/GetServerWorldTimeSeconds?application_version=5.8" rel="noopener noreferrer"&gt;Get Server World Time Seconds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/unreal-engine-console-commands-reference?application_version=5.8" rel="noopener noreferrer"&gt;Unreal Engine Console Commands Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/migrate-to-iris-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Migrate to Iris in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/iris-filtering-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Iris Filtering in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/iris-prioritization-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Iris Prioritization in Unreal Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion-unreal/v3/manual/replication/replicated-properties" rel="noopener noreferrer"&gt;Photon Fusion Unreal Property Replication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.unrealengine.com/t/5-8-1-hotfix-released/2738864" rel="noopener noreferrer"&gt;UE 5.8.1 Hotfix Released&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.unrealengine.com/t/5-8-2-hotfix-released/2746335" rel="noopener noreferrer"&gt;UE 5.8.2 Hotfix Released&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>unrealengine</category>
      <category>gamedev</category>
      <category>multiplayer</category>
      <category>cpp</category>
    </item>
    <item>
      <title>uGUI or UI Toolkit? A Practical Unity 6.5 UI Selection Guide</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Wed, 09 Sep 2026 00:44:52 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/ugui-or-ui-toolkit-a-practical-unity-65-ui-selection-guide-3i27</link>
      <guid>https://dev.to/gamedevtoollab/ugui-or-ui-toolkit-a-practical-unity-65-ui-selection-guide-3i27</guid>
      <description>&lt;p&gt;Choosing a UI system in Unity is not a simple "new UI Toolkit versus old uGUI" decision. Unity gives us three major options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;uGUI&lt;/strong&gt;: GameObject- and Component-based runtime UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Toolkit&lt;/strong&gt;: UXML, USS, and C# for runtime and Editor UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMGUI&lt;/strong&gt;: immediate-mode UI such as &lt;code&gt;OnGUI&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unity's 6.5 comparison still lists uGUI as the recommended general-purpose runtime UI, UI Toolkit as the runtime alternative, and UI Toolkit as the recommended Editor UI. That does not mean every runtime screen should use uGUI. A HUD, a 500-row inventory, a virtual joystick, a world-space panel, and an XR menu have different constraints.&lt;/p&gt;

&lt;p&gt;This article uses two stages: first apply &lt;strong&gt;hard gates&lt;/strong&gt; for version, render pipeline, input, XR, animation, and must-pass requirements; then compare only the surviving candidates using &lt;strong&gt;soft preferences&lt;/strong&gt; and identical prototypes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Version scope:&lt;/strong&gt; rechecked September 9, 2026. The article targets Unity 6.5 and uses &lt;code&gt;6000.5.11f1&lt;/code&gt;, the latest 6.5 patch then listed in the Unity Download Archive. Unity 6.3 LTS is included where its world-space workflow differs. Input System is &lt;code&gt;1.20.0&lt;/code&gt;; XR Interaction Toolkit is &lt;code&gt;3.6.0&lt;/code&gt;. In production, pin &lt;code&gt;ProjectVersion.txt&lt;/code&gt; and &lt;code&gt;packages-lock.json&lt;/code&gt; and rerun the tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;First candidate&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HUD, virtual controls, Animator/Timeline-heavy presentation&lt;/td&gt;
&lt;td&gt;uGUI&lt;/td&gt;
&lt;td&gt;Strong Prefab, EventSystem, and animation workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory, settings, search, large lists&lt;/td&gt;
&lt;td&gt;UI Toolkit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ListView&lt;/code&gt;, USS, reusable styling, flexible layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World-space or XR UI&lt;/td&gt;
&lt;td&gt;Prototype after hard gates&lt;/td&gt;
&lt;td&gt;Version and input route matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor Window / Inspector&lt;/td&gt;
&lt;td&gt;UI Toolkit&lt;/td&gt;
&lt;td&gt;Unity's recommended Editor UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display-only runtime debug overlay&lt;/td&gt;
&lt;td&gt;IMGUI can be acceptable&lt;/td&gt;
&lt;td&gt;Small implementation for short-lived tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interactive Player debug UI&lt;/td&gt;
&lt;td&gt;uGUI or UI Toolkit&lt;/td&gt;
&lt;td&gt;Runtime IMGUI has Input System constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mature uGUI project&lt;/td&gt;
&lt;td&gt;Usually keep uGUI&lt;/td&gt;
&lt;td&gt;Avoid migration and mixed-stack regression cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A useful default is: &lt;strong&gt;gameplay-, GameObject-, and keyframe-heavy UI favors uGUI; data-, list-, and style-heavy UI favors UI Toolkit; Editor tooling favors UI Toolkit; IMGUI stays narrow.&lt;/strong&gt; Hard gates override that default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply hard gates before preferences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hard gate&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;If it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unity version&lt;/td&gt;
&lt;td&gt;6.3 world space uses &lt;code&gt;UIDocument&lt;/code&gt;; 6.5 centers the workflow on &lt;code&gt;PanelRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Use documentation for the exact version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render pipeline&lt;/td&gt;
&lt;td&gt;UI Shader Graph in 6.5 is &lt;strong&gt;URP-only&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Built-in/HDRP need another shader/material route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;Virtual Mouse is uGUI-only; standard tracked input is not UI Toolkit XR input&lt;/td&gt;
&lt;td&gt;Use uGUI or the XRI-specific UI Toolkit path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animation&lt;/td&gt;
&lt;td&gt;PanelRenderer animation support is not general Timeline integration&lt;/td&gt;
&lt;td&gt;Prefer uGUI if Timeline is mandatory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime IMGUI&lt;/td&gt;
&lt;td&gt;Input System-only Player input is incompatible with IMGUI&lt;/td&gt;
&lt;td&gt;Test &lt;code&gt;Both&lt;/code&gt; or move the tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product requirements&lt;/td&gt;
&lt;td&gt;XR hardware, RTL, text scaling, accessibility, focus, performance budget&lt;/td&gt;
&lt;td&gt;Do not adopt an unverified candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Record the Editor patch, packages, SRP, input route, build, and device before judging a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each UI system is good at
&lt;/h2&gt;

&lt;h3&gt;
  
  
  uGUI: scene-oriented runtime UI
&lt;/h3&gt;

&lt;p&gt;uGUI is built around &lt;code&gt;Canvas&lt;/code&gt;, &lt;code&gt;RectTransform&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt;, &lt;code&gt;TextMeshProUGUI&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt;, Scenes, and Prefabs. It fits Inspector/Prefab/Animator/Timeline workflows and is a strong default for HUDs, reward popups, combat indicators, virtual sticks, and heavily animated menus. Its main risk is architectural: logic and update work can spread across Prefabs, MonoBehaviours, Layout components, and Canvases.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI Toolkit: structure, style, and retained state
&lt;/h3&gt;

&lt;p&gt;UI Toolkit uses UXML, USS, and C#. Its Flexbox-style layout is based on Yoga layout principles. It is attractive for inventories, filters, tables, settings, design systems, and repeated data; &lt;code&gt;ListView&lt;/code&gt; is particularly useful for large reusable lists. UXML/USS are not identical to HTML/CSS: Panel Settings, events, atlases, supported properties, and runtime features remain Unity-specific and version-sensitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMGUI: keep it narrow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnGUI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GUILayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Enemy Count: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_enemyCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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;This is fine for a display-only diagnostic or small existing Editor tool. For product UI or a long-lived interactive Player tool, uGUI or UI Toolkit usually gives a safer input, styling, reuse, and accessibility path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime and Editor are different decisions
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;runtime UI&lt;/strong&gt;, uGUI and UI Toolkit are both candidates after the hard gates. Unity 6.5 still recommends uGUI overall, while UI Toolkit can fit data- and style-heavy screens better.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Editor UI&lt;/strong&gt;, start new Editor Windows and Custom Inspectors with UI Toolkit. A small stable IMGUI Editor tool does not need a rewrite. Editor IMGUI is also separate from Player &lt;code&gt;OnGUI&lt;/code&gt; compatibility with the Input System.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unity 6.3 and 6.5 use different world-space workflows
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Unity 6.3 LTS&lt;/th&gt;
&lt;th&gt;Unity 6.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Main world-space component&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UIDocument&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PanelRenderer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size / pivot&lt;/td&gt;
&lt;td&gt;UI Document &lt;code&gt;World-Space Dimensions&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Panel Renderer &lt;code&gt;World-Space Dimensions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple containers&lt;/td&gt;
&lt;td&gt;UI Documents sharing Panel Settings&lt;/td&gt;
&lt;td&gt;Panel Renderers sharing Panel Settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspector references&lt;/td&gt;
&lt;td&gt;Do not assume &lt;code&gt;VisualElementReference&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Runtime &lt;code&gt;PanelRenderer&lt;/code&gt; can use it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy &lt;code&gt;UIDocument&lt;/code&gt; access&lt;/td&gt;
&lt;td&gt;UQuery / query-based&lt;/td&gt;
&lt;td&gt;Still query-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor &lt;code&gt;VisualElementReference&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animator / legacy Animation&lt;/td&gt;
&lt;td&gt;Do not assume 6.5 support&lt;/td&gt;
&lt;td&gt;PanelRenderer content has dedicated support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General Animation Clip / Timeline&lt;/td&gt;
&lt;td&gt;Do not assume it&lt;/td&gt;
&lt;td&gt;Comparison table still lists UI Toolkit as unsupported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World-space ordering&lt;/td&gt;
&lt;td&gt;z distance, sibling &lt;code&gt;sortingOrder&lt;/code&gt;, camera culling&lt;/td&gt;
&lt;td&gt;Same; no 2D Sorting Layers integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you are on 6.3, do not search for a &lt;code&gt;PanelRenderer&lt;/code&gt; from the 6.5 workflow. Use &lt;code&gt;UIDocument.rootVisualElement&lt;/code&gt; and the 6.3 UI Document path.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VisualElementReference&amp;lt;T&amp;gt;&lt;/code&gt; is a &lt;strong&gt;6.5 runtime feature for &lt;code&gt;PanelRenderer&lt;/code&gt;&lt;/strong&gt;; it is not a universal replacement for UQuery in Editor UI or legacy &lt;code&gt;UIDocument&lt;/code&gt; setups.&lt;/p&gt;

&lt;p&gt;Unity 6000.5.0f1 also added Animator/legacy Animation support for PanelRenderer content. That does &lt;strong&gt;not&lt;/strong&gt; make UI Toolkit a general equivalent of uGUI's Animation Clip/Timeline workflow. If existing Timeline-authored UI is a hard requirement, favor uGUI unless a prototype proves the required path.&lt;/p&gt;

&lt;p&gt;World-space UI Toolkit also does not integrate with 2D Sorting Layers. Use camera-relative z distance, sibling &lt;code&gt;sortingOrder&lt;/code&gt; where applicable, and GameObject Layers plus camera culling. A design that depends on SpriteRenderer-style Sorting Layers needs a different plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  World-space rendering is not XR controller input
&lt;/h2&gt;

&lt;p&gt;A panel rendering in 3D space does not prove that a tracked VR controller can operate it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Standard Input System tracked input
&lt;/h3&gt;

&lt;p&gt;Input System 1.20 documents tracked UI input using &lt;code&gt;InputSystemUIInputModule&lt;/code&gt;, tracked position/orientation actions, and &lt;code&gt;TrackedDeviceRaycaster&lt;/code&gt; on a &lt;code&gt;Canvas&lt;/code&gt;. That is the standard tracked &lt;strong&gt;uGUI&lt;/strong&gt; route.&lt;/p&gt;

&lt;p&gt;UI Toolkit is different: Input System 1.20 states that tracked-type XR input is not supported with UI Toolkit, and UI Toolkit does not support &lt;code&gt;TrackedDeviceRaycaster&lt;/code&gt; because it performs its own raycasting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InputSystemUIInputModule
  + trackedDevicePosition / trackedDeviceOrientation
  + TrackedDeviceRaycaster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not copy that Canvas route into UI Toolkit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Panel Input Configuration
&lt;/h3&gt;

&lt;p&gt;Unity 6.5 &lt;code&gt;Panel Input Configuration&lt;/code&gt; handles Event Camera, Physics Layer, maximum distance, and EventSystem redirection, and can create helpers such as &lt;code&gt;Panel Event Handler&lt;/code&gt;, &lt;code&gt;Panel Raycaster&lt;/code&gt;, and &lt;code&gt;World Document Raycaster&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its screen-based pointer route converts screen position through an Event Camera into a world-space ray. &lt;strong&gt;By itself, it is not a tracked XR-controller solution.&lt;/strong&gt; &lt;code&gt;World Document Raycaster&lt;/code&gt; is also not the Input System's &lt;code&gt;TrackedDeviceRaycaster&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  XRI 3.6 dedicated UI Toolkit integration
&lt;/h3&gt;

&lt;p&gt;This is what keeps UI Toolkit viable for VR. Pin XRI 3.6, import the &lt;strong&gt;World Space UI&lt;/strong&gt; sample, follow that package's setup, and validate on hardware. Test Ray, Poke, both controllers, focus, colliders, modal behavior, and latency. If uGUI and UI Toolkit coexist, also test event routing and focus ownership.&lt;/p&gt;

&lt;p&gt;The Unity 6.5 core manual and the XRI 3.6 sample can use different component arrangements. Follow the exact sample for the pinned package rather than mechanically replacing its components with names from newer core documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout and information density
&lt;/h2&gt;

&lt;p&gt;For a fixed combat HUD, uGUI anchors and RectTransforms are often enough, especially when the project already has Safe Area helpers, localization Prefabs, tween scripts, and animation assets.&lt;/p&gt;

&lt;p&gt;For search, filters, multiple panes, reusable themes, text scaling, or hundreds of rows, prototype UI Toolkit first. &lt;code&gt;ListView&lt;/code&gt; is built around reusable items; a naive uGUI list with hundreds of active GameObjects usually needs explicit pooling and careful layout design.&lt;/p&gt;

&lt;p&gt;The real comparison is not "Flexbox versus RectTransform." It is the cost of the actual screen under realistic content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animation and shaders
&lt;/h2&gt;

&lt;p&gt;If UI artists repeatedly tune Animator Controllers, Animation Clips, Timeline tracks, tween components, or UI particle assets, uGUI usually matches the production workflow better. UI Toolkit has USS transitions, class-driven state changes, and 6.5 PanelRenderer-specific animation, but that does not guarantee parity with an established uGUI pipeline.&lt;/p&gt;

&lt;p&gt;Ask: &lt;strong&gt;who changes the animation, in which tool, and how often?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  UI Shader Graph is URP-only
&lt;/h3&gt;

&lt;p&gt;Unity 6.5 UI Shader Graph is a &lt;strong&gt;URP feature&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URP&lt;/strong&gt;: prototype with the real masks, stencil behavior, blending, world-space setup, and target GPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in/HDRP&lt;/strong&gt;: remove UI Shader Graph from the plan and validate another custom Material/Shader route.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Render Pipeline belongs in both the hard-gate table and every benchmark record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input
&lt;/h2&gt;

&lt;p&gt;Virtual sticks, multi-touch gestures, EventSystem extensions, and Virtual Mouse generally push the decision toward uGUI. Input System 1.20's Virtual Mouse path is for uGUI rather than a general UI Toolkit/IMGUI cursor abstraction.&lt;/p&gt;

&lt;p&gt;UI Toolkit also distinguishes pointer and navigation events. A pointer click raises &lt;code&gt;ClickEvent&lt;/code&gt;; a gamepad submit uses navigation events. For a &lt;code&gt;Button&lt;/code&gt; that should activate through either path, &lt;code&gt;Button.clicked&lt;/code&gt; can be a better semantic hook than listening only for &lt;code&gt;ClickEvent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Prototype focus order, device switching, focus restoration after a modal, drag/scroll, IME, software keyboard, local multiplayer, and back/cancel routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime IMGUI with the Input System
&lt;/h3&gt;

&lt;p&gt;Input System 1.20 documents IMGUI as incompatible with the Input System package in the Player path. A display-only &lt;code&gt;OnGUI&lt;/code&gt; overlay can still be useful, but an interactive Player debug menu should not silently assume Input System-only will work.&lt;/p&gt;

&lt;p&gt;You can test &lt;strong&gt;Active Input Handling = Both&lt;/strong&gt;, but then verify duplicate processing and performance. Long-lived interactive Player tools are usually better in uGUI or UI Toolkit. Editor IMGUI is a separate case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization and accessibility
&lt;/h2&gt;

&lt;p&gt;Do not choose from an English mockup alone. Where relevant, make maximum text length, RTL, fallback fonts, 200% text scaling, non-color-only states, keyboard operation, and platform assistive technology must-pass requirements.&lt;/p&gt;

&lt;p&gt;Unity's Accessibility Hierarchy still needs deliberate names, roles, states, focus, and dynamic updates. Neither UI system makes accessibility automatic; validate on the target OS with the target assistive technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: benchmark the update pattern, not the brand
&lt;/h2&gt;

&lt;p&gt;Use the same device, data set, animation, render pipeline, and Graphics API.&lt;/p&gt;

&lt;p&gt;For uGUI, Unity's optimization guidance emphasizes reducing expensive Canvas rebuilds, avoiding unnecessary layout work and raycast targets, and splitting Canvases by update behavior rather than arbitrary counts. Pool large repeated lists.&lt;/p&gt;

&lt;p&gt;For UI Toolkit, test real &lt;code&gt;ListView&lt;/code&gt; reuse, transforms, Dynamic Atlas, masks, transparency, and panel organization. Do not claim "more panels are always slower." Capture UI Toolkit-related Profiler data instead.&lt;/p&gt;

&lt;p&gt;Predefine the metrics and aggregation: CPU time, GC allocations, draw calls, memory, first-open time, open spike, scroll p95/worst, and input response. A benchmark without a fixed measurement interval is not reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing uGUI and UI Toolkit
&lt;/h2&gt;

&lt;p&gt;Mixing is possible, but creates two focus systems, styling models, ordering rules, modal interactions, input-blocking concerns, and extra QA. Prefer screen/navigation boundaries: for example, "HUD is uGUI; inventory is UI Toolkit." Avoid alternating technologies inside one dialog.&lt;/p&gt;

&lt;p&gt;Use a navigation service or presenter layer between systems rather than letting one hierarchy manipulate the other directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep game logic outside the View
&lt;/h2&gt;

&lt;p&gt;A migration becomes expensive when the View owns inventory rules, purchases, networking, saves, analytics, and transitions. Put a small interface at the boundary:&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;interface&lt;/span&gt; &lt;span class="nc"&gt;IInventoryView&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ItemSelected&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;SetItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryItemViewModel&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;void&lt;/span&gt; &lt;span class="nf"&gt;SetGold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;value&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;A presenter can depend on this interface and game-side services. Migration is not free, but the blast radius is smaller when the View implementation changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reproducible selection process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Freeze the test matrix
&lt;/h3&gt;

&lt;p&gt;Record the exact Editor patch, package versions, SRP, Build Target, backend, Graphics API, device/OS, resolution, refresh rate, and thermal/power state. Different matrices are different tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Apply hard gates before soft preferences
&lt;/h3&gt;

&lt;p&gt;Reject unavailable components, unsupported pipelines, unusable input routes, and failed product requirements first. Preserve evidence for rejected candidates. Only surviving candidates should be compared for team skill, authoring speed, layout, animation workflow, maintenance, and migration cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resolve conflicts deterministically
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Evaluate hard gates per candidate.&lt;/li&gt;
&lt;li&gt;If the UI can split at a screen/navigation boundary, prototype that mixed design including focus, modal, ordering, input blocking, and transitions.&lt;/li&gt;
&lt;li&gt;Otherwise compare uGUI and UI Toolkit on the &lt;strong&gt;same&lt;/strong&gt; screen, build, data set, device, and input sequence.&lt;/li&gt;
&lt;li&gt;Only candidates whose complete must-pass set is &lt;code&gt;pass&lt;/code&gt; enter the final cost/performance comparison.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pre-register the next action: &lt;code&gt;unknown&lt;/code&gt; means &lt;code&gt;retest&lt;/code&gt;; any &lt;code&gt;fail&lt;/code&gt; means &lt;code&gt;no-adopt&lt;/code&gt;; both candidates failing means &lt;code&gt;revise-requirement&lt;/code&gt;; one passing candidate can be adopted; two passing candidates are compared against a predefined equivalence band. A tie requires a more discriminating fixture, then a pre-registered final tie-breaker such as migration cost. Do not invent a metric after seeing the result.&lt;/p&gt;

&lt;p&gt;Prototype count is determined by coverage, not by a fixed number. Add fixtures for uncovered XR, RTL/text scaling/accessibility, mixed-stack focus/modal/back, shader/SRP, or mobile requirements. Every must-pass ID needs at least one procedure and one evidence artifact.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep candidate records and the ADR separate
&lt;/h3&gt;

&lt;p&gt;A candidate record should be immutable and reproducible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;record-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TR-UI-CMP-QUEST-UGUI-01&lt;/span&gt;
&lt;span class="na"&gt;comparison-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UI-CMP-QUEST-INVENTORY&lt;/span&gt;
&lt;span class="na"&gt;candidate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ugui&lt;/span&gt;

&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;unity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6000.5.11f1&lt;/span&gt;
  &lt;span class="na"&gt;project-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;git-sha&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Android&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;IL2CPP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;sha256&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;rendering&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;pipeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;URP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;graphics-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Vulkan&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Meta Quest 3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;refresh-hz&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;90&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;fixture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dataset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Tests/UI/inventory-500.json&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;sha256&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;input-sequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scroll&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;modal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;left/right&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ray&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;poke"&lt;/span&gt;
  &lt;span class="na"&gt;random-seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48151623&lt;/span&gt;

&lt;span class="na"&gt;measurement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;warm-up&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;trials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inventory-ready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;through&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scripted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sequence"&lt;/span&gt;
  &lt;span class="na"&gt;outlier-policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;preserve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;trials"&lt;/span&gt;
  &lt;span class="na"&gt;aggregation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;median&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;p95&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;worst&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;must-pass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FUNC-FOCUS-001&lt;/span&gt;
    &lt;span class="na"&gt;criterion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Modal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;restores&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;previous&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;focus"&lt;/span&gt;
    &lt;span class="na"&gt;procedure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Select&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;open/close&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;modal,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;continue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;navigation"&lt;/span&gt;
    &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Navigation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;resumes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
    &lt;span class="na"&gt;actual&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30/30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;restored&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;focus"&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Tests/UI/evidence/focus.mp4&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;sha256&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each requirement needs &lt;code&gt;criterion&lt;/code&gt;, &lt;code&gt;procedure&lt;/code&gt;, &lt;code&gt;expected&lt;/code&gt;, &lt;code&gt;actual&lt;/code&gt;, &lt;code&gt;pass|fail|unknown&lt;/code&gt;, and evidence. Any &lt;code&gt;fail&lt;/code&gt; or &lt;code&gt;unknown&lt;/code&gt; makes the candidate ineligible. Also preserve build configuration, package versions, fixture hashes, warm-up, measurement interval, trial count, aggregation, actual metric values/units, and whether each budget is an &lt;code&gt;example&lt;/code&gt; or an &lt;code&gt;approved&lt;/code&gt; threshold with its basis and approver.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ADR is another file&lt;/strong&gt;. It links all candidate record IDs/hashes and stores comparison scope, status, owner/date, alternatives, decision, rejection reasons, trade-offs, consequences, open issues, and revisit triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;adr-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ADR-UI-QUEST-INVENTORY&lt;/span&gt;
&lt;span class="na"&gt;comparison-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UI-CMP-QUEST-INVENTORY&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;accepted&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UI Platform Lead&lt;/span&gt;

&lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adopt&lt;/span&gt;
  &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ugui&lt;/span&gt;
  &lt;span class="na"&gt;rationale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must-pass&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;passed;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;uGUI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;won&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outside&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;predefined&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;band"&lt;/span&gt;

&lt;span class="na"&gt;revisit-triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Unity/Input System/XRI upgrade&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;item count exceeds &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;XR interaction requirements change&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test record answers &lt;strong&gt;what happened under a reproducible build&lt;/strong&gt;. The ADR answers &lt;strong&gt;why this option won and when the decision must be reopened&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Let content authors test the workflow
&lt;/h3&gt;

&lt;p&gt;Have UI designers or technical artists change copy, spacing, colors, and animation. Record merge conflicts, diff readability, what they can edit without programmer help, and time per representative change. Authoring cost is architecture cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should an existing uGUI project migrate?
&lt;/h2&gt;

&lt;p&gt;Ask whether migration cost can be recovered, not whether migration is technically possible. Benefits can include shared styling, better large-list workflows, flexible layouts, fewer Prefab conflicts, and shared runtime/Editor concepts. Costs include reimplementation, input, focus, animation, localization, analytics, plugins, and regression QA.&lt;/p&gt;

&lt;p&gt;Migrate by screen boundary, preferably with an independent new screen. Avoid a broad rewrite when release is near, the current UI is stable, the alleged performance problem is unmeasured, a must-have path is unsupported, or mixed-stack QA is unfunded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final audit before adoption
&lt;/h2&gt;

&lt;p&gt;Retest if any of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the team chose from "uGUI is obsolete" or "UI Toolkit is automatically faster" without measurement;&lt;/li&gt;
&lt;li&gt;Unity 6.3 &lt;code&gt;UIDocument&lt;/code&gt; and 6.5 &lt;code&gt;PanelRenderer&lt;/code&gt; workflows are mixed;&lt;/li&gt;
&lt;li&gt;PanelRenderer animation is treated as general Timeline integration;&lt;/li&gt;
&lt;li&gt;UI Shader Graph is assumed outside URP;&lt;/li&gt;
&lt;li&gt;UI Toolkit XR uses standard tracked input or &lt;code&gt;TrackedDeviceRaycaster&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Panel Input Configuration&lt;/code&gt; alone is treated as the XR-controller solution;&lt;/li&gt;
&lt;li&gt;Runtime IMGUI input or mixed focus/modal behavior is untested;&lt;/li&gt;
&lt;li&gt;must-pass evidence, candidate records, thresholds, trial conditions, ADR, or revisit triggers are missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice: &lt;strong&gt;uGUI fits gameplay/keyframe-heavy UI; UI Toolkit fits list/layout/style/Editor-heavy UI; IMGUI fits narrow development UI.&lt;/strong&gt; Production adoption should follow hard-gate success and reproducible target-device evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://unity.com/releases/editor/archive" rel="noopener noreferrer"&gt;Unity Download Archive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/UI-system-compare.html" rel="noopener noreferrer"&gt;Unity 6.5: Comparison of UI systems in Unity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/ui-systems/create-world-space-ui.html" rel="noopener noreferrer"&gt;Unity 6.3: Create a World Space UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/ui-systems/create-world-space-ui.html" rel="noopener noreferrer"&gt;Unity 6.5: Create a World Space UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/ui-systems/reference-visual-elements-in-inspector.html" rel="noopener noreferrer"&gt;Unity 6.5: Reference runtime visual elements in Inspector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/ui-systems/world-space-panel-input-configuration.html" rel="noopener noreferrer"&gt;Unity 6.5: Panel Input Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/ui-systems/ui-shader-graph.html" rel="noopener noreferrer"&gt;Unity 6.5: UI Shader Graph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-LayoutEngine.html" rel="noopener noreferrer"&gt;Unity 6.5: Layout engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unity.com/releases/editor/whats-new/6000.5.0f1" rel="noopener noreferrer"&gt;Unity 6000.5.0f1 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unity.com/releases/editor/whats-new/6000.5.11f1" rel="noopener noreferrer"&gt;Unity 6000.5.11f1 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/com.unity.inputsystem.html" rel="noopener noreferrer"&gt;Unity 6.5: Input System package version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/understand-ui-compatibility.html" rel="noopener noreferrer"&gt;Input System 1.20: Understand UI system compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/supported-ui-input-types-tracked.html" rel="noopener noreferrer"&gt;Input System 1.20: Tracked input UI support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/virtual-mouse-ui-cursor-control.html" rel="noopener noreferrer"&gt;Input System 1.20: Virtual Mouse UI cursor control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/use-imgui-alongside-input-system.html" rel="noopener noreferrer"&gt;Input System 1.20: Use IMGUI alongside the Input System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/introduction-multiplayer-ui-input.html" rel="noopener noreferrer"&gt;Input System 1.20: Multiplayer UI input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.6/manual/samples-world-space-ui.html" rel="noopener noreferrer"&gt;XRI 3.6: World Space UI sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unity.com/how-to/unity-ui-optimization-tips" rel="noopener noreferrer"&gt;Unity: Optimization tips for Unity UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-uxml-element-ListView.html" rel="noopener noreferrer"&gt;Unity 6.5: ListView&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/best-practice-guides/ui-toolkit-for-advanced-unity-developers/optimizing-performance.html" rel="noopener noreferrer"&gt;Unity 6.5: Optimizing UI Toolkit performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/UIE-Transitioning-From-UGUI.html" rel="noopener noreferrer"&gt;Unity 6.5: Transitioning from uGUI to UI Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/accessibility/module-intro.html" rel="noopener noreferrer"&gt;Unity 6.5: Accessibility module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>ui</category>
      <category>csharp</category>
    </item>
    <item>
      <title>How Optimized Is Unity Shader Graph in Unity 6? Where HLSL Still Wins</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Tue, 08 Sep 2026 02:11:03 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/how-optimized-is-unity-shader-graph-in-unity-6-where-hlsl-still-wins-4222</link>
      <guid>https://dev.to/gamedevtoollab/how-optimized-is-unity-shader-graph-in-unity-6-where-hlsl-still-wins-4222</guid>
      <description>&lt;p&gt;A few years ago, I treated Shader Graph as a prototyping tool. When a shader became performance-sensitive, I often rewrote the bottleneck in HLSL because stage placement, precision, branching, reuse, and pass structure were easier to control by hand.&lt;/p&gt;

&lt;p&gt;So how much of that old rule still applies?&lt;/p&gt;

&lt;p&gt;This article uses &lt;strong&gt;Unity 6.3 LTS (6000.3) and Shader Graph 17.3&lt;/strong&gt; as the baseline: &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.shadergraph.html" rel="noopener noreferrer"&gt;Shader Graph package information&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For ordinary URP and HDRP materials, starting with Shader Graph is a reasonable default. Do not rewrite a finished graph in HLSL just because it is a graph. Inspect the generated shader and measure the target GPU first.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If Shader Graph and handwritten HLSL express the same work in the same stage, with the same precision, samples, branches, passes, and pipeline features, both eventually enter the platform shader compiler. Their final code may be similar, though identical output is not guaranteed.&lt;/p&gt;

&lt;p&gt;The useful question is therefore: &lt;strong&gt;what work is executed, in which stage, how many times, and across how many passes and variants?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not a fixed-hardware benchmark. It is a guide to what Shader Graph generates, what it does not optimize for you, and when profiling should push you toward Custom Function or handwritten ShaderLab/HLSL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers you should not confuse
&lt;/h2&gt;

&lt;p&gt;Separate &lt;strong&gt;the graph&lt;/strong&gt;, &lt;strong&gt;Unity-generated ShaderLab/HLSL&lt;/strong&gt;, and &lt;strong&gt;the platform-compiled GPU code&lt;/strong&gt;. Nodes are not runtime objects executed one by one; Shader Graph generates source, then normal shader compilation takes over.&lt;/p&gt;

&lt;p&gt;That is why a 100-node graph does not mean 100 runtime function calls, and why a long generated file is not automatically slow. Pipeline templates, varyings, keywords, lighting integration, and helper code can inflate source length. Conversely, a tiny handwritten shader can still be expensive if it performs many texture reads, screen-space operations, or loops.&lt;/p&gt;

&lt;p&gt;Compare generated passes/stages, texture samples, branch structure, varyings/register pressure, variant count, warm-up, and finally GPU time on the target device.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shader Graph optimizes today
&lt;/h2&gt;

&lt;p&gt;Modern Shader Graph is best treated as a &lt;strong&gt;shader code generator and authoring system&lt;/strong&gt;, not a runtime abstraction layer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What it can do&lt;/th&gt;
&lt;th&gt;What it will not do for you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shader Graph generation&lt;/td&gt;
&lt;td&gt;Trace dependencies, generate per-pass/per-stage code, emit fields/varyings&lt;/td&gt;
&lt;td&gt;Redesign your algorithm, always choose the optimal stage, merge every redundant sample&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HLSL compilation&lt;/td&gt;
&lt;td&gt;Constant folding, dead-code elimination, inlining&lt;/td&gt;
&lt;td&gt;Produce identical machine code on every GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Driver/backend&lt;/td&gt;
&lt;td&gt;Generate target-specific instructions&lt;/td&gt;
&lt;td&gt;Guarantee the same register/instruction count as handwritten HLSL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Unconnected nodes normally do not become runtime work
&lt;/h3&gt;

&lt;p&gt;Shader Graph 17.3 collects nodes upstream from active Master Stack blocks/output nodes. Unity's implementation is visible in &lt;a href="https://github.com/Unity-Technologies/Graphics/blob/6000.3/staging/Packages/com.unity.shadergraph/Editor/Generation/Processors/Generator.cs" rel="noopener noreferrer"&gt;Generator.cs&lt;/a&gt; and &lt;a href="https://github.com/Unity-Technologies/Graphics/blob/6000.3/staging/Packages/com.unity.shadergraph/Editor/Generation/Processors/GenerationUtils.cs" rel="noopener noreferrer"&gt;GenerationUtils.cs&lt;/a&gt;. A disconnected experiment therefore does not automatically run every frame.&lt;/p&gt;

&lt;p&gt;A connected Detail Map whose strength is &lt;code&gt;0&lt;/code&gt; at runtime is different: unless the compiler can prove the path unnecessary, the sample may remain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code is generated per pass and stage
&lt;/h3&gt;

&lt;p&gt;Targets/SubTargets define pass requirements. A simple-looking material may still participate in Forward, Depth, ShadowCaster, DepthNormals, MotionVectors, and other passes. Vertex deformation can therefore be repeated in shadow/depth passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Interpolators move suitable work to the vertex stage
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Custom-Interpolators.html" rel="noopener noreferrer"&gt;Custom Interpolators&lt;/a&gt; can pass vertex-calculated values to the fragment stage. A candidate must be vertex-valid, interpolation-safe, within the varying budget, and supported on every target. Shader Graph exposes up to &lt;strong&gt;32 channels, each float4-equivalent&lt;/strong&gt;, while real hardware/API limits can be lower. Check whether the value replaces an existing varying or merely adds another one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Precision is controllable
&lt;/h3&gt;

&lt;p&gt;Shader Graph supports Single and Half precision: &lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Precision-Modes.html" rel="noopener noreferrer"&gt;Precision Modes&lt;/a&gt;. Start with Single for large ranges, accumulated time, and precision-sensitive UV math; test Half for normals, colors, directions, and limited-range masks. FP16 behavior varies by GPU/backend, so verify final code and image quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch node, static variants, and Dynamic Branch are different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Branch node: both inputs are evaluated
&lt;/h3&gt;

&lt;p&gt;Unity's Branch node is effectively generated as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hlsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Unity_Branch_float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Predicate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Predicate&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;True&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;False&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 &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;False&lt;/code&gt; inputs are computed before the helper selects one. If both sides contain expensive samples or math, both sides remain work. See &lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Branch-Node.html" rel="noopener noreferrer"&gt;Branch node&lt;/a&gt;. It is useful for selecting values, not reliably disabling an expensive feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shader Feature / Multi Compile: separate variants
&lt;/h3&gt;

&lt;p&gt;Static keywords generate separate variants. Inside a compiled variant, an inactive path can be constant-folded or removed. Keep this separate from &lt;strong&gt;build stripping&lt;/strong&gt;, which decides which variants survive into the Player.&lt;/p&gt;

&lt;p&gt;Shader Feature generally makes unused combinations easier to strip; Multi Compile retains combinations more aggressively. See &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/shader-conditionals-choose-a-type.html" rel="noopener noreferrer"&gt;Unity's branching guidance&lt;/a&gt; and the Shader Graph &lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Keywords-reference.html" rel="noopener noreferrer"&gt;keyword reference&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Branch: one runtime program
&lt;/h3&gt;

&lt;p&gt;A Shader Graph keyword defined as &lt;strong&gt;Dynamic Branch&lt;/strong&gt; produces one runtime program with a uniform branch; Unity sends its state as a uniform integer for the draw: &lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Keywords-concepts.html" rel="noopener noreferrer"&gt;Keyword concepts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the keyword itself is not normally a per-pixel predicate. Classic wave/warp divergence is more relevant to a &lt;strong&gt;varying branch in custom HLSL&lt;/strong&gt;, for example one driven by texture data. For Dynamic Branch keywords, measure branch overhead, both paths living in one program, register pressure, instruction-cache pressure, and path asymmetry.&lt;/p&gt;

&lt;p&gt;Do not choose Dynamic Branch only because a value changes often. Static variants can also be selected at runtime if the required variants survived stripping. Compare variant count, switching granularity, path cost, warm-up, memory, and GPU time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shader Graph still does not optimize for you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate work:&lt;/strong&gt; repeated Sample Texture nodes are not guaranteed to collapse into one fetch. Reuse sampled results where possible; Sub Graphs are not memoization caches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expensive algorithms:&lt;/strong&gt; triplanar mapping, POM, procedural noise, Scene Color/Depth, transcendental math, extra lights, and shadows remain expensive when their underlying work is expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overdraw:&lt;/strong&gt; transparency, full-screen effects, particles, foliage, and decals can remain fill-rate problems. Alpha Clip has different behavior from blending but still interacts with discard, early depth, MSAA, and dense overlap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline features:&lt;/strong&gt; shadows, fog, decals, SSAO, motion vectors, and pipeline textures are not automatically "Shader Graph overhead." A handwritten comparison is unfair if it simply removes those features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variant explosion:&lt;/strong&gt; ten independent two-way keywords already imply 1024 theoretical combinations before passes and pipeline keywords. This can hurt compile time, build size, loading, memory, and warm-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think in three budgets: &lt;strong&gt;GPU execution&lt;/strong&gt;, &lt;strong&gt;CPU/render submission&lt;/strong&gt;, and &lt;strong&gt;build/load/variant cost&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Custom Function or handwritten HLSL is worth it
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Start with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same passes/stages/samples/precision and already within budget&lt;/td&gt;
&lt;td&gt;Keep Shader Graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A local formula, loop, or special sample is awkward in nodes&lt;/td&gt;
&lt;td&gt;Custom Function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom passes, unusual buffers, stencil/render-state control dominate&lt;/td&gt;
&lt;td&gt;Handwritten ShaderLab/HLSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The real issue is overdraw, pass count, lighting, or coverage&lt;/td&gt;
&lt;td&gt;Fix rendering design first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Custom Function is useful when most of the graph is productive but one algorithm needs explicit HLSL. Check the official &lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Custom-Function-Node.html" rel="noopener noreferrer"&gt;Custom Function Node&lt;/a&gt; rules for &lt;code&gt;$precision&lt;/code&gt;, &lt;code&gt;_half&lt;/code&gt;/&lt;code&gt;_float&lt;/code&gt; suffixes, include guards, texture wrapper types, and Shader Model/API/Target restrictions.&lt;/p&gt;

&lt;p&gt;Moving the same expression into a Custom Function does &lt;strong&gt;not&lt;/strong&gt; automatically make it faster. The gain comes when handwritten code enables a real algorithm or execution-structure improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical optimization workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Count texture samples before nodes.&lt;/strong&gt; A graph with many arithmetic nodes can be cheap; dependent texture reads can dominate. Sample once and reuse where possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate affine from nonlinear vertex work.&lt;/strong&gt; &lt;code&gt;uv * tiling + offset&lt;/code&gt;, including uniform scrolling, is affine and can often move across interpolation without changing the result except for finite precision. &lt;code&gt;sin&lt;/code&gt;, noise, normalization, and distance-based distortion are nonlinear; compare sparse meshes and the lowest LOD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose precision from numeric range.&lt;/strong&gt; Use Single for large ranges/accumulation; test Half for normals, colors, directions, and masks. Verify long runtimes and low-end targets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose branching for its trade-off.&lt;/strong&gt; Branch node = both inputs; Dynamic Branch = one uniform runtime program; Shader Feature/Multi Compile = variants; varying custom-HLSL branch = potential lane divergence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect every pass.&lt;/strong&gt; Disable unnecessary transparency, Alpha Clip, Two Sided, Receive Shadows, and related features. Remember that vertex work can repeat in ShadowCaster/depth passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid giant keyword-driven uber shaders&lt;/strong&gt; when features barely overlap. Separate graphs or quality-specific graphs may be simpler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep CPU and GPU shader optimization separate.&lt;/strong&gt; The &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/SRPBatcher.html" rel="noopener noreferrer"&gt;SRP Batcher&lt;/a&gt; improves CPU setup/state changes; it does not merge draw calls like GPU Instancing, nor does it directly remove fragment instructions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to compare Shader Graph and HLSL fairly
&lt;/h2&gt;

&lt;p&gt;Keep the rendering specification identical: pipeline, renderer, surface mode, passes, lights, textures/samplers, precision, keywords, mesh, camera, resolution, Render Scale, MSAA, and quality settings. If the handwritten shader drops a feature or pass, you are measuring a specification change.&lt;/p&gt;

&lt;p&gt;Use a Development Build for Unity Profiler and diagnostic logs. For final A/B decisions, use a &lt;strong&gt;product-like measurement build&lt;/strong&gt; matching the shipping Graphics API, Render Pipeline Asset, quality, resolution, IL2CPP configuration, and shader stripping, with only the hooks needed by your GPU tool.&lt;/p&gt;

&lt;p&gt;Unity 6.3's GPU Usage Profiler is unavailable or restricted on several configurations. Check the &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/ProfilerGPU.html" rel="noopener noreferrer"&gt;GPU Usage Profiler documentation&lt;/a&gt; and use RenderDoc, PIX, Xcode GPU tools, Android GPU Inspector, or platform-specific timing when appropriate. Frame Debugger is for draw/pass inspection, not GPU timing.&lt;/p&gt;

&lt;p&gt;Warm caches. Control VSync, Dynamic Resolution, frame caps, power state, background work, and device temperature. Measure A→B and B→A; record sample count, median, p95, and thermal state. If the difference is below the noise floor, treat it as no meaningful difference. Amplified tests are useful for diagnosis, but make the final decision in the real scene. Measure &lt;strong&gt;GPU milliseconds&lt;/strong&gt;, not only FPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect generated code and variant logs
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;View Generated Shader&lt;/code&gt; / &lt;code&gt;See Generated Code&lt;/code&gt; lets you check passes, stages, samples, keywords, precision, and varyings.&lt;/p&gt;

&lt;p&gt;For per-shader build information, search &lt;code&gt;Editor.log&lt;/code&gt; for &lt;code&gt;Compiling shader&lt;/code&gt;; Unity reports variant counts before and after stripping: &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/shader-how-many-variants.html" rel="noopener noreferrer"&gt;shader variant counts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;URP aggregate stripping information in Unity 6.3&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Edit &amp;gt; Project Settings &amp;gt; Graphics&lt;/strong&gt; and select the &lt;strong&gt;URP&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Additional Shader Stripping Settings&lt;/strong&gt;, set &lt;strong&gt;Shader Variant Log Level&lt;/strong&gt; to anything except &lt;code&gt;Disabled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Development Build&lt;/strong&gt; in &lt;strong&gt;File &amp;gt; Build Profiles&lt;/strong&gt; and build.&lt;/li&gt;
&lt;li&gt;Inspect the &lt;strong&gt;Shader Stripping&lt;/strong&gt; section in Console or &lt;code&gt;Editor.log&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With &lt;strong&gt;Export Shader Variants&lt;/strong&gt; enabled, Unity documents &lt;code&gt;Temp/graphics-settings-stripping.json&lt;/code&gt; and &lt;code&gt;Temp/shader-stripping.json&lt;/code&gt;: &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/urp/shader-stripping-check.html" rel="noopener noreferrer"&gt;URP shader stripping&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two tiny generated-code tests
&lt;/h2&gt;

&lt;p&gt;Use a fixed baseline such as &lt;strong&gt;Unity 6.3 LTS, Shader Graph 17.3, URP Unlit, Opaque, Alpha Clip off, Single precision&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disconnected sample
&lt;/h3&gt;

&lt;p&gt;Graph A samples one texture into Base Color. Graph B adds a second disconnected Sample Texture node. Search generated code for the second texture/sample reference. If it is absent, you have directly verified dependency-driven generation for that case. This does not prove that connected equivalent expressions are always merged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch versus keyword
&lt;/h3&gt;

&lt;p&gt;Create a heavy Detail path with an extra texture sample. Compare a Branch node, a Boolean Dynamic Branch keyword, and the same keyword as Shader Feature. Inspect branch/&lt;code&gt;#pragma&lt;/code&gt; structure and build variant counts; verify whether the static OFF variant removes the Detail sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical examples
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scrolling water UVs:&lt;/strong&gt; pure &lt;code&gt;uv * scale + offset&lt;/code&gt; is affine and can be a good vertex-stage candidate. If you also move noise, &lt;code&gt;sin&lt;/code&gt;, normalization, or distance-dependent distortion, compare the lowest LOD because those operations are nonlinear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detail Map on/off:&lt;/strong&gt; Branch can still evaluate Detail upstream; Dynamic Branch keeps one runtime program; Shader Feature can remove the OFF path but needs variant management; Multi Compile retains combinations more aggressively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-screen procedural noise:&lt;/strong&gt; multiple noise octaves plus Scene Color distortion are expensive in both Shader Graph and HLSL. Try lower resolution, fewer octaves, smaller coverage, precomputed textures, LUTs, or approximations before rewriting. Handwritten HLSL matters when it enables real algorithmic changes such as early exits or specialized loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build the same rendering specification in Shader Graph.&lt;/li&gt;
&lt;li&gt;Inspect passes, stages, texture samples, branches, varyings, and variants.&lt;/li&gt;
&lt;li&gt;Measure GPU time, image quality, loading, and stutter on the target device.&lt;/li&gt;
&lt;li&gt;Fix sample reuse, stage placement, coverage, passes, and keyword design first.&lt;/li&gt;
&lt;li&gt;Move only the remaining bottleneck to Custom Function or handwritten HLSL when the measured gain exceeds maintenance and portability cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Unity 6.3 / Shader Graph 17.3, the useful shift from the old mindset is: &lt;strong&gt;Shader Graph can be the starting implementation; generated code and target-device profiling define the boundary where handwritten shader code becomes justified.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Product-like build: GPU time and image quality checked.&lt;/li&gt;
&lt;li&gt;[ ] A/B tests use identical rendering specifications and conditions.&lt;/li&gt;
&lt;li&gt;[ ] Caches warmed; median, p95, sample count, and thermal state recorded.&lt;/li&gt;
&lt;li&gt;[ ] Generated code checked for samples, stages, branches, varyings, and passes.&lt;/li&gt;
&lt;li&gt;[ ] Dynamic Branch keywords are not confused with varying per-pixel branches.&lt;/li&gt;
&lt;li&gt;[ ] Static variant counts, stripping, and required runtime combinations verified.&lt;/li&gt;
&lt;li&gt;[ ] Custom Interpolators checked for replacing versus adding varyings.&lt;/li&gt;
&lt;li&gt;[ ] Half/Single verified on the target backend/GPU and against image quality.&lt;/li&gt;
&lt;li&gt;[ ] Overdraw, Alpha Clip, lighting, and pipeline costs separated from authoring-format cost.&lt;/li&gt;
&lt;li&gt;[ ] Any HLSL rewrite produces a repeatable gain larger than noise and maintenance cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.shadergraph.html" rel="noopener noreferrer"&gt;Unity 6.3: Shader Graph package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Branch-Node.html" rel="noopener noreferrer"&gt;Shader Graph 17.3: Branch node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Keywords-concepts.html" rel="noopener noreferrer"&gt;Shader Graph 17.3: Keyword concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/shader-conditionals-choose-a-type.html" rel="noopener noreferrer"&gt;Unity 6.3: Shader branching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Custom-Interpolators.html" rel="noopener noreferrer"&gt;Shader Graph 17.3: Custom Interpolators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.shadergraph@17.3/manual/Custom-Function-Node.html" rel="noopener noreferrer"&gt;Shader Graph 17.3: Custom Function Node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/ProfilerGPU.html" rel="noopener noreferrer"&gt;Unity 6.3: GPU Usage Profiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/urp/shader-stripping-check.html" rel="noopener noreferrer"&gt;Unity 6.3: URP shader stripping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/SRPBatcher.html" rel="noopener noreferrer"&gt;Unity 6.3: SRP Batcher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>shadergraph</category>
      <category>graphics</category>
    </item>
    <item>
      <title>Choosing the Right Real-Time Networking Stack for Unity in 2026</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:54:03 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/choosing-the-right-real-time-networking-stack-for-unity-in-2026-29f4</link>
      <guid>https://dev.to/gamedevtoollab/choosing-the-right-real-time-networking-stack-for-unity-in-2026-29f4</guid>
      <description>&lt;p&gt;When building an online game in Unity, the question is often framed as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should I use Photon, Netcode for GameObjects, FishNet, or Mirror?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question is too small.&lt;/p&gt;

&lt;p&gt;In 2026, there is still no single networking product that is optimal for every Unity game. The real decision is a &lt;strong&gt;stack&lt;/strong&gt;: transport, netcode, authority model, session management, server hosting, and backend services.&lt;/p&gt;

&lt;p&gt;For a GameObject-based action game that needs client prediction, &lt;strong&gt;Photon Fusion 2.1 is a strong first PoC baseline&lt;/strong&gt;. If your priorities are Unity Gaming Services, DOTS/ECS, self-hosting, source access, or deterministic simulation, the starting point changes.&lt;/p&gt;

&lt;p&gt;This article explains how to make that decision in production terms: latency, cheating, reconnection, hosting, bandwidth, operations, and total cost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article uses official documentation checked on &lt;strong&gt;August 31, 2026&lt;/strong&gt; as its factual baseline. SDK versions, pricing, licensing, and service availability can change, so re-check them before committing a production project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I mean by a real-time multiplayer game
&lt;/h2&gt;

&lt;p&gt;The target is roughly this class of game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2–32 players in the same session&lt;/li&gt;
&lt;li&gt;continuously synchronized players, enemies, projectiles, or interactable objects&lt;/li&gt;
&lt;li&gt;input latency that directly affects game feel&lt;/li&gt;
&lt;li&gt;reconnects, host loss, and late joining that must be handled&lt;/li&gt;
&lt;li&gt;co-op action, FPS/TPS, racing, or competitive action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only need turn-based play, leaderboards, chat, friends, or asynchronous PvP, you may not need a sophisticated state-synchronization netcode at all. A backend such as Nakama, PlayFab, or Unity Gaming Services may be the more important part of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not treat “networking” as one product
&lt;/h2&gt;

&lt;p&gt;A production multiplayer stack has at least five layers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Packet delivery, reliability, connection path, secure channel integration&lt;/td&gt;
&lt;td&gt;Unity Transport, Photon transport, UDP/WebSocket-based transports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netcode&lt;/td&gt;
&lt;td&gt;Replication, RPCs, input, prediction, interpolation, rollback&lt;/td&gt;
&lt;td&gt;Fusion, NGO, Netcode for Entities, FishNet, Mirror&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session&lt;/td&gt;
&lt;td&gt;Lobby, matchmaking, relay, connection metadata&lt;/td&gt;
&lt;td&gt;Photon Realtime, MPS SDK, UGS Lobby/Relay/Matchmaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Dedicated-server allocation, startup, scaling, monitoring&lt;/td&gt;
&lt;td&gt;GameLift, PlayFab MPS, Edgegap, Hathora, custom infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Authentication, persistence, inventory, ranking, result settlement&lt;/td&gt;
&lt;td&gt;PlayFab, Nakama, UGS, custom APIs and databases&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is why “WebSocket or Photon?” is not a useful comparison. WebSocket is an application-layer protocol often used as a transport path in a game stack; Fusion is a netcode with prediction and simulation semantics.&lt;/p&gt;

&lt;p&gt;A transport can move packets. It does not automatically give you prediction, snapshot interpolation, authority rules, lag compensation, reconnect restoration, or late-join state reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven criteria that matter more than brand names
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;What to decide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prediction&lt;/td&gt;
&lt;td&gt;Can local input run ahead and be reconciled against authoritative state? How much is built in?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authority&lt;/td&gt;
&lt;td&gt;Who is allowed to decide movement, hits, rewards, inventory, and match results?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Topology&lt;/td&gt;
&lt;td&gt;Host, client-server, or distributed/shared authority?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication scale&lt;/td&gt;
&lt;td&gt;Tick rate, visible objects, predicted physics, update frequency, interest management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;Mobile suspend/resume, WebGL constraints, console certification and invite flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team architecture&lt;/td&gt;
&lt;td&gt;GameObject or ECS? Do not migrate architecture just because the networking SDK prefers it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total cost&lt;/td&gt;
&lt;td&gt;CCU, bandwidth, hosting, observability, operations, migration and maintenance work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few terms used below: &lt;strong&gt;CCU&lt;/strong&gt; is concurrent users, &lt;strong&gt;RTT&lt;/strong&gt; is round-trip latency, &lt;strong&gt;SLO&lt;/strong&gt; is a service-level objective, &lt;strong&gt;egress&lt;/strong&gt; is outbound cloud traffic, and &lt;strong&gt;p95/p99&lt;/strong&gt; are percentile measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the synchronization model before the SDK
&lt;/h2&gt;

&lt;p&gt;Most real-time games are built from three core synchronization models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snapshot replication
&lt;/h3&gt;

&lt;p&gt;The authority sends periodic state snapshots, and remote clients interpolate between them. This is a natural fit for remote players, NPCs, doors, and many world objects.&lt;/p&gt;

&lt;p&gt;The trade-off is bandwidth: higher update frequency improves responsiveness but increases traffic. Quantization, change detection, and interest management matter quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input prediction and resimulation
&lt;/h3&gt;

&lt;p&gt;The local player applies input immediately, sends that input to the authority, then reconciles when authoritative state arrives. If needed, the client rewinds to the authoritative tick and resimulates to the present.&lt;/p&gt;

&lt;p&gt;This is a strong fit for FPS, TPS, racing, and action games, but it requires simulation code that can be run again safely. External I/O, analytics, rewards, and one-shot side effects must not be mixed into replayable tick logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deterministic lockstep with rollback
&lt;/h3&gt;

&lt;p&gt;Peers advance from the same input stream and rollback when delayed input arrives. This can be excellent for fighting games, RTS, sports, and other deterministic simulations, but it usually requires stricter simulation constraints than ordinary MonoBehaviour/PhysX gameplay.&lt;/p&gt;

&lt;p&gt;Real games are hybrids. Your local player may be predicted, remote players interpolated, cosmetic effects local-only, and economic rewards finalized by a backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  My 2026 decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Main strength&lt;/th&gt;
&lt;th&gt;Main caution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photon Fusion&lt;/td&gt;
&lt;td&gt;GameObject action, FPS/TPS, co-op&lt;/td&gt;
&lt;td&gt;Prediction/resimulation, lag compensation, multiple topologies&lt;/td&gt;
&lt;td&gt;Photon dependency, CCU/bandwidth cost, dedicated hosting is separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NGO + MPS SDK&lt;/td&gt;
&lt;td&gt;Unity 6, UGS-heavy co-op&lt;/td&gt;
&lt;td&gt;Unity-first integration, Sessions/Relay/Lobby ecosystem&lt;/td&gt;
&lt;td&gt;MPS Sessions requires Unity 6; advanced action prediction is not a turnkey system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netcode for Entities&lt;/td&gt;
&lt;td&gt;DOTS/ECS, many entities, dedicated servers&lt;/td&gt;
&lt;td&gt;Ghosts, prediction, lag compensation&lt;/td&gt;
&lt;td&gt;ECS commitment, learning curve, resimulation cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FishNet&lt;/td&gt;
&lt;td&gt;GameObject, source access, self-hosting&lt;/td&gt;
&lt;td&gt;Prediction, server authority, transport flexibility&lt;/td&gt;
&lt;td&gt;Custom license; surrounding infrastructure and QA remain your responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mirror&lt;/td&gt;
&lt;td&gt;Existing codebases, MIT requirement, self-hosting&lt;/td&gt;
&lt;td&gt;Snapshot interpolation, lag compensation, PredictedRigidbody&lt;/td&gt;
&lt;td&gt;General prediction is still listed as Researching; PoC the exact physics/use case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photon Quantum&lt;/td&gt;
&lt;td&gt;Deterministic competitive games&lt;/td&gt;
&lt;td&gt;Deterministic ECS and rollback&lt;/td&gt;
&lt;td&gt;Separate simulation model from ordinary Unity physics/MonoBehaviours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nakama&lt;/td&gt;
&lt;td&gt;Backend-driven multiplayer&lt;/td&gt;
&lt;td&gt;Auth, storage, matchmaking, chat, authoritative match loop&lt;/td&gt;
&lt;td&gt;Not a drop-in 3D action-state netcode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Versions I used as the baseline
&lt;/h3&gt;

&lt;p&gt;These are the specific versions/editor combinations checked for this article.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product / stack&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Editor / delivery note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photon Fusion&lt;/td&gt;
&lt;td&gt;2.1.2 Stable&lt;/td&gt;
&lt;td&gt;Unity 2021.3.45, 2022.3.45, 6.0.x, 6.3.x; console access has separate requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netcode for GameObjects&lt;/td&gt;
&lt;td&gt;2.13.2&lt;/td&gt;
&lt;td&gt;Unity 6000.3; package manifest minimum is 6000.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiplayer Services SDK&lt;/td&gt;
&lt;td&gt;2.2.1&lt;/td&gt;
&lt;td&gt;Unity 6.0 LTS+; &lt;code&gt;com.unity.services.multiplayer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netcode for Entities&lt;/td&gt;
&lt;td&gt;1.14.2&lt;/td&gt;
&lt;td&gt;Unity 6000.3 via UPM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netcode for Entities core&lt;/td&gt;
&lt;td&gt;6.5 / 6.6&lt;/td&gt;
&lt;td&gt;Integrated with Unity 6000.5 / 6000.6 editor lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FishNet&lt;/td&gt;
&lt;td&gt;4.7.2R&lt;/td&gt;
&lt;td&gt;Verify editor compatibility per release; custom source-available license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mirror&lt;/td&gt;
&lt;td&gt;v96.11.2&lt;/td&gt;
&lt;td&gt;MIT; evaluate prediction maturity per feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photon Quantum&lt;/td&gt;
&lt;td&gt;3.0.13 Stable&lt;/td&gt;
&lt;td&gt;Unity 2021.3 LTS+; deterministic simulation environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nakama&lt;/td&gt;
&lt;td&gt;Server v3.40.0 / Unity Client 3.21.1&lt;/td&gt;
&lt;td&gt;Backend, not a transform-prediction netcode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For NGO and Netcode for Entities, do not confuse the broad product family with a specific package/editor combination. For example, NGO 2.13.2 is not something I would assume can simply be dropped into a Unity 2021/2022 project; older editors have their own package lines. Likewise, Netcode for Entities 6.5+ is tied to the corresponding Unity 6 editor line as a core package.&lt;/p&gt;

&lt;p&gt;Primary references: &lt;a href="https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/changelog/CHANGELOG.html" rel="noopener noreferrer"&gt;NGO 2.13 changelog&lt;/a&gt;, &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.netcode.gameobjects.html" rel="noopener noreferrer"&gt;Unity 6000.3 NGO&lt;/a&gt;, &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/com.unity.netcode.html" rel="noopener noreferrer"&gt;Unity 6000.3 Netcode for Entities&lt;/a&gt;, and the &lt;a href="https://unity.com/releases/editor/beta/6000.6.0b9" rel="noopener noreferrer"&gt;Unity 6000.6.0b9 release page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Fusion is a sensible first benchmark
&lt;/h2&gt;

&lt;p&gt;I would start a GameObject action-game PoC with Fusion when all of these are acceptable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;movement, shooting, or physics needs client prediction;&lt;/li&gt;
&lt;li&gt;Photon Cloud dependency and its pricing model are acceptable;&lt;/li&gt;
&lt;li&gt;Host or client-server topology matches the product;&lt;/li&gt;
&lt;li&gt;full source redistribution or unrestricted middleware reuse is not a hard requirement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a &lt;strong&gt;comparison baseline, not an automatic production choice&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fusion Host Mode is attractive for small PvE games because it can avoid dedicated-server compute cost. Server Mode is a better fit when match integrity matters. Shared Mode can reduce some topology friction for casual/mobile/WebGL use cases, but its authority model must not be treated as equivalent to a trusted dedicated server.&lt;/p&gt;

&lt;p&gt;Photon provides Fusion Server Mode and the connection/session runtime. It does &lt;strong&gt;not&lt;/strong&gt; automatically provide the infrastructure that builds, allocates, starts, scales, and monitors your Unity headless server fleet. That is a hosting/orchestration problem. See Photon’s &lt;a href="https://doc.photonengine.com/fusion/v2/concepts-and-patterns/dedicated-server-overview" rel="noopener noreferrer"&gt;Dedicated Server overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Console development also has separate access requirements. Photon’s &lt;a href="https://doc.photonengine.com/fusion/v2/consoles/overview" rel="noopener noreferrer"&gt;Console Development Overview&lt;/a&gt; describes certification checks, platform-specific documentation, and native socket libraries. Authentication, invites, suspend/resume, cross-play, and platform certification still need device-level PoCs.&lt;/p&gt;

&lt;h2&gt;
  
  
  NGO + MPS SDK: best when Unity 6 and UGS are deliberate choices
&lt;/h2&gt;

&lt;p&gt;Netcode for GameObjects is Unity’s GameObject/MonoBehaviour-oriented netcode. My current baseline is &lt;strong&gt;Unity 6000.3 + NGO 2.13.2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.unity.com/en-us/mps-sdk/sessions" rel="noopener noreferrer"&gt;Multiplayer Services SDK Sessions&lt;/a&gt; requires Unity 6.0 LTS or newer. Sessions can automate host election, player join/leave handling, and network connection establishment. That does not mean it designs your gameplay state, authority model, session lifetime, migration payloads, or failure UX.&lt;/p&gt;

&lt;p&gt;For existing Unity 2021/2022 projects, treat “stay on the editor-compatible NGO package” and “migrate to Unity 6 + current NGO + MPS Sessions” as separate estimates.&lt;/p&gt;

&lt;p&gt;NGO can be a very good fit for small co-op games where UGS Authentication, Lobby, Relay, and Matchmaker are already part of the product. For highly latency-sensitive competitive action, I would PoC the actual movement and combat loop under 100–200 ms rather than assume anticipation features are equivalent to a complete rollback/resimulation architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Netcode for Entities: use it when ECS is already the product architecture
&lt;/h2&gt;

&lt;p&gt;Netcode for Entities provides Ghost snapshots, command streams, client prediction, rollback/resimulation, interpolation, and lag compensation in an ECS-oriented execution model.&lt;/p&gt;

&lt;p&gt;Its strength is not “more players by magic.” You still need to control which ghosts are predicted, how often they update, and which clients receive them. Sending every entity every tick to every client will fail regardless of the framework.&lt;/p&gt;

&lt;p&gt;I would choose it when DOTS/ECS and dedicated servers are already strategic decisions: large numbers of players, NPCs, projectiles, or units; data-oriented simulation; and a team that can profile prediction loops and server tick budgets.&lt;/p&gt;

&lt;p&gt;I would not move a mature MonoBehaviour/Animator/PhysX/NavMesh project to ECS solely because the networking package looks attractive.&lt;/p&gt;

&lt;h2&gt;
  
  
  FishNet: strong for self-hosted GameObject stacks, but read the license correctly
&lt;/h2&gt;

&lt;p&gt;FishNet offers server authority, prediction/reconciliation, NetworkTransform, observer systems, and transport choice. It can work well when you want to choose your own hosting and surrounding services.&lt;/p&gt;

&lt;p&gt;However, do not describe it simply as “open source.” The current &lt;a href="https://raw.githubusercontent.com/FirstGearGames/FishNet/main/LICENSE.md" rel="noopener noreferrer"&gt;FishNet license&lt;/a&gt; permits game/content use and modification, while section 2.b restricts other networking-solution products from using, reverse-engineering, or implementing the FishNet software. Section 2.d explicitly says that exclusion does not apply to games developed with the software.&lt;/p&gt;

&lt;p&gt;So separate normal game development from middleware/product reuse. If your plan includes forking, redistribution, an internal shared networking platform, a competing networking product, or FishNet-Pro distribution, get legal review of the current terms.&lt;/p&gt;

&lt;p&gt;Self-hosting also means you own more of relay/NAT strategy, allocation, monitoring, DDoS considerations, upgrades, and regression testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mirror: evaluate the current features, not the old reputation
&lt;/h2&gt;

&lt;p&gt;Mirror remains relevant when you value an MIT license, existing assets, and self-hosting.&lt;/p&gt;

&lt;p&gt;It has &lt;a href="https://mirror-networking.gitbook.io/docs/manual/components/network-transform/snapshot-interpolation" rel="noopener noreferrer"&gt;Snapshot Interpolation&lt;/a&gt;, &lt;a href="https://mirror-networking.gitbook.io/docs/manual/general/lag-compensation" rel="noopener noreferrer"&gt;Lag Compensation&lt;/a&gt;, and &lt;a href="https://mirror-networking.gitbook.io/docs/manual/general/client-side-prediction" rel="noopener noreferrer"&gt;PredictedRigidbody&lt;/a&gt;. But the official project status still distinguishes feature maturity: snapshot interpolation is Stable, lag compensation is Beta, and general Prediction is Researching.&lt;/p&gt;

&lt;p&gt;That does not make Mirror unsuitable for action games. It means the PoC must use the exact Unity version, transport, physics objects, character controller, abilities, knockback, and reconciliation requirements you expect to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quantum: deterministic does not mean “automatically cheat-proof”
&lt;/h2&gt;

&lt;p&gt;Photon Quantum uses deterministic ECS and rollback. That can be a very good fit for fighting games, RTS, sports, and other simulations where replayability from input history is valuable.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;determinism is not the same as dedicated-server authority&lt;/strong&gt;. Hidden information, fog of war, input validity, ranking, and reward settlement are separate security problems. Use the official &lt;a href="https://doc.photonengine.com/quantum/current/manual/cheat-protection" rel="noopener noreferrer"&gt;Quantum Cheat Protection&lt;/a&gt; guidance to decide what is validated, what checksums can detect, and what must be verified or persisted elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nakama: backend first, not a replacement for action netcode
&lt;/h2&gt;

&lt;p&gt;Nakama provides authentication, storage, matchmaking, chat, leaderboards, realtime sockets, and authoritative match handlers. It can fully own many card, board, or lower-frequency competitive games.&lt;/p&gt;

&lt;p&gt;It is not, by itself, a drop-in replacement for a 3D action framework that predicts CharacterControllers, Rigidbodies, projectiles, and hitboxes. For a 60 Hz action game, pair it with a netcode or build a game-specific protocol deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unity Multiplay changed: separate matchmaking from hosting
&lt;/h2&gt;

&lt;p&gt;Unity’s directly provided Multiplay Game Server Hosting is marked deprecated as of April 1, 2026 in the &lt;a href="https://status.unity.com/info_notices/362941" rel="noopener noreferrer"&gt;Unity status notice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean Matchmaker or the Multiplayer Services SDK disappeared. Unity’s &lt;a href="https://docs.unity.com/en-us/mps-sdk/game-server-hosting-support" rel="noopener noreferrer"&gt;game server hosting support&lt;/a&gt; documents an architecture where Cloud Code integrates with an external game-server hosting provider for allocation, session creation, and backfill.&lt;/p&gt;

&lt;p&gt;A new design should therefore keep these responsibilities separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
   ↓
Matchmaker
   ↓
Cloud Code / allocation logic
   ↓
External game-server hosting
   ↓
MPS Session
   ↓
Netcode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same principle applies to Fusion: netcode is not hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Host Mode: the backend cannot magically make host-reported gameplay trustworthy
&lt;/h2&gt;

&lt;p&gt;For small co-op games, a common design is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Player-hosted match
   ↓
Backend
   ├─ authentication
   ├─ server-issued mission/reward rules
   ├─ eligibility and limit validation
   └─ capped provisional reward commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be useful, but the participant acting as host owns State Authority and can modify the host process. Sending results to a backend does not prove that reported kills, drops, or clears actually happened.&lt;/p&gt;

&lt;p&gt;Server-issued mission tokens, time windows, reward tables, and daily caps can verify &lt;strong&gt;eligibility&lt;/strong&gt; and limit the maximum loss from cheating. They do not prove host-reported gameplay facts inside those limits.&lt;/p&gt;

&lt;p&gt;If high-value rewards, ranking, or paid currency require stronger trust, move those facts behind a trusted authority: for example dedicated-server telemetry or another verifiable event source outside the player host’s trust boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send verifiable input or commands, not authoritative results
&lt;/h2&gt;

&lt;p&gt;A client should not be trusted when it says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“my position is X”&lt;/li&gt;
&lt;li&gt;“I dealt 100 damage”&lt;/li&gt;
&lt;li&gt;“this purchase succeeded”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an action game, send movement, aim, buttons, and fire requests. The authority derives speed, cooldowns, ammo, collisions, and damage.&lt;/p&gt;

&lt;p&gt;For a card or board game, raw device input is less useful. Send intent-level commands such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PlayCard(cardId, targetId)
UseSkill(skillId, targetId)
BuyItem(sku, quantity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The authority validates ownership, turn/phase, cost, target, inventory, cooldown, and sequence, then derives the result.&lt;/p&gt;

&lt;p&gt;The common principle is simple: &lt;strong&gt;the client should submit the minimum input/command that a trusted authority can validate and recompute.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep tick simulation replayable
&lt;/h2&gt;

&lt;p&gt;For client-server prediction, gameplay code that affects authoritative results belongs in network ticks, not only in ordinary &lt;code&gt;Update()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;conceptual Fusion Host/Server Mode example&lt;/strong&gt;, not copy-paste production code:&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;struct&lt;/span&gt; &lt;span class="nc"&gt;PlayerMoveInput&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INetworkInput&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;Vector2&lt;/span&gt; &lt;span class="n"&gt;Move&lt;/span&gt;&lt;span class="p"&gt;;&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkPlayer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NetworkBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;MaxMoveReuseTicks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Networked&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;Hp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;span class="n"&gt;Networked&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;NetworkBool&lt;/span&gt; &lt;span class="n"&gt;IsStunned&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;span class="n"&gt;Networked&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;LastAcceptedMove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;span class="n"&gt;Networked&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;byte&lt;/span&gt; &lt;span class="n"&gt;MissingMoveTicks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&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;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;FixedUpdateNetwork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;HasInputAuthority&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;HasStateAuthority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// State that advances without player input must still tick.&lt;/span&gt;
        &lt;span class="nf"&gt;SimulateInputIndependentState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeltaTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;GetInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;PlayerMoveInput&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClampMagnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;LastAcceptedMove&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;MissingMoveTicks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MissingMoveTicks&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MissingMoveTicks&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;MaxMoveReuseTicks&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;LastAcceptedMove&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Hp&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;IsStunned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;SimulateMovement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeltaTime&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;The important part is not the exact two-tick policy. It is that the policy is deterministic and explicit. Photon documents that &lt;code&gt;GetInput()&lt;/code&gt; can occasionally fail even on the State Authority during packet loss or latency spikes.&lt;/p&gt;

&lt;p&gt;Held input and edge input should not necessarily use the same missing-packet policy. Reusing a previous held movement state for a bounded number of ticks can be reasonable; inventing a new button-down edge from missing input is not.&lt;/p&gt;

&lt;p&gt;Also keep external side effects out of replayable simulation. Do not call databases, HTTP services, anti-cheat services, analytics, or reward settlement directly from resimulatable tick code.&lt;/p&gt;

&lt;p&gt;For economic or external effects, use a durable, retry-safe flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirmed authoritative domain event
   ↓
durable event log / transactional outbox
   ↓  at-least-once delivery
worker / dispatcher
   ↓
backend with persistent idempotency key
   ↓
persisted result / retryable query
   ↓
apply only if match/player/revision is still valid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An “outbox” stored only in game-server memory or disposable local disk is not crash-safe. If the match state and outbox cannot be committed atomically, you still have a dual-write problem. Either make the durable event the source of truth, use a transactional boundary, or define how the match is invalidated/compensated after unrecoverable failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design session lifetime and reconnection before polishing gameplay
&lt;/h2&gt;

&lt;p&gt;A connection API returning success is not a session design.&lt;/p&gt;

&lt;p&gt;Define the state transitions for create, join, ready, start, late join, temporary disconnect, reconnect, leave, result settlement, and destruction. Decide which record is authoritative when lobby, game server, and backend disagree.&lt;/p&gt;

&lt;p&gt;Do not use a transient connection ID as the persistent player identity. Reconnect using an authenticated user identity plus match identity, with an expiring token that includes build/version and permissions. Invalidate the old connection when the new one is accepted.&lt;/p&gt;

&lt;p&gt;Late joiners should receive scene/assets, current phase, and replicated state in a known order, then acknowledge readiness before spawning and accepting input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a 1–2 week PoC with at most two candidates
&lt;/h2&gt;

&lt;p&gt;Do not choose a multiplayer SDK from documentation alone.&lt;/p&gt;

&lt;p&gt;A useful first timebox is one or two engineers, one to two weeks, and no more than two candidates. Build the same minimum slice in each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real movement plus one important action such as dash/jump/dodge&lt;/li&gt;
&lt;li&gt;shooting or melee, damage, death&lt;/li&gt;
&lt;li&gt;one moving platform or pushable Rigidbody&lt;/li&gt;
&lt;li&gt;match start/end, late join, disconnect, reconnect&lt;/li&gt;
&lt;li&gt;headless build where relevant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the same gameplay and network conditions for every candidate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define pass/fail thresholds before testing
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Example acceptance rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reconciliation&lt;/td&gt;
&lt;td&gt;At 100 ms RTT and 3% loss, p95 correction distance stays below your game-specific limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server tick&lt;/td&gt;
&lt;td&gt;p99 tick time remains below 70% of the tick budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnect&lt;/td&gt;
&lt;td&gt;p95 reconnect completes within the product target and restores authoritative state correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bandwidth&lt;/td&gt;
&lt;td&gt;Average billable bytes/sec per connection stays inside the business target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allocation&lt;/td&gt;
&lt;td&gt;Dedicated-server allocation success and cold-start p95 meet the SLO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure handling&lt;/td&gt;
&lt;td&gt;Expired tokens, version mismatch, and region exhaustion fail explicitly and recover predictably&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result settlement&lt;/td&gt;
&lt;td&gt;Crashes and duplicate submissions do not create missing or double-applied match results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not copy arbitrary values like “0.2 m correction” from another game. Derive thresholds from movement speed, match length, reconnect rules, platform constraints, and economic risk.&lt;/p&gt;

&lt;p&gt;Network emulation should include not only RTT and random packet loss, but also jitter, burst loss, asymmetric uplink/downlink, packet reordering/duplication, temporary disconnects, host loss, backgrounding, different frame rates, and server tick stalls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost: estimate from average CCU and billable traffic, not DAU alone
&lt;/h2&gt;

&lt;p&gt;A rough average CCU estimate is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;average CCU
  ≈ DAU × sessions per day × average connected minutes / 1,440
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, 10,000 DAU × 1.5 sessions × 20 minutes is roughly 208 average CCU.&lt;/p&gt;

&lt;p&gt;Bandwidth is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly billable transfer
  ≈ average CCU × average billable bytes/sec per connection × seconds/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;strong&gt;peak CCU&lt;/strong&gt; for capacity and server-count planning, but average usage for transfer estimates.&lt;/p&gt;

&lt;p&gt;Then price each network leg separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client → relay and relay → host/server&lt;/li&gt;
&lt;li&gt;host/server → client&lt;/li&gt;
&lt;li&gt;game server → backend&lt;/li&gt;
&lt;li&gt;cross-region and redundancy traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same packet may be billable by multiple providers, while some hosting plans include certain bandwidth. Only remove a cost after verifying the provider’s pricing model. Relevant references include &lt;a href="https://unity.com/products/gaming-services/pricing" rel="noopener noreferrer"&gt;UGS pricing&lt;/a&gt;, &lt;a href="https://www.photonengine.com/fusion/pricing" rel="noopener noreferrer"&gt;Photon Fusion pricing&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/gamelift/servers/pricing/" rel="noopener noreferrer"&gt;Amazon GameLift Servers pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical recommendation
&lt;/h2&gt;

&lt;p&gt;In 2026, I would narrow a Unity real-time networking stack in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;decide GameObject vs ECS and the Unity/editor version you can actually ship;&lt;/li&gt;
&lt;li&gt;choose where authority lives: host, dedicated client-server, or distributed/shared;&lt;/li&gt;
&lt;li&gt;separate session, hosting, and backend responsibilities from netcode;&lt;/li&gt;
&lt;li&gt;validate platform access, licensing, CCU/bandwidth, and operational cost;&lt;/li&gt;
&lt;li&gt;compare the real movement/combat/reconnect slice under the same bad-network conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the game is GameObject-based, you accept Photon dependency and pricing, and you need Host or Client-Server prediction, &lt;strong&gt;Fusion 2.1 is a sensible benchmark candidate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If Unity 6 + UGS integration is the priority, start with NGO 2.13.2 + MPS SDK. If DOTS/ECS is already the architecture, start with Netcode for Entities. If source access and self-hosting matter, compare FishNet and Mirror carefully under their actual licensing and feature-maturity constraints. If determinism is a core requirement, evaluate Quantum. Use Nakama primarily as a backend or authoritative match platform rather than as a drop-in transform prediction layer.&lt;/p&gt;

&lt;p&gt;The key point is that &lt;strong&gt;“it connected” is not the success condition&lt;/strong&gt;. Ship only after you have measurable acceptance criteria for 100–200 ms network conditions, reconciliation, authority and cheating, reconnect/late join, server allocation, bandwidth, and crash-safe result settlement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary official references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion/v2/getting-started/sdk-download" rel="noopener noreferrer"&gt;Photon Fusion SDK Download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion/v2/fusion-choose" rel="noopener noreferrer"&gt;Photon Fusion topology guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion/v2/manual/input/player-input" rel="noopener noreferrer"&gt;Photon Fusion input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/fusion/v2/concepts-and-patterns/network-simulation-loop" rel="noopener noreferrer"&gt;Photon Fusion network simulation loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/changelog/CHANGELOG.html" rel="noopener noreferrer"&gt;Unity NGO 2.13 changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity.com/en-us/mps-sdk/sessions" rel="noopener noreferrer"&gt;Unity MPS SDK Sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://status.unity.com/info_notices/362941" rel="noopener noreferrer"&gt;Unity Multiplay deprecation notice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.netcode@1.14/changelog/CHANGELOG.html" rel="noopener noreferrer"&gt;Unity Netcode for Entities 1.14 changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/FirstGearGames/FishNet/main/LICENSE.md" rel="noopener noreferrer"&gt;FishNet license&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MirrorNetworking/Mirror" rel="noopener noreferrer"&gt;Mirror README / feature status&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/quantum/current/manual/cheat-protection" rel="noopener noreferrer"&gt;Photon Quantum cheat protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://heroiclabs.com/docs/nakama/concepts/multiplayer/authoritative/" rel="noopener noreferrer"&gt;Nakama authoritative multiplayer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>multiplayer</category>
      <category>networking</category>
    </item>
    <item>
      <title>Will HTTP/3 Make Unity Faster? Choosing HTTP for APIs, Asset Downloads, and Real-Time Networking</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Wed, 02 Sep 2026 06:19:56 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/will-http3-make-unity-faster-choosing-http-for-apis-asset-downloads-and-real-time-networking-4lgp</link>
      <guid>https://dev.to/gamedevtoollab/will-http3-make-unity-faster-choosing-http-for-apis-asset-downloads-and-real-time-networking-4lgp</guid>
      <description>&lt;p&gt;Unity networking can look simple: call &lt;code&gt;UnityWebRequest.Get()&lt;/code&gt;, wait, and parse. In production, protocol choice also affects concurrent APIs, Addressables, mobile packet loss, platform backends, CDNs, and real-time communication.&lt;/p&gt;

&lt;p&gt;The practical default is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use normal protocol negotiation and treat HTTP/2 as the baseline. Evaluate HTTP/3 only where real-device measurements justify its integration and maintenance cost.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTTP/3 is not automatically faster, and it does not determine action-game synchronization. This article separates APIs, asset delivery, and real-time networking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scope: Unity 6.3 through Unity 6.5. No benchmark numbers are claimed. Behavior depends on the Unity version, platform, package versions, server, and CDN. Compile against your actual package set and verify the negotiated protocol on real devices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Recommendations by workload
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Start with&lt;/th&gt;
&lt;th&gt;Consider HTTP/3 when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Login, inventory, billing, and master-data APIs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UnityWebRequest&lt;/code&gt; with normal negotiation; HTTP/2 where available&lt;/td&gt;
&lt;td&gt;Many independent calls run over lossy mobile links and measurements show a gain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple small or medium assets&lt;/td&gt;
&lt;td&gt;HTTP/2, CDN caching, sensible bundle layout, and bounded concurrency&lt;/td&gt;
&lt;td&gt;Packet loss stalls unrelated parallel downloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One large patch&lt;/td&gt;
&lt;td&gt;HTTP/2 plus Range requests, resume logic, and integrity checks&lt;/td&gt;
&lt;td&gt;Reconnects or network changes improve measurably with QUIC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background download&lt;/td&gt;
&lt;td&gt;Native OS download APIs through a plugin&lt;/td&gt;
&lt;td&gt;Background execution and reliable resume justify native integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time state, chat, or presence&lt;/td&gt;
&lt;td&gt;Photon, Unity Transport, WebSocket, or gRPC as appropriate&lt;/td&gt;
&lt;td&gt;Evaluate separately from HTTP REST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not hard-code one version everywhere. Normal negotiation allows HTTP/2 with HTTP/1.1 fallback for incompatible devices, proxies, or networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP/1.1, HTTP/2, and HTTP/3
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HTTP/1.1: compatible, but awkward for many parallel requests
&lt;/h3&gt;

&lt;p&gt;HTTP/1.1 runs over TCP and reuses connections with keep-alive, but cannot flexibly multiplex independent responses. Clients therefore open several connections to one host, which matters for many startup APIs or small bundles.&lt;/p&gt;

&lt;p&gt;It remains sufficient for a few calls, server-dominated workloads, or one stable large transfer. Its main value is compatibility and rollback.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP/2: the current baseline
&lt;/h3&gt;

&lt;p&gt;HTTP/2 also uses TCP, but multiplexes streams in one connection. HPACK reduces repeated headers, helping APIs with similar authorization and cookies. Unity benefits from fewer TCP connections and efficient multi-file CDN delivery.&lt;/p&gt;

&lt;p&gt;HTTP/2 still inherits TCP-level head-of-line blocking. If a TCP packet is lost, bytes after the gap wait for retransmission, so unrelated streams on that connection can pause.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP/3: QUIC reduces interference between streams
&lt;/h3&gt;

&lt;p&gt;HTTP/3 uses QUIC over UDP, integrates TLS 1.3, and provides independent streams. It can reduce cross-stream blocking, shorten setup, and better tolerate Wi-Fi-to-cellular transitions—especially on lossy mobile links.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.rfc-editor.org/rfc/rfc9114.html" rel="noopener noreferrer"&gt;HTTP/3 specification&lt;/a&gt; explains how independent QUIC streams avoid the TCP-level cross-stream blocking that can affect HTTP/2.&lt;/p&gt;

&lt;p&gt;Three caveats matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One huge file is not automatically faster.&lt;/strong&gt; It normally uses one stream, where loss still requires retransmission. The advantage is clearer when catalogs, bundles, and APIs run together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UDP/443 is not universal.&lt;/strong&gt; Networks may restrict it, so HTTP/2 or HTTP/1.1 fallback is mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0-RTT has replay implications.&lt;/strong&gt; Do not blindly resend purchases, currency spending, or loot-box draws. Use operation IDs, idempotency keys, deduplication, and status queries.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/3 over QUIC
    ↓ unavailable or unsuitable
HTTP/2 over TLS/TCP
    ↓ unavailable
HTTP/1.1 over TLS/TCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a conceptual relationship. An implementation may race candidates rather than wait for every failure serially.&lt;/p&gt;

&lt;h2&gt;
  
  
  What version does Unity actually use?
&lt;/h2&gt;

&lt;p&gt;Specifications are only half the answer. Unity's effective backend changes by editor version and platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity 6.3: HTTP/2 by default on supported platforms
&lt;/h3&gt;

&lt;p&gt;Unity's &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/WhatsNewUnity63.html" rel="noopener noreferrer"&gt;6.3 documentation&lt;/a&gt; says &lt;code&gt;UnityWebRequest&lt;/code&gt; uses HTTP/2 by default on supported platforms, listing Android, Embedded Linux, Linux, macOS, PS4, PS5, UWP, and Windows.&lt;/p&gt;

&lt;p&gt;That list does not prove that every unlisted platform lacks HTTP/2. iOS and web builds need separate verification.&lt;/p&gt;

&lt;p&gt;Android also exposes a rollback switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project Settings &amp;gt; Player &amp;gt; Android &amp;gt; Other Settings &amp;gt; Configuration
Force UnityWebRequest via HTTP/1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it for diagnosis or compatibility, not as the normal default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity 6.5: forcing HTTP/1.1 or HTTP/2
&lt;/h3&gt;

&lt;p&gt;Unity 6.5 adds &lt;code&gt;UnityWebRequest.httpForcedVersion&lt;/code&gt;. Its default is &lt;code&gt;HttpForcedVersion.NotForced&lt;/code&gt;, which leaves selection to negotiation. The available enum values stop at HTTP/2; there is no HTTP/3 value.&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;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.Networking&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiClient&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;static&lt;/span&gt; &lt;span class="n"&gt;IEnumerator&lt;/span&gt; &lt;span class="nf"&gt;GetJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Action&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;onSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;,&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;onError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UnityWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetRequestHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cp"&gt;#if UNITY_6000_5_OR_NEWER
&lt;/span&gt;        &lt;span class="c1"&gt;// Already the default; explicit here to show the intended policy.&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;httpForcedVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpForcedVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotForced&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendWebRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;UnityWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadHandler&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="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&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;This demonstrates version policy, not a production client. Production code still needs cancellation, deadlines, bounded retries, status handling, size limits, authentication refresh, redirects, request IDs, and idempotency.&lt;/p&gt;

&lt;p&gt;Force HTTP/1.1 or HTTP/2 only for comparison or rollback; permanent forcing can disable fallback. Unity's &lt;a href="https://unity.com/releases/editor/whats-new/6000.5.0f1" rel="noopener noreferrer"&gt;6.5 release notes&lt;/a&gt; include an HTTP/2 fix for POST bodies above 600 KB, so test the exact editor patch you ship.&lt;/p&gt;

&lt;p&gt;Unity 6.5 also adds &lt;code&gt;UnityHttpMessageHandler&lt;/code&gt; for &lt;code&gt;HttpClient&lt;/code&gt; and gRPC. It neither enables HTTP/3 automatically nor makes gRPC an action-game transport; test the exact IL2CPP, AOT, generated-code, and package combination.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS: separate client path, TLS, and HTTP/3 capability
&lt;/h3&gt;

&lt;p&gt;URLSession on iOS 15 and later supports HTTP/3 and discovers support through &lt;code&gt;Alt-Svc&lt;/code&gt; or DNS HTTPS resource records. That alone does not prove every Unity request uses URLSession.&lt;/p&gt;

&lt;p&gt;For Unity 6.5, keep three facts separate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The release notes state that iOS &lt;code&gt;UnityWebRequest&lt;/code&gt; moved from &lt;code&gt;NSURLSession&lt;/code&gt; to &lt;code&gt;libcurl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mbed TLS concerns TLS processing; it is not the HTTP client replacing URLSession.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HttpForcedVersion&lt;/code&gt; has no HTTP/3 enum, so verify negotiation rather than infer it from iOS support.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify ordinary &lt;code&gt;UnityWebRequest&lt;/code&gt; through CDN/server logs, ALPN, or platform instrumentation. For URLSession background transfer, resume integration, or an intentional HTTP/3 path, use a Swift or Objective-C plugin. See Apple's &lt;a href="https://developer.apple.com/documentation/technotes/tn3102-http3-in-your-app" rel="noopener noreferrer"&gt;HTTP/3 in your app&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity Web: the browser chooses
&lt;/h3&gt;

&lt;p&gt;Unity web builds route &lt;code&gt;UnityWebRequest&lt;/code&gt; through the Fetch API. The application cannot directly open arbitrary TCP or UDP sockets, so the browser and hosting environment choose HTTP/2 or HTTP/3.&lt;/p&gt;

&lt;p&gt;Verify CDN/origin support, CORS headers, the browser developer tools' Protocol column, and browser support for WebSocket, WebRTC, or WebTransport. Unity's &lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/webgl-networking.html" rel="noopener noreferrer"&gt;web networking documentation&lt;/a&gt; describes the Fetch and CORS constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workload 1: ordinary game APIs
&lt;/h2&gt;

&lt;p&gt;A slow login call may spend most of its time in the server. Measure the entire path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS + connection + TLS + request upload + server wait
+ response download + decompression/JSON + main-thread application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/2 and HTTP/3 can improve connection reuse, concurrency, and loss behavior. They do not fix slow database queries, billing calls, lock contention, oversized JSON, or expensive deserialization.&lt;/p&gt;

&lt;p&gt;A practical optimization order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;measure server time per endpoint;&lt;/li&gt;
&lt;li&gt;remove unnecessary startup dependencies and merge calls where sensible;&lt;/li&gt;
&lt;li&gt;reduce redundant fields and headers;&lt;/li&gt;
&lt;li&gt;use suitable compression;&lt;/li&gt;
&lt;li&gt;verify HTTP/2 negotiation and connection reuse;&lt;/li&gt;
&lt;li&gt;evaluate HTTP/3 only if mobile-network problems remain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multiplexing does not mean “send everything at once.” Group calls as boot-critical, first-screen, or deferrable, and shorten the critical path without overloading the server.&lt;/p&gt;

&lt;p&gt;Retry according to semantics. A failed GET can often use bounded exponential backoff. Blindly retrying a purchase or currency spend can execute it twice. Idempotency remains an application concern under every HTTP version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workload 2: Addressables and asset delivery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fix delivery design before adding HTTP/3
&lt;/h3&gt;

&lt;p&gt;Slow Addressables downloads can come from too many tiny bundles, oversized bundles, duplicated dependencies, a distant CDN, broken cache keys, unsuitable compression, excessive concurrency, or weak catalog consistency.&lt;/p&gt;

&lt;p&gt;HTTP/3 does not repair hundreds of tiny bundles or a URL scheme that always misses cache. First make bundle layout and CDN delivery work well over HTTP/2, then compare HTTP/3 under realistic packet loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bound concurrency even with HTTP/2
&lt;/h3&gt;

&lt;p&gt;Multiplexing does not remove receive buffers, decryption, decompression, storage writes, hash checks, or main-thread spikes when many downloads finish together. CDNs also limit concurrent streams.&lt;/p&gt;

&lt;p&gt;Addressables exposes &lt;code&gt;WebRequestQueue.SetMaxConcurrentRequests(int)&lt;/code&gt;. Six is only a starting point; compare values such as four, six, and eight for your devices and bundle sizes.&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;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.AddressableAssets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.Networking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.ResourceManagement&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddressablesNetworkSettings&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;s_configured&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UnityWebRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s_existingOverride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RuntimeInitializeOnLoadMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RuntimeInitializeLoadType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeforeSceneLoad&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;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_configured&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;s_configured&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;WebRequestQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMaxConcurrentRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;s_existingOverride&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Addressables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebRequestOverride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Addressables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebRequestOverride&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ApplySettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ApplySettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UnityWebRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s_existingOverride&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Keep the default NotForced behavior and allow negotiation.&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;Do not erase an override that adds signed URLs, authorization, certificates, or telemetry. Compose with it or centralize settings. The guard only covers one AppDomain; Domain Reload settings, multiple bootstraps, and tests still need one owner before the first Addressables operation.&lt;/p&gt;

&lt;p&gt;These APIs are from Addressables 2.9, listed as 2.9.1 in Unity 6.5.0f1. Other Unity 6.3–6.5 projects may differ; check &lt;code&gt;manifest.json&lt;/code&gt;, &lt;code&gt;packages-lock.json&lt;/code&gt;, and compile your shipped versions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.addressables%402.9/api/UnityEngine.ResourceManagement.WebRequestQueue.SetMaxConcurrentRequests.html" rel="noopener noreferrer"&gt;&lt;code&gt;WebRequestQueue.SetMaxConcurrentRequests&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.addressables%402.9/api/UnityEngine.AddressableAssets.Addressables.WebRequestOverride.html" rel="noopener noreferrer"&gt;&lt;code&gt;Addressables.WebRequestOverride&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When HTTP/3 is promising for assets
&lt;/h3&gt;

&lt;p&gt;Test HTTP/3 when iOS and Android dominate, users download over 4G/5G, dozens of files run together, packet loss stalls unrelated work, network transitions are common, the CDN supports UDP/443 and HTTP/3 advertisement, and p95/p99 completion time is a business problem.&lt;/p&gt;

&lt;p&gt;Tail latency, failure rate, and retry volume can matter more than the mean. An internal Wi-Fi-only application with few requests may never recover the plugin cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  One large file: prioritize resume and integrity
&lt;/h3&gt;

&lt;p&gt;A multi-gigabyte patch needs application-level resume:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write to a temporary file;&lt;/li&gt;
&lt;li&gt;resume through &lt;code&gt;Accept-Ranges: bytes&lt;/code&gt; and a Range request;&lt;/li&gt;
&lt;li&gt;verify identity with an ETag or content hash;&lt;/li&gt;
&lt;li&gt;validate the final hash or signature;&lt;/li&gt;
&lt;li&gt;atomically replace the production file and retain the last known-good version on failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HTTP/3 does not manage resume position, corrupted partial data, or catalog consistency. Apple's &lt;a href="https://developer.apple.com/videos/play/wwdc2023/10006/" rel="noopener noreferrer"&gt;resumable file transfer session&lt;/a&gt; covers Range requests, ETags, background transfer, and URLSession resume behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you add native iOS or Android plugins?
&lt;/h2&gt;

&lt;p&gt;Ask whether measured gains justify another stack, not merely whether a plugin supports HTTP/3. Native integration adds C# bridging, cancellation, buffers, cookies, caches, certificates, build dependencies, and platform-specific debugging. Ordinary REST APIs should start with Unity's negotiated HTTP/2 path.&lt;/p&gt;

&lt;p&gt;A plugin is reasonable when tail latency or failures improve materially, background delivery is mandatory, or content delivery is central. The CDN must support UDP/443, TLS 1.3, ALPN &lt;code&gt;h3&lt;/code&gt;, &lt;code&gt;Alt-Svc&lt;/code&gt; or DNS HTTPS records, and HTTP/2 fallback.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS: URLSession
&lt;/h3&gt;

&lt;p&gt;Apple's API provides negotiation, caching, Range requests, background transfer, and resume. For a known-capable service, &lt;code&gt;assumesHTTP3Capable&lt;/code&gt; can let URLSession attempt HTTP/3 before receiving an advertisement, but it does not guarantee &lt;code&gt;h3&lt;/code&gt; and still permits fallback. For large files, write to a native temporary file and report progress, path, and validation instead of copying a huge &lt;code&gt;byte[]&lt;/code&gt; through Unity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unity C#
  ├─ StartDownload(url, destination, requestId)
  ├─ Cancel(requestId)
  └─ OnProgress / OnComplete / OnError
             ↓
iOS plugin: URLSessionDownloadTask / background URLSession
             ↓
CDN: HTTP/3 → HTTP/2 → HTTP/1.1 fallback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-lived signed URLs can avoid duplicating token state. URLSession and &lt;code&gt;UnityWebRequest&lt;/code&gt; do not automatically share pools, cookies, cache, certificates, retries, or metrics. Native-enable a narrow path and assign ownership of authentication, cache deletion, and diagnostics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android: HttpEngine or Cronet
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Protocols&lt;/th&gt;
&lt;th&gt;Main condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HttpEngine&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2, HTTP/3 over QUIC&lt;/td&gt;
&lt;td&gt;API 34 or S Extensions 7+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cronet via Google Play services&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2, HTTP/3 over QUIC&lt;/td&gt;
&lt;td&gt;Requires Play services; small size impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded Cronet&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2, HTTP/3 over QUIC&lt;/td&gt;
&lt;td&gt;Bundled implementation; documentation cites about 8 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OkHttp&lt;/td&gt;
&lt;td&gt;HTTP/1.1 and HTTP/2&lt;/td&gt;
&lt;td&gt;Not an HTTP/3 option&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use HttpEngine when the minimum platform permits it. For wider coverage, Play services Cronet plus fallback may work; Embedded Cronet is relevant where Play services are unavailable.&lt;/p&gt;

&lt;p&gt;Reuse engine instances. Running them beside &lt;code&gt;UnityWebRequest&lt;/code&gt; splits connection pools, DNS behavior, caches, cookies, certificates, and metrics. Migrate one high-value path first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/connectivity/cronet" rel="noopener noreferrer"&gt;Android Cronet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/media/media3/exoplayer/network-stacks" rel="noopener noreferrer"&gt;Android network stacks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workload 3: real-time networking is a separate decision
&lt;/h2&gt;

&lt;p&gt;Faster REST is not 60 fps state synchronization. Real-time selection depends on reliability, stale-data handling, ordering, authority, prediction, rollback, tick rate, bandwidth, NAT traversal, reconnect, matchmaking, and platform restrictions.&lt;/p&gt;

&lt;p&gt;HTTP/3 uses QUIC, but an HTTP/3 REST endpoint is not automatically a game-state transport.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Candidate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turn-based or asynchronous play&lt;/td&gt;
&lt;td&gt;HTTP API with normal negotiation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lobby, friends, and light notifications&lt;/td&gt;
&lt;td&gt;WebSocket or Photon Realtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat&lt;/td&gt;
&lt;td&gt;WebSocket, Photon Chat, or gRPC streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Co-op or competitive action&lt;/td&gt;
&lt;td&gt;Photon Fusion, Unity Transport, or dedicated UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typed bidirectional streams or telemetry&lt;/td&gt;
&lt;td&gt;gRPC streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser low-latency communication&lt;/td&gt;
&lt;td&gt;WebSocket, WebRTC, and possibly WebTransport&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Photon does not replace asset HTTP. A common architecture uses HTTP APIs for account data, HTTP plus a CDN for bundles, and Photon for matchmaking and sessions. Web builds also have browser transport restrictions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://doc.photonengine.com/realtime/current/connection-and-authentication/tcp-and-udp-port-numbers" rel="noopener noreferrer"&gt;Photon protocol and ports&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gRPC is typed RPC, not a universal action-game transport. HTTP/2 streaming fits internal APIs, telemetry, and typed streams, but not necessarily stale positions that should be dropped. Test channel reuse, stream limits, AOT, and generated code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://grpc.io/docs/guides/performance/" rel="noopener noreferrer"&gt;gRPC performance best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benchmark protocol choices correctly
&lt;/h2&gt;

&lt;p&gt;Do not decide from one Editor run on wired Ethernet. Test real iOS and Android devices under Wi-Fi, 4G/5G, 50–150 ms latency, 1–3% loss, network transitions, and cold versus reused connections. Record CDN &lt;code&gt;HIT&lt;/code&gt;/&lt;code&gt;MISS&lt;/code&gt;, local cache state, and first versus repeat download so cache effects are not mistaken for protocol gains.&lt;/p&gt;

&lt;p&gt;Compare TTFB, time to first playable, all-files completion, p50/p95/p99, failures, retries, retransferred bytes, CPU, peak memory, battery, and network-change recovery—not only the mean.&lt;/p&gt;

&lt;p&gt;“HTTP/3 enabled” at the CDN is not proof of use. Verify with CDN/origin logs, ALPN, iOS Instruments, the browser Protocol column, or Cronet's &lt;code&gt;getNegotiatedProtocol()&lt;/code&gt;. Do not assume ordinary &lt;code&gt;UnityWebRequest&lt;/code&gt; exposes &lt;code&gt;h2&lt;/code&gt; or &lt;code&gt;h3&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical selection flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Separate APIs, asset delivery, and real-time state.&lt;/li&gt;
&lt;li&gt;Establish HTTP/2 negotiation; fix dependencies, bundle layout, caching, Range support, and idempotency first.&lt;/li&gt;
&lt;li&gt;Measure latency, loss, transitions, tail latency, and failures on real devices.&lt;/li&gt;
&lt;li&gt;Prototype a native stack on one high-value path, and adopt it only when gains exceed maintenance and diagnostic cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep HTTP/2 fallback even after a successful HTTP/3 rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For most Unity projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use normal negotiation and HTTP/2 for ordinary APIs, with HTTP/1.1 as fallback;&lt;/li&gt;
&lt;li&gt;fix CDN caching, bundle layout, concurrency, Range requests, hashes, and resume logic before changing transports;&lt;/li&gt;
&lt;li&gt;use HTTP/3 only where mobile loss, parallel work, or network transitions show a measured benefit;&lt;/li&gt;
&lt;li&gt;add URLSession, HttpEngine, or Cronet only when native integration is justified;&lt;/li&gt;
&lt;li&gt;choose Photon, Unity Transport, WebSocket, or gRPC from real-time requirements, not the HTTP version number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is rarely a reason to move every request to HTTP/3 at once. &lt;strong&gt;Build a correct HTTP/2 path, measure real devices, and native-enable only the traffic where the data supports it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A follow-up can compare Photon, Unity Transport/Netcode, WebSocket, gRPC, WebRTC, WebTransport, and custom UDP by reliability, tick model, authority, browser support, cost, and operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/WhatsNewUnity63.html" rel="noopener noreferrer"&gt;Unity 6.3: What's new&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unity.com/releases/editor/whats-new/6000.5.0f1" rel="noopener noreferrer"&gt;Unity 6.5 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Networking.UnityWebRequest-httpForcedVersion.html" rel="noopener noreferrer"&gt;&lt;code&gt;UnityWebRequest.httpForcedVersion&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9113.html" rel="noopener noreferrer"&gt;RFC 9113: HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9114.html" rel="noopener noreferrer"&gt;RFC 9114: HTTP/3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>networking</category>
      <category>http</category>
    </item>
    <item>
      <title>Unity CLI + AI Agents: A Safe Observe-Act-Verify Workflow</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Tue, 01 Sep 2026 06:15:10 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/unity-cli-ai-agents-a-safe-observe-act-verify-workflow-5c9k</link>
      <guid>https://dev.to/gamedevtoollab/unity-cli-ai-agents-a-safe-observe-act-verify-workflow-5c9k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Checked against the official documentation available on September 1, 2026. Unity CLI and &lt;code&gt;com.unity.pipeline&lt;/code&gt; are experimental, so the installed version's &lt;code&gt;unity --help&lt;/code&gt; is the authoritative reference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why writing C# is not enough
&lt;/h2&gt;

&lt;p&gt;AI agents can edit Unity C#, but an edit is not a verified result. Unity may need to recompile and reimport assets; tests, Console errors, Scene state, Play Mode, or a built Player may also need inspection.&lt;/p&gt;

&lt;p&gt;A useful agent must close this loop without asking a human to copy every result from the Editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Observe -&amp;gt; Plan -&amp;gt; Act -&amp;gt; Wait -&amp;gt; Verify -&amp;gt; Recover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unity's standalone CLI and &lt;code&gt;com.unity.pipeline&lt;/code&gt; provide an official execution layer for that loop. The focus here is a restricted workflow for inspection, mutation, and verification—not a complete command reference or vendor-specific CI file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the three command-line surfaces
&lt;/h2&gt;

&lt;p&gt;Unity developers often call three different systems “the CLI.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional Editor arguments
&lt;/h3&gt;

&lt;p&gt;The Editor executable still accepts &lt;code&gt;-batchmode&lt;/code&gt;, &lt;code&gt;-projectPath&lt;/code&gt;, &lt;code&gt;-executeMethod&lt;/code&gt;, &lt;code&gt;-runTests&lt;/code&gt;, and related flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Unity.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-batchmode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-quit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-projectPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Projects\MyGame"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-executeMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BuildCommand.BuildWindows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-logFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Logs\unity-build.log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This remains useful for headless jobs, but it launches an Editor process and locks the project; it does not control an already-open Editor.&lt;/p&gt;

&lt;h3&gt;
  
  
  The standalone Unity CLI
&lt;/h3&gt;

&lt;p&gt;The newer &lt;code&gt;unity&lt;/code&gt; executable manages Editors, modules, authentication, projects, builds, tests, and connected instances without opening the Hub UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity editors &lt;span class="nt"&gt;--installed&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json
unity &lt;span class="nb"&gt;install &lt;/span&gt;6000.3.7f1 &lt;span class="nt"&gt;-m&lt;/span&gt; android
unity open ./MyGame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It supports human, TSV, JSON, NDJSON, and GitHub-oriented output, with structured stdout, diagnostic stderr, and exit codes for automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity Pipeline
&lt;/h3&gt;

&lt;p&gt;After adding &lt;code&gt;com.unity.pipeline&lt;/code&gt;, the CLI can discover commands registered by a running Editor or configured Development Player:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command
&lt;/span&gt;unity &lt;span class="nb"&gt;command &lt;/span&gt;get_console_logs &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Editor arguments&lt;/td&gt;
&lt;td&gt;Existing headless automation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standalone CLI&lt;/td&gt;
&lt;td&gt;Editor/module management, projects, builds, tests, CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline&lt;/td&gt;
&lt;td&gt;Fast inspection and controlled operations in a running Editor or Player&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Design a small capability surface
&lt;/h2&gt;

&lt;p&gt;The agent reaches Unity through shell commands or the CLI's MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI agent -&amp;gt; unity command / unity mcp -&amp;gt; Unity CLI
         -&amp;gt; com.unity.pipeline -&amp;gt; Editor / Development Player
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not expose unlimited Unity API access as the default. Expose named commands with declared arguments and structured results. Your team can review that surface, restrict paths, require previews, and decide which operations need human approval.&lt;/p&gt;

&lt;p&gt;Start with these three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nt"&gt;--version&lt;/span&gt;
unity status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity list &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They identify the CLI version, connected instance, and registered schemas. Next, add a read-only &lt;code&gt;project_health&lt;/code&gt; command. After each C# edit, require proof of final compilation, passing targeted tests, zero new Console errors, and no forbidden asset changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record versions and install deliberately
&lt;/h2&gt;

&lt;p&gt;As of September 1, 2026, the current documented releases are Unity CLI &lt;code&gt;1.0.0-beta.6&lt;/code&gt; and &lt;code&gt;com.unity.pipeline 0.5.0-exp.1&lt;/code&gt;. Store a compatibility record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unity Editor: 6000.x.yf1
Unity CLI: 1.0.0-beta.6
com.unity.pipeline: 0.5.0-exp.1
Verified: 2026-09-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;beta&lt;/code&gt; channel is not a version pin. Make CI fail when &lt;code&gt;unity --version&lt;/code&gt; differs, and avoid unconditional upgrades inside jobs.&lt;/p&gt;

&lt;p&gt;Install the CLI through an official route, then inspect it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS/Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nv"&gt;UNITY_CLI_CHANNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;beta bash

&lt;span class="c"&gt;# macOS/Linux alternative&lt;/span&gt;
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; unity-cli

unity &lt;span class="nt"&gt;--version&lt;/span&gt;
unity doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Unity.CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installing modules with a new Editor and adding them later are separate operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;install &lt;/span&gt;6000.3.7f1 &lt;span class="nt"&gt;-m&lt;/span&gt; android webgl
unity install-modules &lt;span class="nt"&gt;-e&lt;/span&gt; 6000.3.7f1 &lt;span class="nt"&gt;-m&lt;/span&gt; android webgl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the project, install Pipeline, wait for compilation, and inspect the connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/MyGame
unity auth login
unity pipeline list-versions
unity pipeline &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--package-version&lt;/span&gt; 0.5.0-exp.1
unity status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity list &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm the exact Pipeline flags with local help. Pipeline-driven Editor control requires Unity 6.0 LTS or later. With several Editors open, run from the intended project or specify it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;--project-path&lt;/span&gt; /path/to/MyGame editor_status &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Old examples using &lt;code&gt;--instance &amp;lt;host:port&amp;gt;&lt;/code&gt; are stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct commands or MCP?
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;unity command&lt;/code&gt; and &lt;code&gt;unity eval&lt;/code&gt; directly when the agent can compose shell commands reliably. Unity's current guidance notes that this path is faster and consumes fewer model tokens than MCP.&lt;/p&gt;

&lt;p&gt;Use the built-in MCP server when the client cannot run arbitrary commands or has a stronger tool-calling workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity mcp configure &lt;span class="nt"&gt;--list&lt;/span&gt;
unity mcp configure &amp;lt;client&amp;gt;
unity skill &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;agent-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Client configuration, approval UI, working directory, and inherited permissions vary. MCP is transport and discovery, not authorization; keep policy in the repository and CI.&lt;/p&gt;

&lt;p&gt;Unity has deprecated the MCP server bundled with the in-Editor AI assistant package in favor of &lt;code&gt;unity mcp&lt;/code&gt;; third-party MCP packages are a separate choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn every task into a closed loop
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Observe and Plan
&lt;/h3&gt;

&lt;p&gt;Capture the initial Editor state, open Scenes, dirty state, and existing errors. Define success mechanically: a named test passes, new-error count is zero, only approved files changed, and Scene or Prefab edits are forbidden unless requested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command &lt;/span&gt;editor_status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command &lt;/span&gt;list_open_scenes &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command &lt;/span&gt;get_console_logs &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Act and Wait
&lt;/h3&gt;

&lt;p&gt;Use normal file editing for persistent C#, typed &lt;code&gt;[CliCommand]&lt;/code&gt; methods for repeatable Unity operations, and &lt;code&gt;eval&lt;/code&gt; only for narrow investigation. Start and completion can be different states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recompile -&amp;gt; recompile_status
run_tests -&amp;gt; test_status
build -&amp;gt; build_status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Domain reloads, imports, and target switches can briefly interrupt the connection. Retry only within a bounded policy and poll a final status rather than treating request acceptance as success.&lt;/p&gt;

&lt;p&gt;Pipeline &lt;code&gt;0.5.0-exp.1&lt;/code&gt; also supports detached jobs and live progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command&lt;/span&gt; &amp;lt;long_command&amp;gt; &lt;span class="nt"&gt;--detach&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
unity job status &amp;lt;job-id&amp;gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
unity job &lt;span class="nb"&gt;wait&lt;/span&gt; &amp;lt;job-id&amp;gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detached jobs survive a client timeout, but remain serialized and do not survive domain reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify and Recover
&lt;/h3&gt;

&lt;p&gt;Match evidence to the artifact:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Required evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;final compile state, targeted tests, new Console errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scene/Prefab&lt;/td&gt;
&lt;td&gt;serialized values, dirty/save state, Git diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Play Mode state, Game View capture, logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Player state, Player log, exceptions, capture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;final result, output path, artifact checks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Report commands, exit codes, test counts, new errors, changed paths, and outputs. “It looks fixed” is not evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a typed read-only command
&lt;/h2&gt;

&lt;p&gt;A custom command is a static method marked with &lt;code&gt;[CliCommand]&lt;/code&gt;; arguments use &lt;code&gt;[CliArg]&lt;/code&gt;. The example below depends on &lt;code&gt;UnityEditor&lt;/code&gt;, so place it in an &lt;code&gt;Editor&lt;/code&gt; folder or Editor-only Assembly Definition.&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="cp"&gt;#if UNITY_EDITOR
&lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Pipeline.Commands&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEditor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.SceneManagement&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectHealthCommands&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CliCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"project_health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Return editor and scene state."&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;static&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;GetProjectHealth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scenes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SceneManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sceneCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SceneManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSceneAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDirty&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&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;unityVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unityVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;isPlaying&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EditorApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;isCompiling&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EditorApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isCompiling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;buildTarget&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EditorUserBuildSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeBuildTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;scenes&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;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command &lt;/span&gt;project_health &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MainThreadRequired&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;; keep it unless the implementation is independent of main-thread-only Unity APIs. Return fields, not prose, so an agent can assert them. At the Pipeline layer, a response conceptually resembles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project_health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isCompiling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scenes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isDirty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionTimeMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For failures, add stable codes such as &lt;code&gt;INVALID_ARGUMENT&lt;/code&gt;, &lt;code&gt;NOT_FOUND&lt;/code&gt;, or &lt;code&gt;PRECONDITION_FAILED&lt;/code&gt; to your own result contract. Runtime commands belong in a Player-included assembly with no &lt;code&gt;UnityEditor&lt;/code&gt; dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep &lt;code&gt;eval&lt;/code&gt; for investigation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; is useful for a one-off read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"return UnityEngine.Object.FindObjectsByType&amp;lt;UnityEngine.Light&amp;gt;(UnityEngine.FindObjectsSortMode.None).Length;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is close to arbitrary C# execution. Never send model-generated or prompt-derived code to it without review. Limit it to short, human-inspected, read-only expressions and do not concatenate untrusted external strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One-off inspection          -&amp;gt; eval
Repeated read               -&amp;gt; read-only CliCommand
Scene/Prefab authoring       -&amp;gt; typed CliCommand + Undo
Destructive asset operation -&amp;gt; dry_run + confirm + Git/backup
Stable CI task              -&amp;gt; unity run --command / unity test / unity build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preview mutations and separate approval
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dry_run&lt;/code&gt; and &lt;code&gt;confirm&lt;/code&gt; are per-command conventions; Pipeline does not provide a central approval service.&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;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Pipeline.Commands&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CliCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"normalize_enemy_layers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Preview before applying."&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;static&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;NormalizeEnemyLayers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CliArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Apply changes."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CliArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dry_run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Preview only."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;dryRun&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FindTargets&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dryRun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dry_run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;samplePaths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssetPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;warnings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;50&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;"Target count exceeds 50."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;requiresApproval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSceneOrPrefab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Pass confirm=true or dry_run=true."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;ApplyWithUndo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&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;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"applied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&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;&lt;code&gt;FindTargets&lt;/code&gt; and &lt;code&gt;ApplyWithUndo&lt;/code&gt; are project-specific placeholders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command &lt;/span&gt;normalize_enemy_layers &lt;span class="nt"&gt;--dry_run&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command &lt;/span&gt;normalize_enemy_layers &lt;span class="nt"&gt;--confirm&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return a count, representative paths, warnings, and approval requirement instead of every target. This limits token usage and accidental disclosure of unpublished asset paths.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;confirm=true&lt;/code&gt; is only an intent flag. It does not record who approved what, so it cannot replace a human approval log or pull-request diff. Automatic application should be limited to approved generated paths, no deletion, strict count limits, a clean dedicated worktree, and deterministic verification.&lt;/p&gt;

&lt;p&gt;Require human approval for Scenes, Prefabs, &lt;code&gt;ProjectSettings&lt;/code&gt;, &lt;code&gt;Packages&lt;/code&gt;, deletion, bulk overwrites, external I/O, signing, or publishing. &lt;code&gt;AuthoringUndoScope&lt;/code&gt; can group registered Scene/Object Undo operations, but AssetDatabase, Package Manager, and some settings changes need Git or backup recovery. Restrict file I/O to approved roots and reject absolute paths and &lt;code&gt;..&lt;/code&gt; traversal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parse JSON and exit codes at every layer
&lt;/h2&gt;

&lt;p&gt;Use JSON for a buffered result and NDJSON for machine-readable progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity editors &lt;span class="nt"&gt;--installed&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json
unity &lt;span class="nb"&gt;install &lt;/span&gt;6000.3.7f1 &lt;span class="nt"&gt;--format&lt;/span&gt; ndjson
unity &lt;span class="nb"&gt;command &lt;/span&gt;project_health &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two result layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Typical fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standalone CLI&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;success&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;errors&lt;/code&gt;, &lt;code&gt;warnings&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline response&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;success&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;result&lt;/code&gt;, &lt;code&gt;executionTimeMs&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conceptually, the Pipeline response can be nested under CLI &lt;code&gt;data&lt;/code&gt;. That is a parsing model, not a promise that every experimental build has an identical shape. Capture your installed version's actual &lt;code&gt;unity command ... --json&lt;/code&gt; output before freezing a parser.&lt;/p&gt;

&lt;p&gt;Unity CLI &lt;code&gt;1.0.0-beta.6&lt;/code&gt; documents these process exit codes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;General error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Invalid usage or arguments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Authentication or authorization failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Required configuration or context missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Primary operation failed; &lt;code&gt;unity test&lt;/code&gt; produced no verdict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Unity service unreachable after retries; safe to retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unity test&lt;/code&gt; ran and one or more tests failed; do not retry as infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;Ctrl+C / SIGINT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;td&gt;SIGTERM or runner timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command &lt;/span&gt;project_health &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  1&amp;gt; result.json &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt; error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluate success in order: process exit code, outer CLI &lt;code&gt;success&lt;/code&gt;, inner Pipeline &lt;code&gt;success&lt;/code&gt;, then domain results such as failed tests or build output. Exit code &lt;code&gt;0&lt;/code&gt; only proves success at the process layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the running Editor from clean CI
&lt;/h2&gt;

&lt;p&gt;A running Editor is ideal for current Scene state, Console inspection, targeted tests, small authoring operations, Play Mode, and exploratory reads. Use a fresh process and workspace for pull requests, full suites, deterministic generation, and release builds.&lt;/p&gt;

&lt;p&gt;The same registered command can run headlessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-path&lt;/span&gt; ./MyGame &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--command&lt;/span&gt; validate_release_assets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connect to a Development Player
&lt;/h2&gt;

&lt;p&gt;Runtime connectivity targets Windows, macOS, or Linux standalone Development Builds, not release builds. Mobile, console, and WebGL are outside this article's scope.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;Pipeline &amp;gt; Runtime Pipeline Manager&lt;/code&gt; to a startup Scene GameObject.&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;enableInBuilds&lt;/code&gt;; normally keep &lt;code&gt;autoStart&lt;/code&gt; enabled and port &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make and launch a standalone Development Build.&lt;/li&gt;
&lt;li&gt;Wait for its runtime descriptor.&lt;/li&gt;
&lt;li&gt;Put &lt;code&gt;--runtime&lt;/code&gt; or &lt;code&gt;--runtime-path&lt;/code&gt; immediately after &lt;code&gt;command&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unity &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;--runtime&lt;/span&gt; MyGame.exe runtime_status &lt;span class="nt"&gt;--json&lt;/span&gt;
unity &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;--runtime-path&lt;/span&gt; &lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\B&lt;/span&gt;&lt;span class="s2"&gt;uilds&lt;/span&gt;&lt;span class="se"&gt;\M&lt;/span&gt;&lt;span class="s2"&gt;yGame"&lt;/span&gt; runtime_status &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime server code is compiled only into Development Players. &lt;code&gt;enableInBuilds=true&lt;/code&gt; does not create a release-build backdoor. Keep custom runtime commands in a Player assembly and return structured state—Scene, position, gameplay state, exception count—alongside logs or captures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-only still requires security controls
&lt;/h2&gt;

&lt;p&gt;Pipeline binds to local loopback and authenticates with a bearer token from a descriptor file. Let the CLI handle discovery. A custom HTTP client should use &lt;code&gt;127.0.0.1&lt;/code&gt; explicitly because &lt;code&gt;localhost&lt;/code&gt; can resolve to IPv6 &lt;code&gt;::1&lt;/code&gt;, which is unreliable on the relevant Mono &lt;code&gt;HttpListener&lt;/code&gt; path.&lt;/p&gt;

&lt;p&gt;The agent still has repository and shell access. Use a dedicated branch or worktree, inspect &lt;code&gt;git status&lt;/code&gt;, and expand permissions from read-only inspection to narrowly scoped mutations.&lt;/p&gt;

&lt;p&gt;Treat logs, captures, descriptor files, credentials, signing material, IDs, and unreleased art as sensitive; define what may reach a cloud model.&lt;/p&gt;

&lt;p&gt;Put the operational rules in &lt;code&gt;AGENTS.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Unity workflow rules&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Use ProjectSettings/ProjectVersion.txt as the Editor version source.
&lt;span class="p"&gt;-&lt;/span&gt; Run unity status and project_health before changes.
&lt;span class="p"&gt;-&lt;/span&gt; Check git status before touching Scenes, Prefabs, or settings.
&lt;span class="p"&gt;-&lt;/span&gt; Run dry_run first; dry_run is not approval.
&lt;span class="p"&gt;-&lt;/span&gt; Require human approval or PR review for Scenes, Prefabs,
  ProjectSettings, Packages, deletion, and bulk updates.
&lt;span class="p"&gt;-&lt;/span&gt; Use eval only for reviewed, read-only investigation.
&lt;span class="p"&gt;-&lt;/span&gt; After C# edits, wait for compilation, run targeted tests,
  and prove that new Console error count is zero.
&lt;span class="p"&gt;-&lt;/span&gt; Never sign or upload a release.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common failure modes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Editor found:&lt;/strong&gt; check &lt;code&gt;unity status --json&lt;/code&gt;, &lt;code&gt;unity editors running --json&lt;/code&gt;, &lt;code&gt;unity pipeline list --json&lt;/code&gt;, compilation state, and working directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command missing:&lt;/strong&gt; the method must be static, compile successfully, and live in the correct Editor or Player assembly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale flags:&lt;/strong&gt; read &lt;code&gt;unity --help&lt;/code&gt;, command-specific help, and &lt;code&gt;unity list --json&lt;/code&gt; after upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary disconnect:&lt;/strong&gt; bound retries and poll final status after domain reload, import, target switch, or the Pipeline &lt;code&gt;0.5&lt;/code&gt; startup settling state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False test success:&lt;/strong&gt; request acceptance is not a passing suite; poll &lt;code&gt;test_status&lt;/code&gt; or inspect the NUnit report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsaved Scene:&lt;/strong&gt; report dirty state and make saving a separate explicit operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo gap:&lt;/strong&gt; use previews, Git, and backups for AssetDatabase, packages, and settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A staged adoption plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read:&lt;/strong&gt; status, hierarchy, Console, test list, and &lt;code&gt;project_health&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify:&lt;/strong&gt; bounded compile waits, targeted tests, Play Mode, and captures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author narrowly:&lt;/strong&gt; typed commands with preview, limits, approval, Undo, and Git diffs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect runtime:&lt;/strong&gt; combine Development Player state, logs, exceptions, and captures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is a small command set with explicit preconditions, side effects, outputs, and recovery paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Unity CLI integration matters when it turns an AI edit into a verified engineering operation. Separate the standalone CLI, traditional Editor arguments, and Pipeline. Use &lt;code&gt;eval&lt;/code&gt; only for narrow investigation, promote repeated work into typed commands, and require every task to observe, act, wait, and verify.&lt;/p&gt;

&lt;p&gt;Combine &lt;code&gt;dry_run&lt;/code&gt;, &lt;code&gt;confirm&lt;/code&gt;, limits, Undo, Git, and human approval instead of trusting one safety mechanism. Keep the fast running-Editor loop separate from clean CI, and treat local help plus captured JSON as the source of truth for the experimental versions you run.&lt;/p&gt;

&lt;p&gt;Begin with read-only &lt;code&gt;project_health&lt;/code&gt;; expand only after the agent can prove the before-and-after state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://unity.com/blog/meet-the-unity-cli" rel="noopener noreferrer"&gt;Meet the Unity CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity.com/en-us/unity-cli/unity-cli-reference" rel="noopener noreferrer"&gt;Unity CLI reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity.com/en-us/unity-cli/use-unity-cli" rel="noopener noreferrer"&gt;Use the Unity CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity.com/en-us/unity-cli/release-notes" rel="noopener noreferrer"&gt;Unity CLI release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity.com/en-us/unity-cli/replace-mcp-server-unity-cli" rel="noopener noreferrer"&gt;Replace the in-Editor MCP server with Unity CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/manual/creating-commands.html" rel="noopener noreferrer"&gt;Creating commands: Pipeline 0.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/manual/commands/build-and-compilation.html" rel="noopener noreferrer"&gt;Build, compilation, and tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/manual/connectivity.html" rel="noopener noreferrer"&gt;Connectivity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/manual/safety-and-mutations.html" rel="noopener noreferrer"&gt;Safety and mutations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/manual/runtime-setup.html" rel="noopener noreferrer"&gt;Runtime setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.pipeline@0.5/changelog/CHANGELOG.html" rel="noopener noreferrer"&gt;Pipeline changelog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>ai</category>
      <category>gamedev</category>
      <category>cli</category>
    </item>
    <item>
      <title>How to Choose an Audio System for Unreal Engine 5: Built-in Audio, Wwise, FMOD, or CRI ADX?</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Fri, 28 Aug 2026 09:58:03 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/how-to-choose-an-audio-system-for-unreal-engine-5-built-in-audio-wwise-fmod-or-cri-adx-2o26</link>
      <guid>https://dev.to/gamedevtoollab/how-to-choose-an-audio-system-for-unreal-engine-5-built-in-audio-wwise-fmod-or-cri-adx-2o26</guid>
      <description>&lt;p&gt;When an Unreal Engine 5 project starts taking audio seriously, the same questions appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the engine's built-in audio stack enough?&lt;/li&gt;
&lt;li&gt;Should the team standardize on MetaSounds?&lt;/li&gt;
&lt;li&gt;Is it time to adopt Wwise, FMOD Studio, or CRI ADX?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first distinction matters more than any feature checklist: &lt;strong&gt;MetaSounds is not the same class of product as Wwise, FMOD Studio, or CRI ADX&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MetaSounds is a DSP graph system inside Unreal's Audio Engine. The other three are broader middleware platforms with separate authoring applications, runtimes, data-build pipelines, profilers, and Unreal integration layers.&lt;/p&gt;

&lt;p&gt;In this article, &lt;strong&gt;built-in UE audio&lt;/strong&gt; means Sound Waves, Sound Cues, MetaSounds, Quartz, Audio Modulation, Submixes, Stream Caching, Sound Concurrency, and related engine features used as one production stack. The comparison is not about which product has the best sound quality. It is about who owns authoring, loading, profiling, localization, DLC, engine upgrades, and day-to-day iteration.&lt;/p&gt;

&lt;p&gt;Recorded lines are called &lt;strong&gt;dialogue audio&lt;/strong&gt;. A runtime playback slot is called a &lt;strong&gt;playback voice&lt;/strong&gt; for convenience; this is not a one-to-one synonym for every Unreal internal type. Voice chat is outside the scope of this article.&lt;/p&gt;

&lt;p&gt;No controlled benchmark was performed across all four options. CPU, memory, I/O, and first-play latency must be measured on the actual target hardware.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Version snapshot — August 28, 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Feature maturity, supported UE versions, prices, and licenses change. The dated values below were rechecked against public official documentation on this date. Before adoption, verify the exact engine version, integration release notes, EULA, order terms, platform terms, and written vendor guidance for your project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The practical conclusion first
&lt;/h2&gt;

&lt;p&gt;If the project has no established middleware pipeline, external audio-vendor requirement, shared multi-title toolchain, or confirmed large-scale localization workflow, use built-in UE audio as the &lt;strong&gt;comparison baseline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not a rule that every project should ship without middleware. It is a control case that makes the cost of another SDK, authoring project, build step, data format, license, and compatibility matrix visible.&lt;/p&gt;

&lt;p&gt;Built-in UE audio covers common SFX, 3D attenuation, music, dialogue, routing, dynamic parameters, and concurrency. Middleware becomes more valuable when a large part of content creation, data management, profiling, and validation must be moved away from programmer-owned Unreal workflows.&lt;/p&gt;

&lt;p&gt;Existing team and outsourcing experience should take priority. Make the decision in pre-production with a vertical-slice proof of concept built around the hardest audio problem in the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in UE audio is a stack, not one feature
&lt;/h2&gt;

&lt;p&gt;Judging UE5 audio only by Sound Waves or Sound Cues understates the current system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Typical UE features&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source and per-sound logic&lt;/td&gt;
&lt;td&gt;Sound Wave, Sound Cue, MetaSound Source&lt;/td&gt;
&lt;td&gt;Compression, streaming, randomization, layers, DSP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse and timing&lt;/td&gt;
&lt;td&gt;MetaSound Patch/Preset, Quartz&lt;/td&gt;
&lt;td&gt;Shared graphs and quantized scheduling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mix and routing&lt;/td&gt;
&lt;td&gt;Sound Class, Sound Mix, Audio Modulation, Submix&lt;/td&gt;
&lt;td&gt;Category volume, ducking, buses, effects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spatial and ambient audio&lt;/td&gt;
&lt;td&gt;Attenuation, spatialization, Audio Gameplay Volumes, Soundscape&lt;/td&gt;
&lt;td&gt;Distance, areas, occlusion, ambience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playback and memory&lt;/td&gt;
&lt;td&gt;Concurrency, priority, virtualization, Stream Caching&lt;/td&gt;
&lt;td&gt;Conflict resolution, continuity, chunk/cache policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;Audio Insights, Unreal Insights, console commands&lt;/td&gt;
&lt;td&gt;Runtime state, traces, parameters, supporting diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tradeoff is ownership. Middleware provides a more opinionated audio-production environment. Built-in UE audio provides composable engine features, but the project must define naming, routing, parameter, loading, profiling, and data rules.&lt;/p&gt;

&lt;p&gt;Maturity is not uniform. UE 5.8 documentation attaches shipping cautions to examples including &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/soundscape-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Soundscape&lt;/a&gt;, &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/audio-gameplay-volumes-overview?application_version=5.8" rel="noopener noreferrer"&gt;Audio Gameplay Volumes&lt;/a&gt;, &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/audiolink?application_version=5.8" rel="noopener noreferrer"&gt;AudioLink&lt;/a&gt;, and the &lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/metasounds-quick-start?application_version=5.8" rel="noopener noreferrer"&gt;MetaSounds Quick Start&lt;/a&gt;. &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/metasound-pages-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;MetaSound Pages&lt;/a&gt; is documented as Experimental.&lt;/p&gt;

&lt;p&gt;That list is not exhaustive. Check every feature used by the project, pin the engine revision where necessary, and validate Cook and packaged builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  MetaSounds and Quartz: powerful, but not a complete middleware layer
&lt;/h2&gt;

&lt;p&gt;A MetaSound Source is a playable graph that processes audio and control data. Patches provide reusable graph logic, while Presets reuse structure with different material or defaults.&lt;/p&gt;

&lt;p&gt;MetaSounds is well suited to gameplay-driven DSP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vehicle loops controlled by RPM, speed, load, or surface;&lt;/li&gt;
&lt;li&gt;weapon layers that react to environment or distance;&lt;/li&gt;
&lt;li&gt;procedural ambience built from short recordings;&lt;/li&gt;
&lt;li&gt;reusable synthesis or filtering blocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is unnecessary to make every sound a MetaSound. A UI click may need only a Sound Wave; a small random container may remain easier as a Sound Cue. Complexity should follow acoustic behavior, not a project-wide mandate.&lt;/p&gt;

&lt;p&gt;A practical split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple one-shot: Sound Wave as the acoustic asset;&lt;/li&gt;
&lt;li&gt;light randomization or legacy content: Sound Cue;&lt;/li&gt;
&lt;li&gt;gameplay-driven layers, synthesis, or DSP: MetaSound Source;&lt;/li&gt;
&lt;li&gt;shared graph behavior: MetaSound Patch;&lt;/li&gt;
&lt;li&gt;same structure with different defaults: MetaSound Preset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"A Sound Wave is enough" does not mean arbitrary gameplay code should reference it directly. Asset access can still go through project data and an audio service.&lt;/p&gt;

&lt;p&gt;MetaSounds also does not automatically solve bank partitioning, localization, DLC boundaries, global content auditing, or a separate sound-department workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample-accurate is not zero end-to-end latency
&lt;/h3&gt;

&lt;p&gt;MetaSounds can process audio at sample accuracy inside the renderer. A gameplay event still crosses the game thread, audio logic, render buffers, decoding or streaming, the operating system, and the output device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/overview-of-quartz-in-unreal-engine" rel="noopener noreferrer"&gt;Quartz&lt;/a&gt; schedules playback on musical or time boundaries and is useful for beat-aligned transitions and timing-sensitive effects. It is an &lt;strong&gt;audio-render scheduling clock&lt;/strong&gt;, not a complete rhythm-game timing system.&lt;/p&gt;

&lt;p&gt;A rhythm game still needs high-resolution input timestamps, conversion between gameplay and audio time, input/output latency measurement, user calibration, and resynchronization after pause, seek, retry, suspend, or device changes. Game-thread beat callbacks should not be the sole authority for judgment timing.&lt;/p&gt;

&lt;p&gt;Likewise, interactive music still needs rules for stems, valid transition points, competing requests, intro/loop/outro structure, and state restoration. If composers edit these structures daily without touching Unreal graphs or code, dedicated middleware authoring becomes more attractive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing, streaming, voices, and profiling still need project rules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Treat mixing as ownership, not a feature list
&lt;/h3&gt;

&lt;p&gt;Sound Classes, Sound Mixes, and Audio Modulation can all influence the final mix. Separate persistent user settings, temporary gameplay states, spatial effects, and local presentation. If multiple systems write the same Music value without a documented priority model, debugging becomes expensive regardless of the technology.&lt;/p&gt;

&lt;h3&gt;
  
  
  A loaded asset is not always ready to sound
&lt;/h3&gt;

&lt;p&gt;UE Audio Stream Caching manages compressed data in chunks. A loaded &lt;code&gt;USoundWave&lt;/code&gt; does not guarantee that the required chunk is immediately available.&lt;/p&gt;

&lt;p&gt;That distinction matters for UI confirmation, attacks, parries, rhythm input, and weapon fire. Preload or retain initial data for latency-sensitive sounds during loading, equipment changes, or level entry. Exact APIs vary by UE release and loading behavior.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Typical policy&lt;/th&gt;
&lt;th&gt;Validate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI and input feedback&lt;/td&gt;
&lt;td&gt;Preload&lt;/td&gt;
&lt;td&gt;Cold first-play latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frequent combat SFX&lt;/td&gt;
&lt;td&gt;Keep the beginning available&lt;/td&gt;
&lt;td&gt;Concurrency and decode cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rare SFX&lt;/td&gt;
&lt;td&gt;Cache-on-demand may be acceptable&lt;/td&gt;
&lt;td&gt;Burst I/O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Music&lt;/td&gt;
&lt;td&gt;Stream&lt;/td&gt;
&lt;td&gt;Loop, seek, pause, transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long dialogue&lt;/td&gt;
&lt;td&gt;Stream or split&lt;/td&gt;
&lt;td&gt;Subtitle sync, skip, language switching&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Middleware changes the tools, not the responsibility. The project still decides what loads, when it loads, and how long it stays resident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate concurrency, virtualization, and resource budgets
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt; resolves gameplay conflicts within a group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtualization&lt;/strong&gt; may preserve playback position while a sound is inaudible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource budgets&lt;/strong&gt; cover source voices, decoders, DSP, streaming I/O, mixer CPU, and buffers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sound Concurrency &lt;code&gt;Max Count&lt;/code&gt; is not the hardware or mixer voice limit. Active Sound Components can occupy a group even when they are not currently useful, and routing through a Source Bus can consume a slot for the original source and another for the bus path.&lt;/p&gt;

&lt;p&gt;Prioritize critical player feedback over repeated debris or distant footsteps. Give UI and dialogue their own policies. Virtualize loops only when timeline continuity matters, and do not assume virtualization removes concurrency, CPU, or memory cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audio Insights is runtime monitoring, not external live authoring
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/audio-insights-in-unreal-engine" rel="noopener noreferrer"&gt;Audio Insights&lt;/a&gt; monitors Sources, Audio Buses, Submixes, volume, pitch, parameters, and routing in PIE or standalone sessions. It is not the same workflow as changing content in Wwise Authoring or FMOD Studio and pushing that authoring-side edit into a running game.&lt;/p&gt;

&lt;p&gt;It is also not a complete CPU, memory, and I/O solution. Combine it with Timing Insights or &lt;code&gt;stat&lt;/code&gt;, Memory Insights or LLM, loading traces, console commands, and project logs. Compare how many steps are required to move from a target-device report to a verified fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wwise: large-team authoring and data governance
&lt;/h2&gt;

&lt;p&gt;Wwise combines Wwise Authoring, the Sound Engine, Unreal Integration, SoundBanks, Game Profiler, and automation such as WAAPI. Gameplay sends Events, RTPCs, Switches, and States; the audio side builds playback, routing, effects, and music behavior.&lt;/p&gt;

&lt;p&gt;It is a strong candidate for many contributors, outsourced content, large Event/state models, dedicated music tooling, SoundBank-based language or DLC boundaries, and automated data generation.&lt;/p&gt;

&lt;p&gt;The cost is another production system. The team must manage Unreal and Wwise projects, generate and validate SoundBanks, define Work Unit and naming rules, and design Bank boundaries early enough to protect memory, patching, and DLC.&lt;/p&gt;

&lt;p&gt;The Unreal Integration is implemented in C++, so production needs a C++ module and build toolchain even when much gameplay is Blueprint-based. This does not mean Wwise lacks Blueprint APIs.&lt;/p&gt;

&lt;p&gt;Public documentation checked on August 28, 2026 lists &lt;a href="https://www.audiokinetic.com/en/public-library/2025.1.10_9233/?id=releasenotes_2025_1_10.html&amp;amp;source=UE4" rel="noopener noreferrer"&gt;Wwise Unreal Integration 2025.1.10&lt;/a&gt; for UE 5.6 through 5.8, tested against 5.8.&lt;/p&gt;

&lt;p&gt;Licensing is regional. Audiokinetic's public game pricing page says the displayed prices apply to studios in North America, South America, Africa, Europe, and Australia. In those regions, the public Indie bracket covers production budgets up to USD 250,000. &lt;strong&gt;Do not automatically apply that threshold to Japan or another Asian country.&lt;/strong&gt; Confirm the legal entity, region, platforms, plug-ins, support, source access, registration, and credits with the regional office or a written quote.&lt;/p&gt;

&lt;h2&gt;
  
  
  FMOD Studio: timeline-based iteration
&lt;/h2&gt;

&lt;p&gt;FMOD Studio combines timeline-oriented authoring, runtime, Unreal Integration, Banks, Live Update, and profiling. It often fits teams that value DAW-like editing, parameter automation, Snapshots, and rapid music/SFX iteration.&lt;/p&gt;

&lt;p&gt;Events are built into Banks, but Bank metadata, non-streaming sample data, and streaming sample data are distinct. Loading a Bank does not necessarily make every waveform resident. Measure metadata, preloaded samples, stream buffers, and I/O independently, and design Bank boundaries early.&lt;/p&gt;

&lt;p&gt;Record the authoring tool, Core/Studio Runtime, and FMOD for Unreal Integration separately. Public documentation checked on August 28, 2026 identifies &lt;a href="https://www.fmod.com/docs/2.03/unreal/welcome.html" rel="noopener noreferrer"&gt;FMOD for Unreal Integration 2.03.14&lt;/a&gt; and lists UE 4.27 and UE 5.3 through 5.7. UE 5.8 is not in that public list. A source build for an unsupported engine version is not equivalent to official support.&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://www.fmod.com/licensing" rel="noopener noreferrer"&gt;FMOD pricing page&lt;/a&gt; places projects under USD 600,000 development budget in the Indie bracket. The &lt;a href="https://www.fmod.com/legal" rel="noopener noreferrer"&gt;FMOD EULA&lt;/a&gt; defines Free Indie more precisely: the developer's total gross revenue/funding per year before expenses must be under USD 200,000, the project budget must be under USD 600,000, the product must meet the permitted game-use conditions, the project must be registered, and attribution is required.&lt;/p&gt;

&lt;p&gt;A publisher or subcontracting arrangement may change the developer or license holder. Do not reduce the decision to profit or one game's revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRI ADX: dialogue scale, compression, and CRI-oriented workflows
&lt;/h2&gt;

&lt;p&gt;CRI ADX combines CRI Atom Craft, the ADX runtime, HCA-family codecs, interactive sound, category and playback-voice management, and profiling. Gameplay commonly addresses Cues and AISAC Controls while sound designers configure behavior and data in Atom Craft.&lt;/p&gt;

&lt;p&gt;It is a candidate for large dialogue volumes, dedicated compression and voice-management workflows, Atom Craft experience, CRIWARE video/lip-sync integration, or production support suited to a Japan-based team. Vendor performance claims still require target-device measurement with the project's material and settings.&lt;/p&gt;

&lt;p&gt;Public documentation checked on August 28, 2026 lists &lt;a href="https://game.criware.jp/manual/ue_pluginv2/latest/md__support_2_release_notes_22__05_22__05_release_notes_8_j_p_n.html" rel="noopener noreferrer"&gt;CRIWARE Unreal Engine Plugin 2.05.01&lt;/a&gt; for UE 5.4 through 5.7.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://game.criware.jp/products/adx-le/" rel="noopener noreferrer"&gt;CRI ADX LE&lt;/a&gt; has cumulative distribution conditions. The current public Japanese terms include prior-year annual sales of JPY 10 million or less for a company, the user being the distributor and sales-rights holder, and content sales of JPY 10 million or less. Required copyright and end-user license text must be presented, and the SDK must not be exposed in a public repository. Publisher, agency, related-company, and threshold-exceeding cases can require a commercial ADX agreement.&lt;/p&gt;

&lt;p&gt;Do not use this summary as legal approval. Confirm the bundled license and regional applicability, especially when the publishing structure is not simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility and license checks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Unreal integration or plug-in&lt;/th&gt;
&lt;th&gt;Version checked&lt;/th&gt;
&lt;th&gt;Public UE support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wwise Unreal Integration&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.audiokinetic.com/en/public-library/2025.1.10_9233/?id=releasenotes_2025_1_10.html&amp;amp;source=UE4" rel="noopener noreferrer"&gt;2025.1.10&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;UE 5.6–5.8; tested against 5.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FMOD for Unreal Integration&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.fmod.com/docs/2.03/unreal/welcome.html" rel="noopener noreferrer"&gt;2.03.14&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;UE 4.27 and 5.3–5.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRIWARE Unreal Engine Plugin&lt;/td&gt;
&lt;td&gt;&lt;a href="https://game.criware.jp/manual/ue_pluginv2/latest/md__support_2_release_notes_22__05_22__05_release_notes_8_j_p_n.html" rel="noopener noreferrer"&gt;2.05.01&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;UE 5.4–5.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are Unreal-facing versions, not generic authoring-tool versions. Record the authoring tool, runtime, exact UE patch, platform SDK, and console package separately.&lt;/p&gt;

&lt;p&gt;For every candidate, check the contracting entity, financial thresholds, distributor and sales-rights holder, platform/DLC scope, registration, logo and credits, EULA text, support, source access, and console terms. A pricing page is not the contract that governs the release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational comparison
&lt;/h2&gt;

&lt;p&gt;These tables describe workflow tendencies, not capability ceilings or sound-quality rankings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authoring and ownership
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Main production boundary&lt;/th&gt;
&lt;th&gt;Typical gameplay controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built-in UE audio&lt;/td&gt;
&lt;td&gt;Unreal Editor, UE assets, project APIs&lt;/td&gt;
&lt;td&gt;MetaSound inputs, Audio Parameters, Modulation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wwise&lt;/td&gt;
&lt;td&gt;Wwise Authoring, Events, SoundBanks, Profiler&lt;/td&gt;
&lt;td&gt;Events, RTPCs, Switches, States&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FMOD Studio&lt;/td&gt;
&lt;td&gt;FMOD Studio, Events, Banks, Mixer&lt;/td&gt;
&lt;td&gt;Events, Parameters, Snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRI ADX&lt;/td&gt;
&lt;td&gt;Atom Craft, Cues, CueSheets, Categories&lt;/td&gt;
&lt;td&gt;Cues, AISAC Controls, Categories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Data and iteration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Data/loading unit&lt;/th&gt;
&lt;th&gt;Adjustment and monitoring&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built-in UE audio&lt;/td&gt;
&lt;td&gt;UE assets, chunks, Pak/IoStore&lt;/td&gt;
&lt;td&gt;Unreal editing plus Audio/Unreal Insights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wwise&lt;/td&gt;
&lt;td&gt;SoundBanks&lt;/td&gt;
&lt;td&gt;Authoring connection and Game Profiler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FMOD Studio&lt;/td&gt;
&lt;td&gt;Banks; metadata/resident/stream data can be separated&lt;/td&gt;
&lt;td&gt;Live Update and FMOD Profiler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRI ADX&lt;/td&gt;
&lt;td&gt;CueSheets, ACB/AWB and related data&lt;/td&gt;
&lt;td&gt;In-game preview and Atom Insight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Upgrade risk
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Main release check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built-in UE audio&lt;/td&gt;
&lt;td&gt;Feature maturity, Cook, packaged-build and device regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wwise&lt;/td&gt;
&lt;td&gt;UE, SDK, Integration, platforms, Bank generation, regional license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FMOD Studio&lt;/td&gt;
&lt;td&gt;Integration, runtime, Studio, UE support, Bank generation, attribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRI ADX&lt;/td&gt;
&lt;td&gt;Plug-in, SDK, UE support, target platforms, CueSheet generation, contract tier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each option has concepts corresponding to playback, parameters, buses, 3D audio, and streaming. That does &lt;strong&gt;not&lt;/strong&gt; imply equal maturity, platform coverage, tooling, effort, or quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose by project risk, not genre labels alone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small or medium 3D action:&lt;/strong&gt; test cold input sounds, concurrency, weapon/vehicle loops, and indoor/outdoor transitions. Built-in UE audio is a useful baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dialogue-heavy RPG or adventure:&lt;/strong&gt; test the path from recorded files through language replacement, subtitles, lip-sync, skip, loading, DLC, and distribution. CRI ADX is a candidate, but a proven Wwise or FMOD pipeline may be safer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rhythm or interactive music:&lt;/strong&gt; Quartz can schedule audio, but judgment timing and latency compensation remain game-owned. Test low FPS, pause, seek, retry, suspend, and device changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large open world or multi-platform:&lt;/strong&gt; prioritize data partitioning, languages, patching, memory tiers, streaming budgets, and tool-supported diagnostics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newest UE until late production:&lt;/strong&gt; treat official integration support as a top-level requirement. Assign an owner for every unsupported source build, Shipping target, certification pass, and future update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid choosing by famous shipped titles, converting every sound to MetaSounds, leaving UE/middleware ownership undefined, postponing Bank or CueSheet partitioning, treating upgrades as simple editor plug-in updates, or selecting by perceived sound quality alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a vertical-slice proof of concept
&lt;/h2&gt;

&lt;p&gt;Use the same core tests for every candidate, then add the project's hardest cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required:&lt;/strong&gt; cold first playback, dense concurrency, parameter updates and stopping of a persistent sound, level-scoped loading/unloading, and CPU/memory/I/O on the lowest target device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add when relevant:&lt;/strong&gt; music transitions and recovery; long dialogue, subtitles, skip, lip-sync, and languages; attached 3D loops, occlusion, indoor/outdoor processing, and reverb; Bank/CueSheet/chunk partitioning, DLC, patching, and incremental delivery.&lt;/p&gt;

&lt;p&gt;Fix pass/fail criteria before testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target device and Shipping-equivalent settings;&lt;/li&gt;
&lt;li&gt;sample rate and audio buffer;&lt;/li&gt;
&lt;li&gt;steady CPU and acceptable spikes;&lt;/li&gt;
&lt;li&gt;resident memory and stream-cache budget;&lt;/li&gt;
&lt;li&gt;maximum cold input-to-sound latency;&lt;/li&gt;
&lt;li&gt;stress voice count and categories that must survive;&lt;/li&gt;
&lt;li&gt;Bank, CueSheet, or chunk load/unload time;&lt;/li&gt;
&lt;li&gt;target time from an audio edit to hardware verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First playback&lt;/td&gt;
&lt;td&gt;Is a cold input-coupled sound late?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU and spikes&lt;/td&gt;
&lt;td&gt;Do the content peak and data loads remain inside budget?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;Can the team explain compressed data, metadata, buffers, and resident sounds?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice policy&lt;/td&gt;
&lt;td&gt;Do important sounds survive congestion naturally?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iteration&lt;/td&gt;
&lt;td&gt;How many steps are required from edit to device confirmation?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build and diagnosis&lt;/td&gt;
&lt;td&gt;Can CI detect stale data, and can the owner explain a missing sound?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not stop at Editor preview. Use minimum-spec hardware and Shipping-equivalent builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the gameplay/audio boundary early
&lt;/h2&gt;

&lt;p&gt;Direct references from arbitrary Actors and Blueprints to &lt;code&gt;USoundBase&lt;/code&gt;, Wwise Events, FMOD Events, or Atom Cues spread Cook, loading, stopping, and migration rules across the game.&lt;/p&gt;

&lt;p&gt;A project-specific audio API should establish these rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gameplay requests use project IDs or Gameplay Tags, not vendor assets;&lt;/li&gt;
&lt;li&gt;2D/3D, one-shot/persistent, and prepare/play are separate contracts;&lt;/li&gt;
&lt;li&gt;an unloaded one-shot is not silently queued;&lt;/li&gt;
&lt;li&gt;persistent playback exposes parameter updates;&lt;/li&gt;
&lt;li&gt;World, owner, and attachment lifetime are separate from acoustic parameters;&lt;/li&gt;
&lt;li&gt;global state, listener management, music, and dialogue orchestration are separate services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This boundary can also centralize logs, Dedicated Server disabling, user settings, data-load validation, and mock testing.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Minimal interface and lifetime rules
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EAudioLifetimeScope&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uint8&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Owner&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FAudioLifetime&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EAudioLifetimeScope&lt;/span&gt; &lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EAudioLifetimeScope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;TWeakObjectPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FAudioHandleIdentity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;uint64&lt;/span&gt; &lt;span class="n"&gt;WorldKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;uint32&lt;/span&gt; &lt;span class="n"&gt;WorldGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;uint64&lt;/span&gt; &lt;span class="n"&gt;NonReusableSerial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Zero is invalid; never reused in-process.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FRequestHandle&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FAudioHandleIdentity&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FPlaybackHandle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FAudioHandleIdentity&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;FPrepareStartResult&lt;/span&gt; &lt;span class="nf"&gt;PrepareAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;WorldContextObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FAudioLifetime&lt;/span&gt; &lt;span class="n"&gt;Lifetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FGameplayTag&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FWeakPrepareObserver&lt;/span&gt; &lt;span class="n"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;EControlResult&lt;/span&gt; &lt;span class="nf"&gt;CancelPrepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;WorldContextObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FRequestHandle&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;FStartResult&lt;/span&gt; &lt;span class="nf"&gt;StartPersistentAttached&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="n"&gt;EControlResult&lt;/span&gt; &lt;span class="nf"&gt;UpdateParameters&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="n"&gt;EControlResult&lt;/span&gt; &lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="n"&gt;EControlResult&lt;/span&gt; &lt;span class="nf"&gt;ReleaseFinishedPlaybackHandle&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is design notation, not a compiled UE 5.8 implementation.&lt;/p&gt;

&lt;p&gt;Public calls run on the game thread. Resolve &lt;code&gt;WorldContextObject&lt;/code&gt; synchronously, return &lt;code&gt;InvalidWorld&lt;/code&gt; without issuing a handle on failure, and never store the raw pointer. Internal identity includes World key, World generation, and a process-wide non-reused serial so a stale handle cannot match new playback after finished-record cleanup.&lt;/p&gt;

&lt;p&gt;A started preparation reaches exactly one internal terminal state such as &lt;code&gt;Ready&lt;/code&gt;, &lt;code&gt;Failed&lt;/code&gt;, &lt;code&gt;Cancelled&lt;/code&gt;, or &lt;code&gt;Expired&lt;/code&gt;. A weak observer is notified at most once and only while alive. World-scoped lifetime ends at World cleanup; Owner-scoped lifetime requires a valid owner and expires when that owner is lost.&lt;/p&gt;

&lt;p&gt;Keep finished results in a bounded, expiring diagnostic structure. Release APIs should accept only finished playback. Parameter deltas distinguish Set and Clear. Specify missing-socket, attachment-destruction, detach, and lifetime-priority behavior. Make Cook references explicit with Primary Data Assets, &lt;code&gt;TSoftObjectPtr&lt;/code&gt;, or Asset Manager rules.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final selection flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Inventory existing people, assets, vendors, and automation.&lt;/li&gt;
&lt;li&gt;Decide which changes audio staff must make without programmers.&lt;/li&gt;
&lt;li&gt;Put candidates into the PoC based on the hardest problem: procedural audio, large-team governance, timeline iteration, dialogue scale, or platform support. These are heuristics, not exclusive capability categories.&lt;/li&gt;
&lt;li&gt;Verify every target and exact version.&lt;/li&gt;
&lt;li&gt;Measure edit-to-device time and failure-to-diagnosis time as well as CPU and memory.&lt;/li&gt;
&lt;li&gt;Assign long-term ownership for UE/SDK upgrades, data regeneration, licensing, and console support.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Built-in UE audio is a capable production foundation. Middleware earns its cost when it lets the actual team create, manage, profile, localize, and ship the game's audio workload more reliably than the studio's Unreal-only pipeline.&lt;/p&gt;

&lt;p&gt;Choose based on operators, project-specific risks, public integration support, target-device iteration, and post-launch ownership. The decisive test is not whether a demo sound plays; it is whether the team can keep changing the hardest audio safely until release.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/audio-in-unreal-engine-5" rel="noopener noreferrer"&gt;Epic Games: Audio in Unreal Engine 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/metasounds-the-next-generation-sound-sources-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games: MetaSounds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/metasounds-reference-guide-in-unreal-engine?application_version=5.8" rel="noopener noreferrer"&gt;Epic Games: MetaSounds Reference Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/overview-of-quartz-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games: Quartz Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/audio-modulation-overview-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games: Audio Modulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/an-overview-of-audio-stream-caching-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games: Audio Stream Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/sound-concurrency-reference-guide" rel="noopener noreferrer"&gt;Epic Games: Sound Concurrency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/audio-insights-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games: Audio Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.audiokinetic.com/en/public-library/2025.1.10_9233/?id=releasenotes_2025_1_10.html&amp;amp;source=UE4" rel="noopener noreferrer"&gt;Audiokinetic: Wwise Unreal Integration 2025.1.10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.audiokinetic.com/wwise/pricing/for-games/" rel="noopener noreferrer"&gt;Audiokinetic: Wwise Pricing for Games&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fmod.com/docs/2.03/unreal/welcome.html" rel="noopener noreferrer"&gt;FMOD: Welcome to FMOD for Unreal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fmod.com/docs/2.03/api/studio-api-bank.html" rel="noopener noreferrer"&gt;FMOD: Studio API Bank Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fmod.com/licensing" rel="noopener noreferrer"&gt;FMOD: Licensing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fmod.com/legal" rel="noopener noreferrer"&gt;FMOD: EULA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://game.criware.jp/products/adx/" rel="noopener noreferrer"&gt;CRIWARE: CRI ADX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://game.criware.jp/manual/ue_pluginv2/latest/index.html" rel="noopener noreferrer"&gt;CRIWARE: Unreal Engine Plugin Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://game.criware.jp/manual/ue_pluginv2/latest/md__support_2_release_notes_22__05_22__05_release_notes_8_j_p_n.html" rel="noopener noreferrer"&gt;CRIWARE: Unreal Engine Plugin 2.05.xx Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://game.criware.jp/products/adx-le/" rel="noopener noreferrer"&gt;CRIWARE: CRI ADX LE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This English adaptation was prepared with AI assistance and manually reviewed against the original Japanese article and the official sources linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>unrealengine</category>
      <category>ue5</category>
      <category>audio</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>5 UE 5.8 Plugins Worth Evaluating for a 3D Action Game</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Wed, 26 Aug 2026 10:11:32 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/5-ue-58-plugins-worth-evaluating-for-a-3d-action-game-2n8j</link>
      <guid>https://dev.to/gamedevtoollab/5-ue-58-plugins-worth-evaluating-for-a-3d-action-game-2n8j</guid>
      <description>&lt;p&gt;A third-person template gets a character moving quickly, but a real action game soon needs much more: mutually exclusive attack and dodge states, lock-on selection, attack alignment, input-mode switching, hit reactions, and readable enemy AI.&lt;/p&gt;

&lt;p&gt;Before buying an all-in-one combat framework from Fab, it is worth checking what Unreal Engine already ships with.&lt;/p&gt;

&lt;p&gt;This article focuses on five engine plugins:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enhanced Input&lt;/li&gt;
&lt;li&gt;Gameplay Ability System&lt;/li&gt;
&lt;li&gt;Motion Warping&lt;/li&gt;
&lt;li&gt;Gameplay Targeting System&lt;/li&gt;
&lt;li&gt;StateTree&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They do &lt;strong&gt;not&lt;/strong&gt; have the same maturity. Motion Warping is listed as Beta in UE 5.8; Gameplay Targeting System is also Beta and lives under an Experimental directory, so I treat it as a trial candidate. “Engine plugin” means the functionality ships with UE, not that every project should enable it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article uses UE 5.8 documentation as its reference point. Validate Beta or Experimental functionality against the exact engine version you plan to ship.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The five plugins at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Main use&lt;/th&gt;
&lt;th&gt;Main caution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enhanced Input&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Input actions, remapping, context switching&lt;/td&gt;
&lt;td&gt;Enabled by default; architecture matters more than the checkbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gameplay Ability System&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Actions, attributes, costs, cooldowns, status&lt;/td&gt;
&lt;td&gt;High learning cost; excessive for some small games&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion Warping&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Melee alignment, finishers, traversal&lt;/td&gt;
&lt;td&gt;Beta in UE 5.8; requires Root Motion and Montage validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gameplay Targeting System&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;Lock-on queries, filtering, sorting&lt;/td&gt;
&lt;td&gt;Beta in UE 5.8 and stored under an Experimental plugin path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;StateTree&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Hierarchical AI and state transitions&lt;/td&gt;
&lt;td&gt;Define which system owns authoritative gameplay state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Marketplace frameworks accelerate prototypes, but vendor-specific combat architecture can become expensive when the engine, networking, or requirements change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the game-specific combat yourself, but do not rebuild generic engine infrastructure that Epic already provides.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Enhanced Input: the default foundation
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/enhanced-input-in-unreal-engine" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/enhanced-input-in-unreal-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enhanced Input is enabled by default in UE5. The important question is how to divide Input Actions and Input Mapping Contexts.&lt;/p&gt;

&lt;p&gt;Its main building blocks are Input Action, Input Mapping Context, Input Modifier, and Input Trigger. The most important architectural tool is the Mapping Context.&lt;/p&gt;

&lt;p&gt;Without clear contexts, input logic tends to accumulate inside the Character:&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bIsAiming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Aiming input&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bIsInMenu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Menu input&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Normal input&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enhanced Input lets the project switch the active mapping instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMC_Common
  Pause

IMC_OnFoot
  Move / Look / Jump / Attack / Dodge / LockOn

IMC_Aiming
  Move / Look / Shoot / CancelAim

IMC_Menu
  Navigate / Confirm / Cancel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scales better as the game adds lock-on, ranged aiming, conversations, vehicles, or menus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Name actions by meaning, not by button
&lt;/h3&gt;

&lt;p&gt;Avoid &lt;code&gt;IA_R1&lt;/code&gt; or &lt;code&gt;IA_LeftMouse&lt;/code&gt;. Prefer &lt;code&gt;IA_Attack&lt;/code&gt;, &lt;code&gt;IA_Dodge&lt;/code&gt;, and &lt;code&gt;IA_Interact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Game code should receive “Attack was requested,” not “R1 was pressed,” keeping device layout and remapping out of combat logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let triggers interpret physical input
&lt;/h3&gt;

&lt;p&gt;Action games distinguish tap, hold, release, and chorded input. Do not reproduce all of that by measuring time in Character Tick. Enhanced Input triggers such as Hold, Tap, and Chord are better suited to physical input interpretation.&lt;/p&gt;

&lt;p&gt;However, Enhanced Input should not decide whether the character has enough stamina or whether &lt;code&gt;State.Stunned&lt;/code&gt; blocks an attack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Physical input
    ↓
Enhanced Input creates a semantic action
    ↓
Combat system or GAS decides whether it can execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep the combat input buffer separate
&lt;/h3&gt;

&lt;p&gt;A responsive action game often buffers the next attack during recovery. Enhanced Input should report the input; the combat layer should store something like &lt;code&gt;BufferedAction&lt;/code&gt; and consume it when an Animation Notify or Ability opens the window. This also helps AI commands, replay input, and tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check migrated projects
&lt;/h3&gt;

&lt;p&gt;For older UE5 projects or UE4 migrations, verify the Default Classes, Local Player Mapping Context registration, and &lt;code&gt;Config/DefaultInput.ini&lt;/code&gt; instead of assuming template defaults. During PIE, &lt;code&gt;showdebug enhancedinput&lt;/code&gt; is a useful first check when an action does not fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Gameplay Ability System: valuable when combat grows
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gameplay Ability System, or GAS, can look like an RPG spell framework. It is also a strong fit for many 3D action games.&lt;/p&gt;

&lt;p&gt;Typical candidates include attacks, dodge, guard, parry, stamina costs, cooldowns, buffs, poison, stun, invulnerability, and super armor.&lt;/p&gt;

&lt;p&gt;A simplified view is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AbilitySystemComponent
  ├ Gameplay Ability
  ├ Gameplay Effect
  ├ Attribute
  ├ Gameplay Tag
  └ Gameplay Cue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An Ability represents an action such as &lt;code&gt;GA_LightAttack&lt;/code&gt;. An Effect represents damage, poison, or a stamina cost. Attributes represent values such as Health and Stamina.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gameplay Tags reduce scattered boolean logic
&lt;/h3&gt;

&lt;p&gt;A handmade combat system often starts with:&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="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;bIsAttacking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;bIsDodging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;bIsStunned&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;bIsInvincible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, every action checks a different combination of those values.&lt;/p&gt;

&lt;p&gt;Gameplay Tags allow a more composable model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State.Attacking
State.Dodging
State.Stunned
State.Dead
Status.Invincible
Status.SuperArmor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An Ability can declare which tags block it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GA_Dodge
  Blocked Tags
    State.Stunned
    State.Dead
    State.Dodging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit is a consistent place to answer, “Why was this action rejected?” Tags also handle simultaneous state better than one large enum.&lt;/p&gt;

&lt;h3&gt;
  
  
  GAS has a real learning cost
&lt;/h3&gt;

&lt;p&gt;GAS introduces AbilitySystemComponent, GameplayEffect, AttributeSet, GameplayCue, AbilityTask, prediction, and replication. A small offline game with a few attacks may be faster with a lightweight combat component. GAS is easier to justify with many skills, equipment-driven attributes, status effects, cooldowns, networking, or long-term character growth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking still needs an authority design
&lt;/h3&gt;

&lt;p&gt;GAS supports network execution and client prediction, but it does not decide your security boundaries.&lt;/p&gt;

&lt;p&gt;The team still chooses a Gameplay Net Execution Policy and what may be predicted. A responsive action may use Local Predicted execution, while the final target, hit, and damage normally remain server-authoritative. Distance and line-of-sight checks still require explicit design. Lyra is useful for studying Ability granting, input, equipment, Effects, and networking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not turn everything into an Ability
&lt;/h3&gt;

&lt;p&gt;GAS is strongest for actions with activation conditions, a lifetime, cancellation, costs, Effects, or Tags. Attacks, dodge, skills, and status effects fit naturally; walking, UI, saves, and camera orbit usually do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Motion Warping: high impact for melee combat
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/motion-warping-in-unreal-engine" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/motion-warping-in-unreal-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suppose an attack animation moves the character forward 1.3 meters. If the enemy is 1.8 meters away, the attack stops short. If the enemy is 0.8 meters away, the character penetrates the target.&lt;/p&gt;

&lt;p&gt;Repeatedly calling &lt;code&gt;SetActorLocation&lt;/code&gt; can fight Root Motion and produce sliding or snapping. Motion Warping adjusts Root Motion during a selected animation window so that the motion better matches a target transform.&lt;/p&gt;

&lt;p&gt;In UE 5.8, Motion Warping is listed as Beta. A production team should test Montage behavior, Root Motion, networking, and packaged builds on the exact engine version it will ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical uses
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;melee approach&lt;/li&gt;
&lt;li&gt;finishers and executions&lt;/li&gt;
&lt;li&gt;a counterattack after parry&lt;/li&gt;
&lt;li&gt;vault and mantle alignment&lt;/li&gt;
&lt;li&gt;positioning at a door, lever, or chest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is strongest when valid animation must adapt to a runtime target position.&lt;/p&gt;

&lt;h3&gt;
  
  
  The implementation has linked pieces
&lt;/h3&gt;

&lt;p&gt;A typical setup adds a Motion Warping Component to the Character, places a Motion Warping Anim Notify State in a Montage, assigns a Warp Target Name, updates the same target name on the component, and then plays the Montage. Root Motion, the Notify window, the component, and the name must all agree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target Transform → Warp Target → Matching Montage Notify window
                 → Root Motion correction inside that window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Do not magnetize every attack
&lt;/h3&gt;

&lt;p&gt;Aggressive warping can make the attacker curve unnaturally, follow an enemy who dodged, or disagree with hit validation.&lt;/p&gt;

&lt;p&gt;A practical policy might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal attack   no correction or weak correction
Combo opener    small distance correction
Finisher        stronger correction
Execution       strict position and rotation alignment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Motion Warping is not a guaranteed-hit system. It absorbs the mismatch between authored animation and runtime geometry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decide whether a moving target is fixed or followed
&lt;/h3&gt;

&lt;p&gt;For a moving enemy, choose a transform captured at attack start or a followed Scene Component. Fixed targets are predictable; following can suit executions. If the target dies or disappears, define whether to cancel, stop warping, keep the last transform, or remove the target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep movement correction separate from hit detection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Motion Warping  → visual and movement correction
Trace/Collision → hit detection
GAS/Combat      → gameplay result and damage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Online, the client may predict some input and Ability behavior, but the server should normally validate the target, distance, obstruction, hit, and damage. A good-looking client animation is not proof that the hit was legal.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Gameplay Targeting System: useful when lock-on becomes a subsystem
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/gameplay-targeting-system-in-unreal-engine" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/gameplay-targeting-system-in-unreal-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A first lock-on may be a sphere overlap followed by “choose the nearest enemy.” Production requirements add visibility, occlusion, screen-center priority, dead-target rejection, stick-direction switching, boss parts, ally targeting, and multi-target attacks. Lock-on then becomes a query pipeline.&lt;/p&gt;

&lt;p&gt;Gameplay Targeting System provides a data-driven framework for gathering candidates, filtering them, sorting them, and returning results. It can extend GAS, but it can also run without GAS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Targeting Presets form a reusable pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Targeting Preset
  1. Gather candidates with Trace or AOE
  2. Filter by Actor class
  3. Apply game-specific filters
  4. Sort by distance, screen angle, or priority
  5. Return the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids copying similar search logic into lock-on, homing attacks, and area abilities.&lt;/p&gt;

&lt;p&gt;The system supports immediate and asynchronous requests, but async does not make an expensive query free. Search radius, frequency, candidate count, traces, and sorting still matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat it as a trial in UE 5.8
&lt;/h3&gt;

&lt;p&gt;The UE 5.8 Plugin Index labels Targeting System as Beta, while its plugin file is under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Engine/Plugins/Experimental/GameplayTargetingSystem/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluate API stability, packaging, debugging, and upgrade cost before making it a core dependency.&lt;/p&gt;

&lt;p&gt;For a small lock-on system, direct project code may remain easier to understand. The following is only pseudocode, not production-ready C++:&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;TArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AActor&lt;/span&gt;&lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FindCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;FilterVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;SortByScreenCenter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;AActor&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CurrentTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real code also needs validity, death, team, occlusion, destroyed-Actor handling, and server-side revalidation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The nearest enemy is not always the best target
&lt;/h3&gt;

&lt;p&gt;A score can combine multiple signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Score =
  ScreenCenterWeight * 0.50
+ DistanceWeight     * 0.25
+ InputDirection     * 0.20
+ PriorityBonus      * 0.05
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values are game-specific; the important decision is separating candidate gathering from evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. StateTree: explicit hierarchical state for enemy AI
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/state-tree-in-unreal-engine" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/state-tree-in-unreal-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;StateTree combines ideas from behavior trees and state machines. It is useful when the central question is, “Which state is this entity in, and what causes a transition?”&lt;/p&gt;

&lt;p&gt;A typical action-game enemy might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Idle
Patrol
Alert
Combat
  Approach
  Strafe
  Attack
  Retreat
Stunned
Dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementing this entirely with Character Tick, an enum, and Blueprint branches becomes difficult as transitions grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  It does not replace every Behavior Tree
&lt;/h3&gt;

&lt;p&gt;Behavior Trees remain strong for continuous condition evaluation and tactical selection. StateTree is often easier to read when explicit modes, phases, and transitions dominate. Environment queries may still fit Behavior Tree and EQS better; do not force one tool onto every AI problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan interruption priority
&lt;/h3&gt;

&lt;p&gt;Action enemies are interrupted constantly. Define the hierarchy before building many transitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dead       highest priority
Stunned
Knockback
Attack
Movement
Idle       lowest priority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool visualizes the structure, but it cannot decide the gameplay priority for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep StateTree and GAS from competing
&lt;/h3&gt;

&lt;p&gt;Both can represent “state,” so give them different responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;StateTree
  What the AI intends to do
  Approach / Attack / Retreat

GAS and Gameplay Tags
  What gameplay rules currently allow
  Stunned / Invincible / Dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;StateTree can request &lt;code&gt;GA_EnemyAttack&lt;/code&gt;, while GAS rejects it because &lt;code&gt;State.Stunned&lt;/code&gt; is active. This separates decision-making from combat authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: study the Game Animation Sample Project
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/game-animation-sample-project-in-unreal-engine" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/game-animation-sample-project-in-unreal-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Game Animation Sample Project, or GASP, is &lt;strong&gt;not a plugin&lt;/strong&gt;. It is an official sample project available through Fab, but it is still worth studying before committing to a locomotion architecture.&lt;/p&gt;

&lt;p&gt;GASP demonstrates Motion Matching, locomotion organization, traversal, Animation Blueprint responsibilities, and retargeting. Study the boundary between animation and gameplay rather than copying it wholesale. ALS variants still make sense when a team already has relevant assets and expertise, but new projects should compare them with Epic's current animation stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the five plugins fit together
&lt;/h2&gt;

&lt;p&gt;A player attack can be divided cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enhanced Input
  Detect IA_Attack
      ↓
GAS
  Validate tags, cost, stamina, and activation
      ↓
Targeting System
  Select a target when required
      ↓
Motion Warping
  Correct the approach window in the Montage
      ↓
Animation and Hit Detection
  Play animation and perform the trace
      ↓
GAS or Combat Layer
  Apply the authoritative result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For enemies, StateTree chooses intent and GAS validates whether the requested action is legal. Input, combat rules, target selection, animation correction, and AI intent remain replaceable subsystems rather than one monolithic framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical adoption order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Input:&lt;/strong&gt; establish naming and Mapping Contexts at project start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion Warping:&lt;/strong&gt; test it with the first melee attack before dozens of Montages exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GAS:&lt;/strong&gt; decide during the vertical slice, before health, damage, buffs, and attack state multiply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targeting System:&lt;/strong&gt; consider it when gathering, filtering, and sorting rules are reused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StateTree:&lt;/strong&gt; evaluate it before the enemy roster expands and shared behavior becomes difficult to manage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not enable everything without recording ownership and a fallback:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Fallback&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enhanced Input&lt;/td&gt;
&lt;td&gt;Adopt&lt;/td&gt;
&lt;td&gt;Player team&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GAS&lt;/td&gt;
&lt;td&gt;Adopt if complexity justifies it&lt;/td&gt;
&lt;td&gt;Combat team&lt;/td&gt;
&lt;td&gt;Custom ability layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion Warping&lt;/td&gt;
&lt;td&gt;Adopt after validation&lt;/td&gt;
&lt;td&gt;Animation team&lt;/td&gt;
&lt;td&gt;Manual correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeting System&lt;/td&gt;
&lt;td&gt;Trial&lt;/td&gt;
&lt;td&gt;Combat team&lt;/td&gt;
&lt;td&gt;Project query code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;StateTree&lt;/td&gt;
&lt;td&gt;Adopt when AI benefits&lt;/td&gt;
&lt;td&gt;AI team&lt;/td&gt;
&lt;td&gt;BT or custom FSM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Making the plugin the game design
&lt;/h3&gt;

&lt;p&gt;GAS does not mean every behavior must be an Ability. StateTree does not mean every state belongs in one tree. Motion Warping does not mean every attack should track the target.&lt;/p&gt;

&lt;p&gt;Frameworks cannot choose input windows, cancel timing, hit stop, camera behavior, or enemy reactions. A clean architecture still feels bad if an attack begins too late after the button press.&lt;/p&gt;

&lt;h3&gt;
  
  
  Turning Blueprint versus C++ into a rule of faith
&lt;/h3&gt;

&lt;p&gt;A practical split is often:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C++
  Base classes, shared rules, high-frequency or network-critical logic

Blueprint / Data Assets
  Parameters, Montage references, Effects, Targeting Presets, AI tuning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose based on ownership and change frequency, not ideology.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Animation Blueprint authoritative for gameplay
&lt;/h3&gt;

&lt;p&gt;Animation can open a hit window through a Notify. It should not become the only source of damage, invulnerability, or server authority.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Animation Notify
  ↓ Hit window opened
Combat Component or Ability
  ↓ Trace
Damage rule or Gameplay Effect
  ↓
Target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This direction is easier to test and survives animation replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one vertical slice
&lt;/h2&gt;

&lt;p&gt;Before building a large framework, make one enemy interaction feel good:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Light Attack through Enhanced Input&lt;/li&gt;
&lt;li&gt;Attack Montage playback&lt;/li&gt;
&lt;li&gt;Tag or lightweight state prevents duplicate attacks&lt;/li&gt;
&lt;li&gt;Trace-based hit detection&lt;/li&gt;
&lt;li&gt;Motion Warping adjusts the approach&lt;/li&gt;
&lt;li&gt;Enemy hit reaction&lt;/li&gt;
&lt;li&gt;Lock-on&lt;/li&gt;
&lt;li&gt;Dodge and cancel rules&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After this slice proves the desired feel, expand into GAS, Targeting System, and StateTree as required.&lt;/p&gt;

&lt;h2&gt;
  
  
  If I could choose only three
&lt;/h2&gt;

&lt;p&gt;For a small offline action game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enhanced Input
Motion Warping
StateTree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a medium-to-large or online game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enhanced Input
Motion Warping
Gameplay Ability System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes the vertical slice has already validated the controls. It is not a recommendation to fully adopt GAS on the first day of every prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Gameplay Cameras?
&lt;/h2&gt;

&lt;p&gt;Official documentation: &lt;a href="https://dev.epicgames.com/documentation/unreal-engine/gameplay-camera-system-overview" rel="noopener noreferrer"&gt;https://dev.epicgames.com/documentation/unreal-engine/gameplay-camera-system-overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gameplay Camera System can build complex Camera Rigs and transitions as data, which is highly relevant to 3D action games. However, UE 5.8 documentation describes it as Experimental. I would evaluate it separately for a game with many camera modes rather than placing it in the core five.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checks that matter
&lt;/h2&gt;

&lt;p&gt;Keep dependencies flowing in one direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input
  ↓
Player or AI command
  ↓
Gameplay rules (GAS / Combat)
  ↓
Requests to animation, movement, and targeting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluate debugging, not only features. “Dodge did not happen” may mean the action failed, the wrong context was active, a Tag blocked the Ability, stamina was insufficient, or the Montage failed. Expose the latest input, active Tags and Abilities, target, and StateTree state in development builds.&lt;/p&gt;

&lt;p&gt;Measure real workload with Unreal Insights. Wide overlaps, traces, projection, and sorting every Tick are expensive with or without a framework. When upgrading UE, regression-test plugin maturity, Root Motion, Ability replication, target sorting, StateTree bindings, Mapping Context order, and packaged dependencies. Keep small repeatable checks for attack, dodge, lock-on, stun, death, and a networked hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The goal is not to maximize the number of enabled plugins. The goal is to keep generic infrastructure from leaking into game-specific code.&lt;/p&gt;

&lt;p&gt;Enhanced Input separates physical controls from semantic actions. GAS organizes actions, states, costs, and Effects. Motion Warping absorbs the difference between authored motion and runtime target positions. Gameplay Targeting System turns selection into a reusable query pipeline. StateTree makes hierarchical AI intent easier to inspect.&lt;/p&gt;

&lt;p&gt;You do not need all five. In UE 5.8, Motion Warping and Gameplay Targeting System also require explicit production validation because they are listed as Beta, with Targeting System located under an Experimental path.&lt;/p&gt;

&lt;p&gt;My default evaluation order is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enhanced Input
    ↓
Motion Warping
    ↓
GAS or StateTree
    ↓
Gameplay Targeting System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Study GASP before committing to locomotion. Epic's current animation stack helps you choose only what the game needs instead of accumulating legacy “standard” solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/enhanced-input-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Enhanced Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/understanding-the-unreal-engine-gameplay-ability-system" rel="noopener noreferrer"&gt;Epic Games, Gameplay Ability System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/using-gameplay-abilities-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Using Gameplay Abilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/abilities-in-lyra-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Abilities in Lyra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/lyra-sample-game-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Lyra Sample Game&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/motion-warping-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Motion Warping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/PluginIndex/MotionWarping" rel="noopener noreferrer"&gt;Epic Games, Motion Warping Plugin Index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/Plugins/MotionWarping/UMotionWarpingComponent" rel="noopener noreferrer"&gt;Epic Games, UMotionWarpingComponent API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/gameplay-targeting-system-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Gameplay Targeting System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/PluginIndex/TargetingSystem" rel="noopener noreferrer"&gt;Epic Games, Targeting System Plugin Index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/state-tree-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, StateTree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/game-animation-sample-project-in-unreal-engine" rel="noopener noreferrer"&gt;Epic Games, Game Animation Sample Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/gameplay-camera-system-overview" rel="noopener noreferrer"&gt;Epic Games, Gameplay Camera System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/PluginIndex/GameplayCameras" rel="noopener noreferrer"&gt;Epic Games, Gameplay Cameras Plugin Index&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unrealengine</category>
      <category>ue5</category>
      <category>gamedev</category>
      <category>gameanimation</category>
    </item>
    <item>
      <title>Practical UE5 File Loading Optimization: Beyond Parallel I/O and Raising the FPS Cap</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:15:26 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/practical-ue5-file-loading-optimization-beyond-parallel-io-and-raising-the-fps-cap-5g6n</link>
      <guid>https://dev.to/gamedevtoollab/practical-ue5-file-loading-optimization-beyond-parallel-io-and-raising-the-fps-cap-5g6n</guid>
      <description>&lt;p&gt;UE5 loading optimization is often reduced to two ideas: read more files in parallel, and raise the FPS cap during a loading screen. Both can help, but only when they target the real bottleneck.&lt;/p&gt;

&lt;p&gt;A transition also includes dependency discovery, decompression, UObject creation, &lt;code&gt;Serialize&lt;/code&gt; and &lt;code&gt;PostLoad&lt;/code&gt;, activation, collision, texture/audio streaming, PSOs, and GC. If activation dominates the Game Thread, more parallel reads may only create a larger completion spike.&lt;/p&gt;

&lt;p&gt;This article targets UE 5.8 as of August 2026. Use this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measure I/O and post-load work separately.&lt;/li&gt;
&lt;li&gt;Reduce hard-reference graphs and requested data.&lt;/li&gt;
&lt;li&gt;Preload by player-experience phase.&lt;/li&gt;
&lt;li&gt;Change loading budgets during controlled transitions instead of blindly unlocking FPS.&lt;/li&gt;
&lt;li&gt;Tune containers, compression, streaming, and activation on target hardware.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read by symptom&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow startup: reference graphs, Asset Manager, IoStore.&lt;/li&gt;
&lt;li&gt;Teleport hitch: World Partition sources, texture mips, PSOs.&lt;/li&gt;
&lt;li&gt;Freeze after the loading screen: activation, overlap, PSOs.&lt;/li&gt;
&lt;li&gt;Slow custom JSON or binary data: split Read, Decode, and Apply.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Decompose load time first
&lt;/h2&gt;

&lt;p&gt;Treat loading as a pipeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Typical work&lt;/th&gt;
&lt;th&gt;Common bottleneck&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discover&lt;/td&gt;
&lt;td&gt;Asset Registry, Primary Assets, dependencies&lt;/td&gt;
&lt;td&gt;Large search scope or graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Container or file reads&lt;/td&gt;
&lt;td&gt;Random I/O, many small requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode&lt;/td&gt;
&lt;td&gt;Decryption, Oodle decompression&lt;/td&gt;
&lt;td&gt;CPU saturation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deserialize&lt;/td&gt;
&lt;td&gt;UObject creation, &lt;code&gt;Serialize&lt;/code&gt;, &lt;code&gt;PostLoad&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Custom work, nested sync loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activate&lt;/td&gt;
&lt;td&gt;Actor spawn, Component registration, render setup&lt;/td&gt;
&lt;td&gt;Game Thread, collision, PSOs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If Read takes 0.4 seconds and Activate takes 2.0 seconds, more I/O concurrency cannot remove most of the wait. It may make more packages finish together and worsen the activation spike.&lt;/p&gt;

&lt;p&gt;Record median and p95 transition time, worst Game Thread frame, bytes and request count, decode CPU, activation time, and peak memory. Separate cold and warm-cache runs. Use cooked packaged builds on target devices for final decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate loading from waiting with Unreal Insights
&lt;/h2&gt;

&lt;p&gt;In UE 5.8, &lt;code&gt;AssetLoadTime&lt;/code&gt; exposes timing around asset serialization, while &lt;code&gt;LoadTime&lt;/code&gt; covers runtime loading from pak or IoStore paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-trace=default,AssetLoadTime,LoadTime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use Editor or PIE for reference investigation, then compare a Shipping-equivalent packaged build with the same channels, device, and cache conditions. Add &lt;code&gt;-statnamedevents&lt;/code&gt; only when Blueprint names are necessary, and place bookmarks around the transition.&lt;/p&gt;

&lt;p&gt;Check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I/O or Async Loading Thread starvation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PostLoad&lt;/code&gt;, Actor creation, or registration spikes.&lt;/li&gt;
&lt;li&gt;Duplicate requests or urgent work buried under broad preloads.&lt;/li&gt;
&lt;li&gt;Texture or PSO hitches after package loading completes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Focused Zen Loader logging can help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-LogCmds="LogStreaming veryverbose"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not keep VeryVerbose logging in normal profiling builds.&lt;/p&gt;

&lt;p&gt;Avoid polling &lt;code&gt;GetAsyncLoadPercentage&lt;/code&gt; every frame. Epic warns that it is slow and may block async loading. Prefer load groups, handle callbacks, and monotonic phase-based progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  The largest optimization is not loading the asset
&lt;/h2&gt;

&lt;p&gt;Hard references such as &lt;code&gt;UTexture2D*&lt;/code&gt;, &lt;code&gt;USkeletalMesh*&lt;/code&gt;, and Blueprint classes pull dependency graphs into memory. A persistent “all characters” registry can make the title screen load meshes, materials, animation, audio, and VFX. Inspect inclusive size, dependency direction, and cooked chunks with Reference Viewer, Size Map, and Asset Audit.&lt;/p&gt;

&lt;p&gt;When owner and target lifetimes differ, use soft references:&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;UPROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TSoftObjectPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;USkeletalMesh&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CharacterMesh&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;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TSoftClassPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CharacterClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A soft reference does not load its target automatically. Request it through Streamable Manager or Asset Manager when its phase begins.&lt;/p&gt;

&lt;p&gt;Changing the type does &lt;strong&gt;not&lt;/strong&gt; guarantee cooking. The Cooker still needs a discoverable route through a serialized &lt;code&gt;UPROPERTY&lt;/code&gt;, Asset Bundle, Primary Asset rule, &lt;code&gt;PrimaryAssetLabel&lt;/code&gt;, explicit Cook Rule, or similar mechanism. Runtime-generated soft paths need separate cook registration. Verify final chunk membership in a packaged build.&lt;/p&gt;

&lt;p&gt;Keep hard references when assets are always required together. Otherwise inspect &lt;code&gt;ConstructorHelpers&lt;/code&gt;, Blueprint types and casts, class defaults, and distant World Partition Actor links. Interfaces, IDs, Gameplay Tags, events, or subsystem queries can avoid loading a concrete class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load by experience phase with Asset Manager
&lt;/h2&gt;

&lt;p&gt;Individual soft references become difficult to own and release consistently. Centralize policy around Asset Manager and divide Secondary Assets into named Asset Bundles:&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;BlueprintType&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;UCharacterDefinition&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;UPrimaryDataAsset&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="nl"&gt;public:&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;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AssetBundles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Menu"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;TSoftObjectPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UTexture2D&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Portrait&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;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AssetBundles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Gameplay"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;TSoftObjectPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;USkeletalMesh&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Mesh&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;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AssetBundles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Gameplay"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;TSoftClassPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CharacterClass&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;EditDefaultsOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AssetBundles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Voice"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;TArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TSoftObjectPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;USoundBase&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;VoiceAssets&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;Load &lt;code&gt;Menu&lt;/code&gt; during selection, &lt;code&gt;Gameplay&lt;/code&gt; after confirmation, and &lt;code&gt;Voice&lt;/code&gt; before dialogue. Bundle by &lt;strong&gt;player-experience phase&lt;/strong&gt;, not only file type.&lt;/p&gt;

&lt;p&gt;This UE 5.8 excerpt shows ownership and request replacement for one character. It omits declarations, logging, retries, and multi-consumer counting; older versions may use earlier overloads.&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;UStageLoadSubsystem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LoadCharacter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FPrimaryAssetId&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ReleaseCharacter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;FAssetManagerLoadParams&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FStreamableManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DefaultAsyncLoadPriority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnComplete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FStreamableDelegateWithHandle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CreateWeakLambda&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;TSharedPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FStreamableHandle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UAssetManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPrimaryAssetObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;OnCharacterReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Asset&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;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FName&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bundles&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FName&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="s"&gt;"Gameplay"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;CharacterHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UAssetManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;LoadPrimaryAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bundles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MoveTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Params&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;UStageLoadSubsystem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ReleaseCharacter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CharacterHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;CharacterHandle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;HasLoadCompleted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CharacterHandle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;CancelHandle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;CharacterHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;UAssetManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;UnloadPrimaryAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;LoadedCharacterId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FPrimaryAssetId&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;Production code still needs request generations, consumer counts, and separate failure and cancellation signals.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Lifetime model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LoadPrimaryAsset(s)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retained until &lt;code&gt;UnloadPrimaryAsset(s)&lt;/code&gt;. The handle controls progress, waiting, cancellation, and callbacks, not asset lifetime.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreloadPrimaryAssets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Valid while its handle is retained; releasable afterward if unreferenced.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct Streamable Manager request&lt;/td&gt;
&lt;td&gt;A retained handle or hard reference owns the useful lifetime.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;LoadPrimaryAsset(s)&lt;/code&gt; may return a null handle when no new work is required while still invoking completion. Validate the object in &lt;code&gt;OnComplete&lt;/code&gt;, keep failure/cancellation/replacement distinct, and put release under one owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parallel loading is not “the more, the faster”
&lt;/h2&gt;

&lt;p&gt;Unlimited concurrency can create small random reads, decode contention, registration bursts, high peak memory, and urgent-work starvation. Group requests by player need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minimum: map, player, required UI.&lt;/li&gt;
&lt;li&gt;Immediate: nearby assets, first enemies, essential SFX.&lt;/li&gt;
&lt;li&gt;Later: next area, additional enemies, dialogue voice.&lt;/li&gt;
&lt;li&gt;Optional: distant high mips and cosmetics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only the minimum set should receive high priority. Share in-flight handles and track consumers to prevent duplicate requests and premature unloads.&lt;/p&gt;

&lt;p&gt;Cancellation does not rewind completed work. Treat request cancellation, handle release, and UObject-reference removal as separate operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not represent readiness with one boolean
&lt;/h2&gt;

&lt;p&gt;An async callback rarely means gameplay is safe to start. Track at least:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Requested&lt;/li&gt;
&lt;li&gt;AssetsReady&lt;/li&gt;
&lt;li&gt;WorldReady&lt;/li&gt;
&lt;li&gt;RuntimeReady&lt;/li&gt;
&lt;li&gt;PresentationReady&lt;/li&gt;
&lt;li&gt;Interactive&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After a handle completes, spawning, collision, UI, network synchronization, texture/audio readiness, and PSOs may remain. Aggregate completion from the owning subsystems.&lt;/p&gt;

&lt;p&gt;Use measured phase weights for monotonic display progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AssetsReady       35%
WorldReady        25%
RuntimeReady      20%
PresentationReady 15%
Interactive        5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep display progress separate from the internal state machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a higher FPS cap make loading faster?
&lt;/h2&gt;

&lt;p&gt;Sometimes. Streaming and Actor/Component registration include per-frame budgeted work. Raising the FPS cap may apply those budgets more often per second.&lt;/p&gt;

&lt;p&gt;It will not help when storage, decompression CPU, or one long Game Thread task is saturated. It may hurt when rendering, animation, UI, or unrelated Tick consumes the extra frames. Treat &lt;code&gt;t.MaxFPS 0&lt;/code&gt; as a diagnostic experiment, not a permanent strategy.&lt;/p&gt;

&lt;p&gt;During gameplay, stable frame time matters. During a controlled transition, total completion time may matter more:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example setting&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.AsyncLoadingTimeLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-frame time for async loading&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.PriorityAsyncLoadingExtraTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extra time for priority loading&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.LevelStreamingActorsUpdateTimeLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time for streamed Actor updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.PriorityLevelStreamingActorsUpdateExtraTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extra priority streaming time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.LevelStreamingComponentsRegistrationGranularity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registration batch size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.UnregisterComponentsTimeLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time for unregister work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Names and behavior vary by version and platform. Check &lt;code&gt;VariableName ?&lt;/code&gt;, Engine Source, Device Profiles, and current values. Centralize changes and restore prior values on success, cancellation, error, or map transition. Use &lt;strong&gt;Gameplay&lt;/strong&gt;, &lt;strong&gt;Transition&lt;/strong&gt;, and &lt;strong&gt;Tail&lt;/strong&gt; profiles; wait only for the mandatory remainder in Tail.&lt;/p&gt;

&lt;p&gt;Keep the loading screen cheap: heavy 3D scenes, Niagara, blur, Scene Capture, animated UI, and unnecessary Tick compete with loading. Preserve required render preparation or the hitch moves to the first interactive frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid routine synchronous flushes
&lt;/h2&gt;

&lt;p&gt;Calling &lt;code&gt;FlushAsyncLoading&lt;/code&gt; immediately after an async request recreates the Game Thread stall it was meant to avoid. It is easier to justify at startup, in a fully non-interactive transition, or in deterministic editor and automation workflows.&lt;/p&gt;

&lt;p&gt;If a synchronous boundary is unavoidable, start early, keep the mandatory set small, and wait only for the remaining tail. &lt;code&gt;LoadSynchronous&lt;/code&gt; is not safe merely because the directly referenced asset looks small; its hard-reference graph may be large.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tune IoStore, Zen Loader, and Oodle together
&lt;/h2&gt;

&lt;p&gt;Zen Loader uses cooked dependency data and retrieves chunks from &lt;code&gt;.utoc&lt;/code&gt; and &lt;code&gt;.ucas&lt;/code&gt; containers. IoStore is the normal UE 5.8 packaged path.&lt;/p&gt;

&lt;p&gt;Use Asset Registry for packaged discovery. Place custom data deliberately as a UE asset, staged UFS file, Non-UFS file, or Save/Download data; packaged Content is not a loose-file directory.&lt;/p&gt;

&lt;p&gt;Inspect final layout when chunk placement looks wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UnrealPak.exe IoStore -Describe=&amp;lt;Global.ucas&amp;gt; -DumpToFile=Output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encrypted containers also need &lt;code&gt;-CryptoKeys=&amp;lt;Crypto.json&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compression trades storage reads for decode CPU. Oodle &lt;strong&gt;Method&lt;/strong&gt; changes the main size/decode-speed tradeoff; &lt;strong&gt;Level&lt;/strong&gt; changes encoder effort and output size without selecting another runtime decoder. Smaller output can still reduce reads and patch size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;General tendency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kraken&lt;/td&gt;
&lt;td&gt;Strong compression and good decode speed; useful baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mermaid&lt;/td&gt;
&lt;td&gt;Less compression, faster decode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selkie&lt;/td&gt;
&lt;td&gt;Favors decode speed further&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leviathan&lt;/td&gt;
&lt;td&gt;Smaller data is possible, with slower decode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compare total time, decode CPU, container size, bytes read, and peak memory on weak-CPU and weak-storage targets. No compression may remove decode work while increasing I/O, download, and patch size.&lt;/p&gt;

&lt;h2&gt;
  
  
  World Partition is not only Cell Size
&lt;/h2&gt;

&lt;p&gt;Small cells increase request count; large cells include unnecessary Actors. Tune for movement speed, visibility, Actor density, and storage. Before adding Runtime Grids, use HLOD Layers, Data Layers, and spatial loading.&lt;/p&gt;

&lt;p&gt;For teleports, create a World Partition Streaming Source at the destination before moving the player:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure grid, priority, and target state.&lt;/li&gt;
&lt;li&gt;Load during a fade, door animation, or elevator ride.&lt;/li&gt;
&lt;li&gt;Move after streaming completion.&lt;/li&gt;
&lt;li&gt;Disable the temporary source and release the old area.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Direct references between distant Actors can couple their cells. Replace them with IDs, events, or subsystem queries where appropriate.&lt;/p&gt;

&lt;p&gt;Increasing &lt;code&gt;wp.Runtime.MaxLoadingLevelStreamingCells&lt;/code&gt; may shorten elapsed time while raising CPU, memory, and registration peaks. Measure gameplay and transitions separately. UE 5.8 includes World Partition Insights; use branch-appropriate tools on older versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize activation and second-stage resources
&lt;/h2&gt;

&lt;p&gt;A completed read can still be followed by expensive spawning, registration, collision, and overlap work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consider ISM/HISM for repeated static geometry.&lt;/li&gt;
&lt;li&gt;Avoid excessive Actor counts for non-gameplay decoration.&lt;/li&gt;
&lt;li&gt;Remove always-present Components that are rarely needed.&lt;/li&gt;
&lt;li&gt;Avoid global Actor searches and synchronous loads in &lt;code&gt;BeginPlay&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Delay nonessential initialization until later frames or proximity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initial overlap calculation can be expensive. Evaluate &lt;code&gt;UpdateOverlapsMethodDuringLevelStreaming&lt;/code&gt; and “Generate Overlap Events During Level Streaming” per class: decoration may not need it, while triggers may depend on it.&lt;/p&gt;

&lt;p&gt;A package can be loaded while presentation data is still missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Textures:&lt;/strong&gt; prioritize critical UI and near-field mips; do not mark everything &lt;code&gt;Never Stream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio:&lt;/strong&gt; stream long assets, but prepare the first chunk for sounds that must start immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shaders/PSOs:&lt;/strong&gt; use PSO Precaching or Pipeline Cache for materials guaranteed to appear at startup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prepare mandatory presentation data and continue optional resources in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom files need Read, Decode, and Apply stages
&lt;/h2&gt;

&lt;p&gt;Many worker tasks using synchronous file APIs can still create competing small reads. Choose storage first, then the API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small text: &lt;code&gt;FFileHelper::LoadFileToStringAsync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Large sequential processing without one full allocation: &lt;code&gt;FFileHelper::LoadFileInBlocks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Async offset-and-size reads: &lt;code&gt;IAsyncReadFileHandle::ReadRequest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Custom cooked chunks: consider &lt;code&gt;FIoDispatcher&lt;/code&gt; with Asset Registry and chunk rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;LoadFileInBlocks&lt;/code&gt; calls a visitor for sequential blocks. It avoids retaining the whole file, but does &lt;strong&gt;not&lt;/strong&gt; guarantee asynchronous I/O. Keep it off the Game Thread. Use &lt;code&gt;IAsyncReadFileHandle::ReadRequest&lt;/code&gt; for true async range reads.&lt;/p&gt;

&lt;p&gt;Validate size and read requests; a non-null &lt;code&gt;OpenAsyncRead&lt;/code&gt; handle is not full success. Keep each &lt;code&gt;IAsyncReadRequest&lt;/code&gt; alive until completion and define one owner for the handle, request, and buffer.&lt;/p&gt;

&lt;p&gt;Use this pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read:&lt;/strong&gt; obtain bytes with async I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode:&lt;/strong&gt; decompress, validate, and parse on a worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply:&lt;/strong&gt; create or update necessary UObjects on the Game Thread.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep parsed data in plain structures until Apply. Avoid retaining compressed input, decompressed output, and final objects simultaneously. For random access, index the format and read only required ranges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefetch from player intent and control memory overlap
&lt;/h2&gt;

&lt;p&gt;Distance-only prefetch is unreliable with fast movement and branching paths. Strong signals include a confirmed stage, destination, character selection, door sequence, matchmaking result, quest update, or server instruction.&lt;/p&gt;

&lt;p&gt;Scale requests to confidence: hover may load a portrait, confirmation loads Gameplay, and an irreversible transition loads voice. Track hit rate, time hidden, wasted bytes, and residency because wrong predictions still consume work.&lt;/p&gt;

&lt;p&gt;Preloading the next stage while retaining the old one can overlap working sets and trigger GC or OS pressure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prefetch the new minimum set while the old scene remains visible.&lt;/li&gt;
&lt;li&gt;Stop expensive old-scene systems after the fade begins.&lt;/li&gt;
&lt;li&gt;Release old handles and references.&lt;/li&gt;
&lt;li&gt;Place necessary GC in a known non-interactive interval.&lt;/li&gt;
&lt;li&gt;Activate the new scene and continue optional loading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use Memory Insights before adding more manual GC. Compare peak memory beside total time when tuning concurrency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast Geometry Streaming is not the default answer
&lt;/h2&gt;

&lt;p&gt;UE 5.8 improves the Experimental Fast Geometry Streaming plugin for static, non-gameplay assets. It may help very large static worlds, but does not replace reference cleanup, Asset Manager, World Partition, or gameplay Actor loading. Validate platform and workflow constraints before adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Fix device, build, storage, and starting state; record median, p95, worst hitch, and peak memory.&lt;/li&gt;
&lt;li&gt;Classify Read, Decode, Deserialize, and Activate in Insights.&lt;/li&gt;
&lt;li&gt;Remove unnecessary hard references and define mandatory/immediate/later/optional bundles.&lt;/li&gt;
&lt;li&gt;Add intent-driven preloading and Gameplay, Transition, and Tail budgets.&lt;/li&gt;
&lt;li&gt;Compare IoStore, Oodle, mips, audio, and PSOs under identical conditions.&lt;/li&gt;
&lt;li&gt;Add regression limits for dependency size, transition time, hitch, and memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;UE5 loading performance is not determined by one concurrency number.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The largest wins usually come from loading fewer dependencies and dividing assets by experience phase.&lt;/li&gt;
&lt;li&gt;Parallel I/O and a higher FPS cap should follow measurement of I/O, CPU, Game Thread, and memory behavior.&lt;/li&gt;
&lt;li&gt;IoStore, Oodle, World Partition, activation, texture residency, audio, PSOs, and GC all affect perceived loading.&lt;/li&gt;
&lt;li&gt;Custom data needs explicit Read, Decode, Apply, cancellation, and ownership boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find the slow stage in Unreal Insights and why each asset is reachable in Reference Viewer. Apply parallelism, prefetching, budgets, and compression only where measurements support them. The goal is to become interactive without a large hitch afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Checked against UE 5.8 documentation on August 9, 2026. For another engine version, verify the matching documentation and Engine Source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/unreal-engine-5-8-release-notes" rel="noopener noreferrer"&gt;UE 5.8 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine" rel="noopener noreferrer"&gt;Asset Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/asynchronous-asset-loading-in-unreal-engine" rel="noopener noreferrer"&gt;Asynchronous Asset Loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/CoreUObject/GetAsyncLoadPercentage" rel="noopener noreferrer"&gt;&lt;code&gt;GetAsyncLoadPercentage&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/zen-loader-in-unreal-engine" rel="noopener noreferrer"&gt;Zen Loader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/oodle-data" rel="noopener noreferrer"&gt;Oodle Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/world-partition-in-unreal-engine" rel="noopener noreferrer"&gt;World Partition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/trace-in-unreal-engine-5" rel="noopener noreferrer"&gt;Unreal Insights and Trace Channels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Core/FFileHelper/LoadFileInBlocks" rel="noopener noreferrer"&gt;&lt;code&gt;FFileHelper::LoadFileInBlocks&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.epicgames.com/documentation/unreal-engine/cooking-content-and-creating-chunks-in-unreal-engine" rel="noopener noreferrer"&gt;Cooking Content and Creating Chunks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Editorial disclosure: this English edition was prepared with AI-assisted translation and editing from a technically reviewed Japanese draft. The technical claims and code excerpts were reviewed against the references above before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>unrealengine</category>
      <category>cpp</category>
      <category>gamedev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Where Unity Burst and Jobs Actually Help: A Practical Guide for GameObject-Based Projects</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Sat, 22 Aug 2026 09:14:06 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/where-unity-burst-and-jobs-actually-help-a-practical-guide-for-gameobject-based-projects-4pjd</link>
      <guid>https://dev.to/gamedevtoollab/where-unity-burst-and-jobs-actually-help-a-practical-guide-for-gameobject-based-projects-4pjd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Burst Compiler and the C# Job System can solve CPU bottlenecks, but they are not performance switches. Scheduling, copying, immediate &lt;code&gt;Complete()&lt;/code&gt; calls, and unclear NativeContainer ownership can easily erase the gain.&lt;/p&gt;

&lt;p&gt;In a GameObject-based project, performance depends on &lt;strong&gt;which calculation you isolate, which data crosses the managed boundary, and when the main thread waits&lt;/strong&gt;. This article focuses on that boundary rather than a full ECS migration or Job syntax basics.&lt;/p&gt;

&lt;p&gt;The baseline is Unity 6.3 LTS, Burst 1.8.30, Collections 2.6.8, and Mathematics 1.3.3 as of August 2026. Examples use &lt;code&gt;IJobFor&lt;/code&gt; and &lt;a href="https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Unity.Jobs.IJobForExtensions.ScheduleParallelByRef.html" rel="noopener noreferrer"&gt;&lt;code&gt;ScheduleParallelByRef&lt;/code&gt;&lt;/a&gt;. ByRef avoids a large scheduling-time struct copy, although workers still use local copies. Older Unity versions may expose different overloads.&lt;/p&gt;

&lt;p&gt;The code demonstrates design boundaries, not a compiled drop-in sample. Validate it with Safety Checks, a Development Build, and the Profiler on target hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Burst and Jobs are a good fit
&lt;/h2&gt;

&lt;p&gt;Burst and Jobs fit workloads with many independent elements, substantial CPU work, contiguous value-type data, little Unity API access inside the kernel, and useful work available before the result is needed.&lt;/p&gt;

&lt;p&gt;Good candidates include movement, projectiles, FOV tests, LOD or culling, AI scoring, procedural geometry, noise, grids, and bulk processing. Deterministic server or replay workloads need cross-platform reproducibility tests.&lt;/p&gt;

&lt;p&gt;Poor candidates include small collections, Unity-API-heavy or immediate-result logic, managed-object-heavy processing, and cases where marshaling costs more than calculation. A compute shader may suit very large data-parallel work better.&lt;/p&gt;

&lt;p&gt;Ask: &lt;strong&gt;can you isolate a large enough calculation kernel that is closed over value-type data?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the four separate roles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Burst Compiler&lt;/strong&gt; optimizes supported C# kernels into native code; it does not create multithreading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C# Job System&lt;/strong&gt; schedules work and tracks dependencies, with its own overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NativeContainer&lt;/strong&gt; exposes shared data under Unity's ownership and safety rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unity.Mathematics&lt;/strong&gt; provides Burst-friendly numerical types and functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prepare data, schedule execution, and optimize the kernel as separate concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four questions before introducing Jobs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is CPU computation really the bottleneck?
&lt;/h3&gt;

&lt;p&gt;Profile first. Jobs do not fix frames dominated by rendering, physics, GC, asset loading, or synchronous I/O.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is each element independent?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;IJobFor.Execute(int index)&lt;/code&gt; has no guaranteed order. Shared accumulation should become per-index output followed by a reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the managed boundary stay small?
&lt;/h3&gt;

&lt;p&gt;Measure input collection and result application. A faster worker kernel can still make the frame slower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can waiting be delayed?
&lt;/h3&gt;

&lt;p&gt;Immediate &lt;code&gt;Complete()&lt;/code&gt; removes most overlap. Schedule early, consume next frame, or chain dependencies and wait only for the final handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with &lt;code&gt;IJobFor&lt;/code&gt; and fixed-index output
&lt;/h2&gt;

&lt;p&gt;For a new parallel loop, begin with input and output that use the same index.&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;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Burst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Mathematics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BurstCompile&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;struct&lt;/span&gt; &lt;span class="nc"&gt;IntegratePositionJob&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IJobFor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&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;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;float3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Velocities&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;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;float3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Positions&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;DeltaTime&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;Execute&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;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Positions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;Positions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Velocities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;DeltaTime&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;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;IntegratePositionJob&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Velocities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Positions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DeltaTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deltaTime&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;JobHandle&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleParallelByRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;innerloopBatchCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A NativeContainer indexer is not a &lt;code&gt;ref return&lt;/code&gt;, so “read, modify, write back” is the clearest default for structures. The outer owner allocates and disposes the arrays and must call &lt;code&gt;Complete()&lt;/code&gt; before reading or disposing data still owned by a job. &lt;code&gt;[ReadOnly]&lt;/code&gt; and fixed-index output also make the access pattern explicit to the Safety System.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical example: target evaluation with one-frame latency
&lt;/h2&gt;

&lt;p&gt;Consider a lock-on system that scores targets by distance, field of view, and threat. The main thread snapshots Transform data into persistent NativeArrays and consumes the job next frame. A version prevents stale results from reaching changed or removed targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input, output, and the job
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Burst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Mathematics&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;struct&lt;/span&gt; &lt;span class="nc"&gt;TargetInput&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;float3&lt;/span&gt; &lt;span class="n"&gt;Position&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;Threat&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;byte&lt;/span&gt; &lt;span class="n"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;;&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;struct&lt;/span&gt; &lt;span class="nc"&gt;TargetEvaluation&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;DistanceSq&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;Score&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;byte&lt;/span&gt; &lt;span class="n"&gt;IsCandidate&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;span class="n"&gt;BurstCompile&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;struct&lt;/span&gt; &lt;span class="nc"&gt;EvaluateTargetsJob&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IJobFor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReadOnly&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;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Inputs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WriteOnly&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;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetEvaluation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Outputs&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;float3&lt;/span&gt; &lt;span class="n"&gt;ObserverPosition&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;float3&lt;/span&gt; &lt;span class="n"&gt;ObserverForward&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;MaxDistanceSq&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;CosHalfFieldOfView&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;Execute&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;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TargetInput&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;toTarget&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ObserverPosition&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;distanceSq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lengthsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isNearlySamePosition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distanceSq&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0.0001f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalizesafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toTarget&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;facing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isNearlySamePosition&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;1.0f&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ObserverForward&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;distanceSq&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;MaxDistanceSq&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;facing&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;CosHalfFieldOfView&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;distanceSq01&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;distanceSq&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MaxDistanceSq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0001f&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;Outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TargetEvaluation&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;DistanceSq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distanceSq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Threat&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2.0f&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;facing&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;distanceSq01&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NegativeInfinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IsCandidate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job receives no managed references and writes only to matching output indices. Near-zero-distance targets pass the FOV test; tune the threshold for your scale. &lt;code&gt;float.NegativeInfinity&lt;/code&gt; is an internal sentinel, so use &lt;code&gt;Score&lt;/code&gt; only when &lt;code&gt;IsCandidate != 0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the target contract
&lt;/h3&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;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TargetAgent&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="k"&gt;abstract&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Threat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&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;abstract&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;EvaluationVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&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;abstract&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ApplyEvaluation&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;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isCandidate&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;Read &lt;code&gt;Threat&lt;/code&gt; before scheduling, increment &lt;code&gt;EvaluationVersion&lt;/code&gt; when relevant state changes, and call &lt;code&gt;ApplyEvaluation&lt;/code&gt; only on the main thread.&lt;/p&gt;

&lt;p&gt;This example allows one frame of stale &lt;code&gt;_maxDistance&lt;/code&gt; or &lt;code&gt;_fieldOfView&lt;/code&gt;. That may suit lock-on assistance, LOD, or visual filtering, but not damage, server validation, deterministic replays, or ranking logic. Those paths need a request ID or deterministic synchronization.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a design excerpt, not a drop-in sample. Registration APIs, capacity growth, the complete &lt;code&gt;TargetAgent&lt;/code&gt;, and tests are omitted.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Unity.Mathematics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TargetEvaluationSystem&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="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="n"&gt;Transform&lt;/span&gt; &lt;span class="n"&gt;_observer&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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetAgent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&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="n"&gt;TargetAgent&lt;/span&gt;&lt;span class="p"&gt;&amp;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="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&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;_capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&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="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&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;_maxDistance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30f&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="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;179&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;_fieldOfView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;100f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetEvaluation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_outputs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TargetAgent&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;_scheduledTargets&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="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;_scheduledVersions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;JobHandle&lt;/span&gt; &lt;span class="n"&gt;_pending&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;_pendingCount&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;bool&lt;/span&gt; &lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Awake&lt;/span&gt;&lt;span class="p"&gt;()&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;capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_capacity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetInput&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NativeArrayOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UninitializedMemory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_outputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetEvaluation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Allocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NativeArrayOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UninitializedMemory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_scheduledTargets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TargetAgent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;_scheduledVersions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;CompleteAndApply&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isActiveAndEnabled&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;_observer&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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;targetCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;targetCount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Target count exceeds NativeArray capacity."&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;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;TargetAgent&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;_scheduledTargets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_scheduledVersions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EvaluationVersion&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isActiveAndEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;Vector3&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TargetInput&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;float3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Threat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Threat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;Vector3&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Vector3&lt;/span&gt; &lt;span class="n"&gt;forward&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forward&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;maxDistanceSq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_maxDistance&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_maxDistance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EvaluateTargetsJob&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Inputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Outputs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ObserverPosition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;float3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ObserverForward&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalizesafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;float3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;MaxDistanceSq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxDistanceSq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CosHalfFieldOfView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_fieldOfView&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;_pending&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScheduleParallelByRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_pendingCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CompleteAndApply&lt;/span&gt;&lt;span class="p"&gt;()&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;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pendingCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_pendingCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;TargetAgent&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scheduledTargets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scheduledVersions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                    &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isActiveAndEnabled&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
                    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EvaluationVersion&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;TargetEvaluation&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyEvaluation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCandidate&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isActiveAndEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;break&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;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;ClearScheduledReferences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&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;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDisable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CompleteWithoutApply&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDestroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_isDisposed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;CompleteWithoutApply&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;_inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;_outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CompleteWithoutApply&lt;/span&gt;&lt;span class="p"&gt;()&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;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pendingCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_pendingCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;ClearScheduledReferences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ClearScheduledReferences&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;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_scheduledTargets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_scheduledVersions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&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;The next &lt;code&gt;Update&lt;/code&gt; completes the previous job before reusing the arrays. Removal code must also advance the target version.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;finally&lt;/code&gt; clears managed references but does not swallow exceptions. This sample is fail-fast; a system that must continue should catch individual callbacks and define recovery.&lt;/p&gt;

&lt;p&gt;Capacity overflow triggers &lt;code&gt;Debug.Assert&lt;/code&gt;; production code should define a hard limit or resize only after the pending job completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build pipelines instead of isolated jobs
&lt;/h2&gt;

&lt;p&gt;Pass one job's handle into the next instead of completing between stages. Use &lt;code&gt;JobHandle.CombineDependencies&lt;/code&gt; when several independent jobs feed one later stage. Keep data in NativeContainers through evaluation and reduction, then copy only the smallest required result back to GameObjects.&lt;/p&gt;

&lt;p&gt;Do not let parallel iterations update one shared maximum. Write one score per index, then run a dependent single &lt;code&gt;IJob&lt;/code&gt; reduction. A linear final pass is often cheap and gives deterministic tie-breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variable-length output and &lt;code&gt;ParallelWriter&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use a container-specific writer when parallel iterations append results.&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="n"&gt;ReadOnly&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;NativeArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TargetEvaluation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Evaluations&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;NativeList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;ParallelWriter&lt;/span&gt; &lt;span class="n"&gt;CandidateIndices&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;Execute&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;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Evaluations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;IsCandidate&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;CandidateIndices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddNoResize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&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;&lt;code&gt;AddNoResize&lt;/code&gt; requires enough Capacity before scheduling. When reusing the list, complete the previous writer, call &lt;code&gt;Clear()&lt;/code&gt; on the main thread, verify Capacity, and schedule again.&lt;/p&gt;

&lt;p&gt;Parallel append order is undefined. If order affects state, sort afterward, compact fixed flags in source order, or preserve source indices with an explicit tie rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  NativeContainer lifetime and batch size
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;Temp&lt;/code&gt; for extremely short-lived data, &lt;code&gt;TempJob&lt;/code&gt; for short jobs disposed within four frames, and &lt;code&gt;Persistent&lt;/code&gt; for explicitly owned buffers. For every-frame work, allocate persistent capacity once and reuse it. One owner should control allocation, the outstanding handle, resizing, and disposal. Never resize or dispose while a job may access the memory; call &lt;code&gt;Complete()&lt;/code&gt; even when &lt;code&gt;IsCompleted&lt;/code&gt; is true. See the &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/job-system-native-container.html" rel="noopener noreferrer"&gt;NativeContainer manual&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Batch size also needs measurement. Start around 64 for light work, 16 or 32 for medium work, and 1 for expensive iterations; compare average time, worst frames, and the wait at &lt;code&gt;Complete()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GameObjects, Transforms, and data boundaries
&lt;/h2&gt;

&lt;p&gt;A Burst job cannot directly access a normal &lt;code&gt;GameObject&lt;/code&gt; or &lt;code&gt;MonoBehaviour&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshot values:&lt;/strong&gt; copy Transform data into NativeArrays. This suits multi-stage numerical pipelines, but collection and application costs must be measured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;IJobParallelForTransform&lt;/code&gt;:&lt;/strong&gt; process a &lt;code&gt;TransformAccessArray&lt;/code&gt;. Use read-write scheduling when modifying Transforms and read-only scheduling when reading; read-only jobs may receive invalid entries, so check &lt;code&gt;TransformAccess.isValid&lt;/code&gt;. Frequent additions and removals make maintenance and index mapping part of the cost. See &lt;a href="https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Jobs.IJobParallelForTransformExtensions.html" rel="noopener noreferrer"&gt;&lt;code&gt;IJobParallelForTransformExtensions&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write kernels Burst can optimize
&lt;/h2&gt;

&lt;p&gt;Keep managed references outside the kernel and copy only required values before scheduling. Prefer Unity.Mathematics and squared-distance comparisons for numerical work.&lt;/p&gt;

&lt;p&gt;Do not enable &lt;code&gt;FloatMode.Fast&lt;/code&gt; automatically. Approximate visuals may tolerate it; replay, server validation, and cross-device agreement require error and reproducibility tests.&lt;/p&gt;

&lt;p&gt;Per-element &lt;code&gt;FunctionPointer&amp;lt;T&amp;gt;&lt;/code&gt; calls retain overhead and can block wider vectorization. Prefer direct job code or batched calls; see Burst's &lt;a href="https://docs.unity3d.com/Packages/com.unity.burst@1.8/manual/csharp-function-pointers.html" rel="noopener noreferrer"&gt;Function Pointer guidance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Treat Safety System errors as design feedback. Prefer fixed-index output, dependency chains, staged reductions, or supported writers before disabling restrictions. Pass mutable settings through the job struct, not mutable static state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidate results instead of canceling jobs
&lt;/h2&gt;

&lt;p&gt;A scheduled job cannot be interrupted halfway through. When a result becomes obsolete, let the job finish and reject it with a generation or request ID. Split very long work into shorter jobs. This is usually safer than a cancellation flag because the main thread cannot freely mutate shared job memory after scheduling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the complete system
&lt;/h2&gt;

&lt;p&gt;Compare a normal C# loop, &lt;code&gt;RunByRef&lt;/code&gt;, &lt;code&gt;ScheduleByRef&lt;/code&gt;, and &lt;code&gt;ScheduleParallelByRef&lt;/code&gt;. Measure collection, scheduling, &lt;code&gt;Complete()&lt;/code&gt; wait, result application, and the whole frame. Reject the design if the kernel improves but the full boundary becomes slower.&lt;/p&gt;

&lt;p&gt;Profile after Burst warm-up and decide in a Development Build on target hardware. For &lt;code&gt;WaitForJobGroup&lt;/code&gt;, inspect Timeline: completion timing, scheduling timing, dependency serialization, worker occupancy, and Burst compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical adoption sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Extract numerical work from &lt;code&gt;MonoBehaviour.Update&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Express inputs and outputs as value-type arrays&lt;/li&gt;
&lt;li&gt;Validate results, disposal, invalidation, and scene transitions with &lt;code&gt;RunByRef&lt;/code&gt; or &lt;code&gt;ScheduleByRef&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Switch to &lt;code&gt;ScheduleParallelByRef&lt;/code&gt; and measure batch size and independence&lt;/li&gt;
&lt;li&gt;Schedule earlier and delay &lt;code&gt;Complete()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Split work into dependent jobs only when profiling supports it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Common failures are per-frame allocation, immediate completion, many tiny jobs, excessive copying, suppressed safety errors, and assumed output order. Fix data and synchronization before adding more jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With Burst and the Job System, data boundaries and synchronization matter more than syntax.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep Unity API access outside the kernel&lt;/li&gt;
&lt;li&gt;Reuse NativeContainers and complete jobs before reading, resizing, or disposal&lt;/li&gt;
&lt;li&gt;Chain work instead of waiting immediately&lt;/li&gt;
&lt;li&gt;Validate stale results and variable output order explicitly&lt;/li&gt;
&lt;li&gt;Measure the entire path on target hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with many similar elements and a result that can tolerate one frame of latency. Expand only where profiling proves a benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/job-system-native-container.html" rel="noopener noreferrer"&gt;Unity Manual: NativeContainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Unity.Jobs.IJobForExtensions.html" rel="noopener noreferrer"&gt;Unity Scripting API: IJobForExtensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Jobs.IJobParallelForTransformExtensions.html" rel="noopener noreferrer"&gt;Unity Scripting API: IJobParallelForTransformExtensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Unity.Jobs.JobHandle.Complete.html" rel="noopener noreferrer"&gt;Unity Scripting API: JobHandle.Complete&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This article was created with AI-assisted drafting. The author reviewed the technical content and makes the final publication decision.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>csharp</category>
      <category>gamedev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Unity Mipmaps Beyond Smaller Textures: Temporal Stability, Streaming, and Semantic Mips</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Wed, 19 Aug 2026 07:31:55 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/unity-mipmaps-beyond-smaller-textures-temporal-stability-streaming-and-semantic-mips-38ck</link>
      <guid>https://dev.to/gamedevtoollab/unity-mipmaps-beyond-smaller-textures-temporal-stability-streaming-and-semantic-mips-38ck</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Mipmaps are usually introduced as smaller copies of a texture. That is correct, but it does not explain why a no-mipmap screenshot looks sharp while motion shimmers, why foliage disappears, why normal-mapped highlights flash, or why an atlas bleeds only in lower levels.&lt;/p&gt;

&lt;p&gt;A more useful model treats a mip chain as three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A tool for &lt;strong&gt;temporal image stability&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A mechanism for &lt;strong&gt;allocating bandwidth and resident memory&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A hierarchy whose levels can preserve different kinds of &lt;strong&gt;meaning&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article uses Unity 6.5 (6000.5) terminology. Inspector names can differ in Unity 2022/2023 LTS, other Unity 6 versions, and between URP and HDRP package versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mipmaps Match Sampling Density, Not Distance
&lt;/h2&gt;

&lt;p&gt;Imagine a 4096 x 4096 texture on a floor viewed at a shallow angle. One screen pixel may cover hundreds of texels. Sampling only mip 0 selects a tiny subset of that footprint, so a small camera movement selects a different subset and produces crawling detail, moire, and flicker.&lt;/p&gt;

&lt;p&gt;A mipmap selects a texel density closer to the projected footprint. Distance matters, but it is not the rule. Projected size, UV scale, surface angle, texture resolution, projection, bias, and anisotropy all affect the result.&lt;/p&gt;

&lt;p&gt;For ordinary implicit sampling, the GPU estimates UV change across neighboring fragments. In simplified form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rho = max(length(ddx(texelPosition)), length(ddy(texelPosition)))
lod = log2(rho)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An LOD near 0 points to mip 0; near 1 points to mip 1. Hardware is more sophisticated, especially with anisotropy, but the key model is: &lt;strong&gt;screen-space UV derivatives drive mip selection&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bilinear, trilinear, and anisotropic filtering solve different problems
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bilinear:&lt;/strong&gt; blends texels inside one mip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trilinear:&lt;/strong&gt; blends two adjacent mips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anisotropic:&lt;/strong&gt; handles elongated footprints on oblique surfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trilinear filtering does not replace anisotropic filtering. A road may need anisotropic sampling even when mip transitions are smooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ideal LOD and resident quality are separate
&lt;/h3&gt;

&lt;p&gt;The sampler may want mip 0 while Streaming currently has only mip 2 and coarser levels resident. This explains why a surface can be blurry after a camera cut and sharpen later. Always separate the ideal sample LOD from the highest-resolution mip actually available in GPU memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 33% Cost Hides a More Useful Fact
&lt;/h2&gt;

&lt;p&gt;For a square texture, the complete chain is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mip 0: 4096 x 4096
Mip 1: 2048 x 2048
Mip 2: 1024 x 1024
...
Mip 12: 1 x 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each level contains one quarter as many texels as the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 + 1/4 + 1/16 + 1/64 + ... = 4/3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a full mip chain adds about 33% more texels than mip 0 alone. The reverse view is more useful for budgeting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Approximate share of the full chain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mip 0&lt;/td&gt;
&lt;td&gt;75.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mip 1&lt;/td&gt;
&lt;td&gt;18.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mip 2&lt;/td&gt;
&lt;td&gt;4.69%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mip 3&lt;/td&gt;
&lt;td&gt;1.17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mip 4 and below combined&lt;/td&gt;
&lt;td&gt;0.39%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Mip 0 alone is roughly three quarters of the chain. Dropping the maximum resolution by one level does not halve the texel count; it reduces the remaining chain to about one quarter. Two levels reduce it to about one sixteenth.&lt;/p&gt;

&lt;p&gt;Compression blocks, minimum mip storage, alignment, and platform details change exact byte counts, but this approximation explains why Mipmap Limits and Mipmap Streaming can have such large effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why “No Mipmaps” Can Win a Screenshot Comparison
&lt;/h2&gt;

&lt;p&gt;Disabling mipmaps can sharpen one frame while making motion unstable. Test fine grids, gravel, leaves, thin text, and repeating patterns with a slow, repeatable camera pan. Temporarily disabling TAA can help isolate texture aliasing.&lt;/p&gt;

&lt;p&gt;Watch for crawling patterns, distant moire, flashing highlights, oblique-surface noise, and discontinuities between render scales. Mipmaps are spatial prefilters, but in practice they are also &lt;strong&gt;temporal noise reduction&lt;/strong&gt;. Judge negative mip bias the same way: in motion, not from one sharp frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Import Settings by Meaning, Not File Extension
&lt;/h2&gt;

&lt;p&gt;Two PNG files can require opposite settings. Decide from what the shader expects the texels to mean.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Texture meaning&lt;/th&gt;
&lt;th&gt;sRGB&lt;/th&gt;
&lt;th&gt;Mipmaps&lt;/th&gt;
&lt;th&gt;Main concern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base color / ordinary emissive color&lt;/td&gt;
&lt;td&gt;Usually on&lt;/td&gt;
&lt;td&gt;Usually on in 3D&lt;/td&gt;
&lt;td&gt;Alpha coverage; HDR/data exceptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal map&lt;/td&gt;
&lt;td&gt;Import as Normal Map&lt;/td&gt;
&lt;td&gt;Usually on&lt;/td&gt;
&lt;td&gt;Average direction loses normal variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roughness / metallic / AO&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;Often on&lt;/td&gt;
&lt;td&gt;Numerical meaning after filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Material ID / category&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;Use cautiously&lt;/td&gt;
&lt;td&gt;Averaging can invent invalid IDs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed-size UI&lt;/td&gt;
&lt;td&gt;Usually on&lt;/td&gt;
&lt;td&gt;Often unnecessary&lt;/td&gt;
&lt;td&gt;Reconsider for scaling, rotation, world space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pixel art&lt;/td&gt;
&lt;td&gt;Pipeline-dependent&lt;/td&gt;
&lt;td&gt;Usually off or custom&lt;/td&gt;
&lt;td&gt;Point sampling and integer scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lookup/data texture&lt;/td&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;Usually off&lt;/td&gt;
&lt;td&gt;Automatic averaging may corrupt data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  sRGB is semantic
&lt;/h3&gt;

&lt;p&gt;Base color is normally color. Roughness, metallic, AO, height, vectors, coefficients, LUT values, and masks are normally linear data. HDR textures and emissive maps that store numerical intensity are common exceptions. Because mip generation is filtering, the wrong color space changes the meaning of lower levels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Box, Kaiser, and negative bias
&lt;/h3&gt;

&lt;p&gt;Unity's Box filter is smoother; Kaiser retains more sharpness and potentially more shimmer. Negative bias also selects higher-resolution levels, increasing aliasing, bandwidth, cache pressure, and streaming demand. Before using it globally, check anisotropic filtering, UV density, source resolution, render scale, and the active upscaler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Case 1: Foliage and Fences Disappear
&lt;/h2&gt;

&lt;p&gt;An alpha-tested shader may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hlsl"&gt;&lt;code&gt;&lt;span class="nb"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A thin leaf may be alpha 1 against alpha 0. Downsampling creates boundary values such as 0.4, which fail a 0.5 cutoff. The average is not mathematically wrong; the &lt;strong&gt;area that survives the threshold&lt;/strong&gt;, or coverage, changed between mip levels.&lt;/p&gt;

&lt;p&gt;Unity's &lt;strong&gt;Preserve Coverage&lt;/strong&gt; adjusts lower levels to keep coverage more stable. Match its cutoff to the shader and check the base pass, shadow-caster pass, Shader Graph threshold, LOD materials, and any Alpha To Coverage path. If the object remains visible but its shadow thins, inspect the shadow pass first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replicate Border&lt;/strong&gt; is different: it preserves image-edge values for cases such as light cookies. It does not replace Preserve Coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Case 2: Normal Maps Still Produce Flashing Highlights
&lt;/h2&gt;

&lt;p&gt;Mipmapping averages normal directions, but an average normal does not describe their distribution. Equal left- and right-facing normals may average forward; that does not make the region smooth. It means variance was discarded. Keeping the original smoothness can then create a sharp, unstable specular lobe around the average direction.&lt;/p&gt;

&lt;p&gt;Treat normal filtering and specular stability separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;import the normal map correctly and give it mipmaps;&lt;/li&gt;
&lt;li&gt;keep roughness/smoothness linear;&lt;/li&gt;
&lt;li&gt;feed lost normal variance into roughness when possible;&lt;/li&gt;
&lt;li&gt;consider HDRP's Geometric Specular Anti-Aliasing;&lt;/li&gt;
&lt;li&gt;in custom shaders, reduce smoothness from normal variation or LOD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MSAA mainly addresses polygon edges. TAA can hide some instability, but unstable input often trades shimmer for blur or ghosting. Stabilize the material before temporal reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Case 3: Packed Masks Share One Mip Policy
&lt;/h2&gt;

&lt;p&gt;Packing metallic, AO, roughness, and other masks into RGBA reduces samples and memory, but all channels still share mip generation, filtering, wrap mode, sRGB state, compression, Mipmap Limit Group, streaming priority, and bias.&lt;/p&gt;

&lt;p&gt;Mip filtering operates on channel values, while compression error is not guaranteed to be independent per channel. More importantly, sampling and residency policy are selected at texture granularity. Pack values because they can share that policy, not merely because a channel is empty.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Desired reduction behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AO&lt;/td&gt;
&lt;td&gt;Smooth aggregation may be acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roughness&lt;/td&gt;
&lt;td&gt;Specular energy matters, not only arithmetic mean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary mask&lt;/td&gt;
&lt;td&gt;Preserve area, majority, or max depending on meaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Material ID&lt;/td&gt;
&lt;td&gt;Never invent an intermediate category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signed direction&lt;/td&gt;
&lt;td&gt;May require renormalization or reinterpretation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For IDs and categories, consider a separate non-mipmapped texture, point sampling, custom majority/max mips, or a buffer/spatial structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Case 4: Atlas Bleeding Gets Worse at Lower Mips
&lt;/h2&gt;

&lt;p&gt;Four pixels of mip-0 padding become two in mip 1, one in mip 2, and half a pixel in mip 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;effective padding at mip L ~= mip 0 padding / 2^L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Design padding for the lowest level you expect to use. Dilate island colors, increase padding, keep UVs inside their islands, and distinguish texture-edge problems from neighbors inside the atlas.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Texture2DArray&lt;/code&gt; isolates layers and avoids much atlas bleeding, but Unity's Mipmap Streaming does not support array textures. Balance visual stability, batching, streaming, and platform support.&lt;/p&gt;

&lt;p&gt;Hand-authored lower mips can go further: detailed sign text can become a bold pictogram at distance. That is the basis of semantic mipmaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mipmap Limits and Streaming as a Quality Scheduler
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Mipmap Limit&lt;/strong&gt; caps allowed quality; &lt;strong&gt;Mipmap Streaming&lt;/strong&gt; decides which allowed levels are resident. Group textures by visual importance, such as &lt;code&gt;CharacterHero&lt;/code&gt;, &lt;code&gt;CharacterCrowd&lt;/code&gt;, &lt;code&gt;WorldNear&lt;/code&gt;, &lt;code&gt;WorldFar&lt;/code&gt;, and &lt;code&gt;Cinematic&lt;/code&gt;. One removed top level has a large effect because mip 0 was about 75% of the chain.&lt;/p&gt;

&lt;p&gt;Priority is relative, not a reservation for mip 0. A budget shortage can prevent a requested level from loading, so never wait forever. This is a small cutscene-oriented example; a general preloader also needs ownership, cancellation, limit handling, camera-change handling, destruction safety, and budget monitoring.&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;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MipmapPreloader&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="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="n"&gt;Texture2D&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;textures&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;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="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&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;requestedLevel&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="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1f&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;timeoutSeconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3f&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;IEnumerator&lt;/span&gt; &lt;span class="nf"&gt;Preload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Texture2D&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;textures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CanStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestedMipmapLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;requestedLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipmapCount&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&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;deadline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtimeSinceStartup&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtimeSinceStartup&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Texture2D&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;textures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CanStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsRequestedMipmapLevelLoaded&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mip preload timed out; continuing with fallback quality."&lt;/span&gt;&lt;span class="p"&gt;);&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;Release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Texture2D&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;textures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CanStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClearRequestedMipmapLevel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnDisable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CanStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Texture2D&lt;/span&gt; &lt;span class="n"&gt;texture&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;texture&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streamingMipmaps&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;Define the timeout fallback: continue at lower quality, extend a fade, or reduce the request. Release control after the cutscene. Centralize ownership because one caller's &lt;code&gt;ClearRequestedMipmapLevel&lt;/code&gt; can clear another caller's expectation.&lt;/p&gt;

&lt;p&gt;Unity estimates required levels from meshes, UVs, cameras, and standard material conventions. Array textures, cubemap arrays, and 3D textures are unsupported by Mipmap Streaming; custom drawing and nonstandard UV transforms also need verification.&lt;/p&gt;

&lt;p&gt;Success is not “Streaming is enabled.” It is that &lt;strong&gt;required-mip estimation and the memory budget remain valid during real camera motion on target hardware&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize the Approximate Mip Level
&lt;/h2&gt;

&lt;p&gt;Subjective reports such as “slightly blurry on one device” are easier to investigate when approximate LOD is visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hlsl"&gt;&lt;code&gt;&lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ApproximateMipLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;texelSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// texelSize = (1/width, 1/height, width, height)&lt;/span&gt;
    &lt;span class="kt"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;texelPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;texelSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ddx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texelPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ddy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texelPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="n"&gt;footprint2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;footprint2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&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;Color &lt;code&gt;floor(lod)&lt;/code&gt; bands to reveal UV-density discontinuities, material bias differences, oblique surfaces, dynamic-resolution changes, and the mip where an atlas starts bleeding.&lt;/p&gt;

&lt;p&gt;This is diagnostic, not an exact sampler result. Real sampling also accounts for anisotropy, bias, and platform details. Calculate derivatives before divergent per-fragment branches whenever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 1: Semantic Mipmaps
&lt;/h2&gt;

&lt;p&gt;A mip level can carry a representation designed for its scale rather than an automatic reduction. I will call this a &lt;strong&gt;semantic mipmap&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace a shop name with its icon at distance;&lt;/li&gt;
&lt;li&gt;remove minor roads from lower map levels;&lt;/li&gt;
&lt;li&gt;thicken selection or emissive marks in coarse mips;&lt;/li&gt;
&lt;li&gt;fade a detail normal toward neutral;&lt;/li&gt;
&lt;li&gt;reduce a binary mask by majority or max instead of average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is texture LOD: the representation changes, not only its resolution.&lt;/p&gt;

&lt;p&gt;Trilinear filtering blends adjacent levels. If mip 2 is text and mip 3 is an icon, the transition contains both. Design blendable levels, transition over several mips, use explicit LOD, or blend separate textures.&lt;/p&gt;

&lt;p&gt;Filter and wrap modes are semantic too. A sign may use &lt;code&gt;Trilinear&lt;/code&gt; and &lt;code&gt;Clamp&lt;/code&gt;; an ID hierarchy may require &lt;code&gt;Point&lt;/code&gt; or explicit LOD. Point filtering stops interpolation but cannot repair a mip generated with the wrong reduction rule.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Texture2D.SetPixelData&lt;/code&gt; can write every level. The critical detail is &lt;code&gt;Apply(updateMipmaps: false)&lt;/code&gt;; otherwise Unity regenerates the chain from mip 0.&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;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SemanticMipTextureBuilder&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;static&lt;/span&gt; &lt;span class="n"&gt;Texture2D&lt;/span&gt; &lt;span class="nf"&gt;Create&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;width&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;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Color32&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;FilterMode&lt;/span&gt; &lt;span class="n"&gt;filterMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TextureWrapMode&lt;/span&gt; &lt;span class="n"&gt;wrapMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&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;expected&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FloorToInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mips&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;mips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Exactly &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; mip levels are required."&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;mipWidth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&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;mipHeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&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;level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;mips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;mipWidth&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mipHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Mip &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; has an invalid pixel count."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;mipWidth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mipWidth&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;mipHeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mipHeight&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;texture&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Texture2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextureFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RGBA32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;mipChain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;filterMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filterMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wrapMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wrapMode&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;for&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;level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetPixelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mips&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updateMipmaps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;makeNoLongerReadable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;texture&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;For shipped assets, an editor or external pipeline that generates levels before platform compression is often easier to manage. Use &lt;code&gt;makeNoLongerReadable: true&lt;/code&gt; when the CPU no longer needs the texture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 2: Use the Mip Chain as a Hierarchical Data Structure
&lt;/h2&gt;

&lt;p&gt;A mip chain repeatedly aggregates each 2 x 2 region. The reduction need not be an average:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average:&lt;/strong&gt; luminance, exposure, bloom;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Min / Max:&lt;/strong&gt; regional extrema;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth pyramid:&lt;/strong&gt; occlusion and screen-space effects;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Occupancy:&lt;/strong&gt; reject empty coarse regions before reading detail;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variance:&lt;/strong&gt; preserve mean and dispersion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For custom reduction, create a mipmapped &lt;code&gt;RenderTexture&lt;/code&gt;, disable &lt;code&gt;autoGenerateMips&lt;/code&gt;, and dispatch a compute shader from level 0 to 1, then 1 to 2. &lt;code&gt;GenerateMips&lt;/code&gt; performs ordinary generation; it does not express max, majority vote, or arbitrary aggregation.&lt;/p&gt;

&lt;p&gt;This is only an outline. Enable &lt;code&gt;enableRandomWrite&lt;/code&gt; before creation, verify &lt;code&gt;GraphicsFormat&lt;/code&gt; random-write support, bind the destination mip with &lt;code&gt;ComputeShader.SetTexture(..., mipLevel)&lt;/code&gt;, define ordering and synchronization, and test every target graphics API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mip 0: source 16 x 16
  -&amp;gt; max over each 2 x 2 block
Mip 1: 8 x 8 -&amp;gt; Mip 2: 4 x 4 -&amp;gt; Mip 3: 2 x 2 -&amp;gt; Mip 4: 1 x 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coarse occupancy level can terminate a search before fine reads. For depth pyramids, min/max meaning reverses between conventional Z and reversed Z.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 3: Automate Import Rules by Semantic Suffix
&lt;/h2&gt;

&lt;p&gt;Many mip failures are configuration drift. Avoid an ambiguous &lt;code&gt;_mask&lt;/code&gt; suffix and split conventions by reduction behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_orm&lt;/code&gt;: continuous linear data with mipmaps;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_cutout&lt;/code&gt;: alpha-tested color with Preserve Coverage;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_coverage&lt;/code&gt;: linear coverage data with Preserve Coverage;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_binary&lt;/code&gt; / &lt;code&gt;_id&lt;/code&gt;: discrete values, no automatic mipmaps, point filtering;&lt;/li&gt;
&lt;li&gt;separate suffixes for majority, max, or authored semantic mips.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEditor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextureImportConvention&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AssetPostprocessor&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;override&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="nf"&gt;GetVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnPreprocessTexture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;importer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextureImporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;assetImporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assetPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\\'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/textures/world/"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipmapEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filterMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FilterMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trilinear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streamingMipmaps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_orm"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sRGBTexture&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipmapEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filterMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FilterMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trilinear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_cutout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_coverage"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipmapEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filterMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FilterMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trilinear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipMapsPreserveCoverage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alphaTestReferenceValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_coverage"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sRGBTexture&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_binary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sRGBTexture&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mipmapEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streamingMipmaps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;importer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filterMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FilterMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Point&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;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;suffix&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.tga"&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;Place this in an &lt;code&gt;Editor&lt;/code&gt; folder or editor-only assembly. Keep the cutoff synchronized with the shader. Separate mandatory rules, first-import defaults, and convention warnings. Increment &lt;code&gt;GetVersion()&lt;/code&gt; when behavior changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic Resolution, Upscalers, and Mip Bias
&lt;/h2&gt;

&lt;p&gt;Lower internal resolution changes UV derivatives and can select coarser mips. A strong global negative bias may sharpen a still image while increasing shimmer, bandwidth, and streaming pressure.&lt;/p&gt;

&lt;p&gt;Check the active pipeline and upscaler first. HDRP provides &lt;strong&gt;Use Mip Bias&lt;/strong&gt; for Dynamic Resolution, and its DLSS integration can apply automatic correction. URP/HDRP, DLSS/FSR/STP, package versions, and custom shaders do not apply bias in the same place, so avoid correcting it twice at pipeline, texture, and shader levels.&lt;/p&gt;

&lt;p&gt;Compare stationary detail, slow-pan shimmer, GPU time, streaming-budget pressure, and render-scale transitions on one camera path. Output sharpening and sampler bias are different: one modifies the reconstructed image; the other introduces higher-frequency texture input.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Decision Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Will it shrink or rotate on screen?&lt;/strong&gt; Use mipmaps for most 3D textures; reconsider UI that scales, rotates, or enters world space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color or data?&lt;/strong&gt; Use sRGB for ordinary color, linear for numerical data, and the Normal Map importer for normals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha tested?&lt;/strong&gt; Match Preserve Coverage with every shader pass that clips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewed obliquely?&lt;/strong&gt; Try anisotropic filtering before negative bias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlased?&lt;/strong&gt; Derive padding and dilation from the lowest required mip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory constrained?&lt;/strong&gt; Prefer Mipmap Limit Groups and Streaming over disabling mipmaps globally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does averaging preserve meaning?&lt;/strong&gt; Use ordinary generation for color-like signals, Preserve Coverage for cutouts, custom min/max/majority reduction for special data, and semantic mips when the representation should change.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common Mipmap Myths
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Myth&lt;/th&gt;
&lt;th&gt;Better model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Distance alone selects the mip&lt;/td&gt;
&lt;td&gt;Screen-space UV derivatives select the ideal LOD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No mipmaps means higher quality&lt;/td&gt;
&lt;td&gt;It may sharpen a still frame while increasing temporal aliasing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trilinear fixes an oblique floor&lt;/td&gt;
&lt;td&gt;Anisotropic footprints need anisotropic filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A mip chain nearly doubles memory&lt;/td&gt;
&lt;td&gt;The theoretical increase is about 33%; mip 0 is about 75% of the chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High priority guarantees mip 0&lt;/td&gt;
&lt;td&gt;Requests can lose to the memory budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal mipmaps guarantee stable highlights&lt;/td&gt;
&lt;td&gt;Average direction does not preserve normal variance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any empty packed channel is free&lt;/td&gt;
&lt;td&gt;All channels share filtering, compression, and residency policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atlas padding is a mip-0 decision&lt;/td&gt;
&lt;td&gt;Effective padding halves at every level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mips must be automatic reductions&lt;/td&gt;
&lt;td&gt;Levels can contain semantic replacements or custom aggregates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mipmaps connect image stability, bandwidth, resident memory, material semantics, and scalable representation. The central question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When this texture becomes smaller on screen, what information must survive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Color may need an average; foliage needs coverage; normals need variance; IDs need category integrity; signs need readability; hierarchical depth needs min or max. Treat every level as a deliberate representation, and mipmaps become a design tool rather than an import checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/texture-mipmaps-introduction.html" rel="noopener noreferrer"&gt;Unity 6.5: Mipmaps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/texture-type-default.html" rel="noopener noreferrer"&gt;Unity 6.5: Default Texture Import Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/class-QualitySettings.html" rel="noopener noreferrer"&gt;Unity 6.5: Quality Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/TextureStreaming-introduction.html" rel="noopener noreferrer"&gt;Unity 6.5: Mipmap Streaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Texture-mipMapBias.html" rel="noopener noreferrer"&gt;Unity API: Texture.mipMapBias&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Texture2D-requestedMipmapLevel.html" rel="noopener noreferrer"&gt;Unity API: Texture2D.requestedMipmapLevel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Texture2D.SetPixelData.html" rel="noopener noreferrer"&gt;Unity API: Texture2D.SetPixelData&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Texture2D.Apply.html" rel="noopener noreferrer"&gt;Unity API: Texture2D.Apply&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Texture2D.IsRequestedMipmapLevelLoaded.html" rel="noopener noreferrer"&gt;Unity API: Texture2D.IsRequestedMipmapLevelLoaded&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Rendering.CommandBuffer.GenerateMips.html" rel="noopener noreferrer"&gt;Unity API: CommandBuffer.GenerateMips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/RenderTexture-enableRandomWrite.html" rel="noopener noreferrer"&gt;Unity API: RenderTexture.enableRandomWrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/ComputeShader.SetTexture.html" rel="noopener noreferrer"&gt;Unity API: ComputeShader.SetTexture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/ScriptReference/FilterMode.html" rel="noopener noreferrer"&gt;Unity API: FilterMode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/HDRP-Asset.html" rel="noopener noreferrer"&gt;HDRP: Dynamic Resolution and Use Mip Bias&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/deep-learning-super-sampling-in-hdrp.html" rel="noopener noreferrer"&gt;HDRP: DLSS and Mip Bias&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.4/manual/Geometric-Specular-Anti-Aliasing.html" rel="noopener noreferrer"&gt;HDRP: Geometric Specular Anti-Aliasing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>graphics</category>
      <category>shaders</category>
    </item>
    <item>
      <title>Is LitMotion the Best Tween Library for Unity in 2026? A Practical Guide</title>
      <dc:creator>GameDevToolLab</dc:creator>
      <pubDate>Tue, 18 Aug 2026 10:19:47 +0000</pubDate>
      <link>https://dev.to/gamedevtoollab/is-litmotion-the-best-tween-library-for-unity-in-2026-a-practical-guide-1k4e</link>
      <guid>https://dev.to/gamedevtoollab/is-litmotion-the-best-tween-library-for-unity-in-2026-a-practical-guide-1k4e</guid>
      <description>&lt;p&gt;DOTween has been the default answer for Unity tweening for years, and it is still a very strong library. But for a new code-driven project, I would no longer choose it automatically.&lt;/p&gt;

&lt;p&gt;After using LitMotion recently, I found its API especially comfortable: Transform values, UI values, and custom values all follow the same &lt;code&gt;Create -&amp;gt; With -&amp;gt; Bind&lt;/code&gt; pattern. LitMotion v2 also adds Sequence support, Inspector editing through LitMotion.Animation, debugging tools, and improved cancellation/control APIs.&lt;/p&gt;

&lt;p&gt;This article compares LitMotion, PrimeTween, and DOTween, then shows the parts of LitMotion I consider important in production: setup, &lt;code&gt;MotionHandle&lt;/code&gt;, object lifetime, &lt;code&gt;Time.timeScale&lt;/code&gt;, Sequence, UniTask, Punch/Shake, TextMeshPro, and migration design.&lt;/p&gt;

&lt;p&gt;The version information here was checked on &lt;strong&gt;July 28, 2026&lt;/strong&gt;. Recheck release pages if you read this much later.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendation
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New project, animation logic mainly in C#&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LitMotion&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Very sensitive to tween CPU/GC cost&lt;/td&gt;
&lt;td&gt;Benchmark &lt;strong&gt;LitMotion and PrimeTween&lt;/strong&gt; in your real workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefer &lt;code&gt;Tween.Position()&lt;/code&gt;-style target APIs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PrimeTween&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want short Inspector-edited animations&lt;/td&gt;
&lt;td&gt;Evaluate &lt;strong&gt;LitMotion.Animation&lt;/strong&gt;, PrimeTween PRO, and DOTween Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing project already uses DOTween heavily&lt;/td&gt;
&lt;td&gt;Usually &lt;strong&gt;keep DOTween&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third-party assets depend on DOTween&lt;/td&gt;
&lt;td&gt;Keep DOTween, or strictly separate ownership&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a new code-first project, &lt;strong&gt;LitMotion is currently my first candidate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It emphasizes low-allocation tween creation, uses the C# Job System and Burst, supports arbitrary values, and keeps a consistent API. It is MIT licensed and supports Unity 2021.3 or later.&lt;/p&gt;

&lt;p&gt;That does not mean your whole animation path becomes allocation-free. Capturing lambdas, strings, LINQ, async code, Canvas rebuilds, and whatever you do in the binding still matter.&lt;/p&gt;

&lt;p&gt;At the research date, LitMotion's latest release was v2.0.2. PrimeTween was also actively maintained, so I consider it a serious alternative rather than a secondary option.&lt;/p&gt;

&lt;h2&gt;
  
  
  LitMotion, PrimeTween, and DOTween
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LitMotion
&lt;/h3&gt;

&lt;p&gt;LitMotion is a data-oriented tween library built around &lt;code&gt;LMotion&lt;/code&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a value transition, add options, then bind it to a target. The same structure works for Transform, UI, TextMeshPro, and your own values.&lt;/p&gt;

&lt;p&gt;LitMotion v2 adds &lt;code&gt;LSequence&lt;/code&gt;, LitMotion.Animation, &lt;code&gt;TryCancel()&lt;/code&gt; / &lt;code&gt;TryComplete()&lt;/code&gt; style handle control, and LitMotion Debugger.&lt;/p&gt;

&lt;h3&gt;
  
  
  PrimeTween
&lt;/h3&gt;

&lt;p&gt;PrimeTween is also designed around modern, low-allocation usage, but its API feels more target-oriented.&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="n"&gt;Tween&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PositionY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;endValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InOutSine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting from &lt;code&gt;Tween.&lt;/code&gt; is very discoverable in an IDE. If you prefer choosing a target operation first, PrimeTween may feel more natural than LitMotion.&lt;/p&gt;

&lt;h3&gt;
  
  
  DOTween
&lt;/h3&gt;

&lt;p&gt;DOTween remains excellent when you already have production code and tools around it.&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="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DOMoveX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tweening&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has a mature ecosystem, many shortcuts, strong Sequence support, and DOTween Pro. If your project already depends on those strengths and profiling shows no problem, migration can easily cost more than it saves.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about MagicTween?
&lt;/h3&gt;

&lt;p&gt;MagicTween's repository was archived in 2025 and points users toward LitMotion. I would not choose MagicTween for a new project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not choose from a benchmark alone
&lt;/h2&gt;

&lt;p&gt;The author's public &lt;a href="https://github.com/AnnulusGames/TweenPerformance" rel="noopener noreferrer"&gt;TweenPerformance&lt;/a&gt; project shows strong LitMotion results in its tested environment, including very low allocation during tween creation.&lt;/p&gt;

&lt;p&gt;But benchmark versions differ from the current libraries, the environment is specific, and real games also pay for bindings, Canvas work, closures, strings, async state machines, materials, and game-specific logic.&lt;/p&gt;

&lt;p&gt;Use benchmarks as a hint. Make the final decision from a Player build on your target hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing LitMotion
&lt;/h2&gt;

&lt;p&gt;For LitMotion v2.0.2, the package requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity 2021.3+&lt;/li&gt;
&lt;li&gt;Burst 1.6.0+&lt;/li&gt;
&lt;li&gt;Collections 1.5.1+&lt;/li&gt;
&lt;li&gt;Mathematics 1.0.1+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add the package through Unity Package Manager. In a team project, pin a tag or commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion#v2.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;?path=...&lt;/code&gt; part comes before &lt;code&gt;#v2.0.2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Typical namespaces are:&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;using&lt;/span&gt; &lt;span class="nn"&gt;LitMotion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;LitMotion.Extensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UniTask integration requires &lt;code&gt;com.cysharp.unitask&lt;/code&gt;. TextMeshPro integration requires TMP. If you use custom asmdefs, verify references to &lt;code&gt;LitMotion&lt;/code&gt;, &lt;code&gt;LitMotion.Extensions&lt;/code&gt;, UniTask, and Unity.TextMeshPro as needed.&lt;/p&gt;

&lt;p&gt;The snippets in this article are based on LitMotion v2.0.2's public API and source. I would still compile representative examples in a minimal Unity project before adopting them as team standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core pattern: Create, configure, Bind
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LMotion.Create()&lt;/code&gt; returns a builder. The motion becomes useful when you configure it and finally bind it.&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a Transform:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an arbitrary value:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A capturing lambda can allocate. If this is a hot path, use a state-passing overload instead:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For built-in properties, dedicated &lt;code&gt;BindTo...()&lt;/code&gt; helpers are usually clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ease, delay, and loops
&lt;/h2&gt;

&lt;p&gt;Builder options compose naturally:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutBack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List entrance effects are easy to stagger:&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;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&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;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&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;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.04f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalScaleXYZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&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;For infinite loops:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.95f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.05f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithLoops&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LoopType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yoyo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalScaleXYZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-1&lt;/code&gt; means the motion does not end by itself, so always provide a lifetime through object destruction, a stored handle, or cancellation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;MotionHandle&lt;/code&gt;: stop old animations before starting new ones
&lt;/h2&gt;

&lt;p&gt;Bindings return a &lt;code&gt;MotionHandle&lt;/code&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Replay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryCancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&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;Use &lt;code&gt;TryComplete()&lt;/code&gt; when you want to apply the final value instead of stopping at the current one.&lt;/p&gt;

&lt;p&gt;The bigger design issue is &lt;strong&gt;property ownership&lt;/strong&gt;. If open/close, hover, selection, and click feedback all write the same Scale or Alpha, they can fight each other even if every individual tween is valid.&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;private&lt;/span&gt; &lt;span class="n"&gt;MotionHandle&lt;/span&gt; &lt;span class="n"&gt;fadeHandle&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;SetVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fadeHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryCancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;fadeHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="m"&gt;0.2f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&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;Starting from the current value also avoids a jump when the animation reverses halfway through.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;AddTo(gameObject)&lt;/code&gt; and CancellationToken are different tools
&lt;/h2&gt;

&lt;p&gt;For UI and temporary objects, I normally tie a motion to its owner:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AddTo(gameObject)&lt;/code&gt; cancels the motion when the GameObject is destroyed. It is lifetime ownership, not async-flow control.&lt;/p&gt;

&lt;p&gt;LitMotion v2.0.2's &lt;code&gt;ToUniTask(token)&lt;/code&gt; uses &lt;code&gt;CancelBehavior.Cancel&lt;/code&gt; with &lt;code&gt;cancelAwaitOnMotionCanceled: true&lt;/code&gt;. If you want the intent visible in code, write it explicitly:&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;await&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUniTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CancelBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancelAwaitOnMotionCanceled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If token cancellation should move the motion to its end value, use &lt;code&gt;CancelBehavior.Complete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Decide whether a screen token, GameObject lifetime, explicit handle, or caller token owns the animation. Do not add all of them blindly and assume they mean the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI animation while &lt;code&gt;Time.timeScale == 0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Pause menus often still need animation after gameplay time is stopped.&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MotionScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateIgnoreTimeScale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful schedulers include normal &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;UpdateIgnoreTimeScale&lt;/code&gt;, &lt;code&gt;FixedUpdate&lt;/code&gt;, and the &lt;code&gt;PreLateUpdate&lt;/code&gt; / &lt;code&gt;PostLateUpdate&lt;/code&gt; families.&lt;/p&gt;

&lt;p&gt;The scheduler also changes ordering relative to other scripts, so verify camera, layout, and physics interactions instead of treating this as only a time-scale switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sequence for fixed timelines
&lt;/h2&gt;

&lt;p&gt;LitMotion v2 provides &lt;code&gt;LSequence&lt;/code&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sequenceHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LSequence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.9f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutBack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalScaleXYZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.15f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;20f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="m"&gt;0.2f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithLoops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LoopType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yoyo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalPositionY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Append()&lt;/code&gt; is sequential, &lt;code&gt;Join()&lt;/code&gt; is parallel, &lt;code&gt;Insert()&lt;/code&gt; places a motion at a specific time, and &lt;code&gt;AppendInterval()&lt;/code&gt; adds a wait. &lt;code&gt;Run()&lt;/code&gt; starts the sequence.&lt;/p&gt;

&lt;p&gt;If initial values are being applied too early while assembling a sequence, consider &lt;code&gt;WithImmediateBind(false)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sequence is best for a fixed timeline. Input waits, network waits, branching, and complex cancellation are usually clearer in async code or a state machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  UniTask: &lt;code&gt;await handle&lt;/code&gt; and &lt;code&gt;ToUniTask()&lt;/code&gt; do not cancel the same way
&lt;/h2&gt;

&lt;p&gt;LitMotion supports directly awaiting a &lt;code&gt;MotionHandle&lt;/code&gt;, but in v2.0.2 &lt;code&gt;await handle;&lt;/code&gt; resumes on either completion or cancellation, and &lt;code&gt;MotionAwaiter.GetResult()&lt;/code&gt; does not throw.&lt;/p&gt;

&lt;p&gt;If you need CancellationToken integration or want motion cancellation to propagate as async cancellation, use &lt;code&gt;ToUniTask()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A UI animation that can be triggered repeatedly can share one linked CTS so the next play request cancels the previous one:&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;private&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt; &lt;span class="n"&gt;playCts&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;async&lt;/span&gt; &lt;span class="n"&gt;UniTask&lt;/span&gt; &lt;span class="nf"&gt;PlayAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;playCts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentCts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLinkedTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;playCts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentCts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentCts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fade&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToAlpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;canvasGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localScale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutBack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalScaleXYZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;UniTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;ToCancelableUniTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;ToCancelableUniTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playCts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentCts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;playCts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;currentCts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&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;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;UniTask&lt;/span&gt; &lt;span class="nf"&gt;ToCancelableUniTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;MotionHandle&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUniTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CancelBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancelAwaitOnMotionCanceled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&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;When a new call cancels the previous &lt;code&gt;PlayAsync()&lt;/code&gt;, the previous caller observes cancellation. If that is normal UI control flow, handle it at a clear boundary:&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;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resultPanelAnimation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlayAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Re-entry, screen transition, or destruction is normal here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is not to hide cancellation semantics. Decide where cancellation becomes a normal result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Punch and Shake
&lt;/h2&gt;

&lt;p&gt;Punch is useful for button feedback:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;baseScale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buttonTransform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localScale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Punch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.15f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithFrequency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDampingRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buttonTransform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buttonTransform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second argument is &lt;strong&gt;strength&lt;/strong&gt;, not an end value. Do not pass &lt;code&gt;Vector3.zero&lt;/code&gt; as the start value unless you intentionally want to oscillate around zero scale.&lt;/p&gt;

&lt;p&gt;Shake works similarly:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;basePosition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;basePosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0.35f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithFrequency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDampingRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithRandomSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToLocalPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cameraRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cameraRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For cameras, a dedicated shake-offset transform is often safer than letting Cinemachine, follow code, recoil, and shake all write the same transform.&lt;/p&gt;

&lt;h2&gt;
  
  
  TextMeshPro integration
&lt;/h2&gt;

&lt;p&gt;LitMotion can animate fixed-size strings:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create128Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"&amp;lt;color=#FFD54F&amp;gt;MISSION COMPLETE&amp;lt;/color&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;1.2f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithRichText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithScrambleChars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ScrambleMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uppercase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;128&lt;/code&gt; is a &lt;strong&gt;byte capacity&lt;/strong&gt;, not a character count. Localized Japanese text, emoji, and RichText tags can consume more space than a short English sample. Choose capacity from the longest production string.&lt;/p&gt;

&lt;p&gt;Numeric binding is also convenient:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.8f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoreText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoreText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Formatted binding is supported too:&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="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100000f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoreText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"{0:N2}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoreText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standard formatted path uses &lt;code&gt;string.Format()&lt;/code&gt;, so it can allocate. If this is a hot path, consider the documented ZString integration and profile the full UI update cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  LitMotion.Animation for Inspector workflows
&lt;/h2&gt;

&lt;p&gt;LitMotion.Animation is a separate package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion.Animation#v2.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using Git URL dependencies, I prefer pinning both core and animation packages to the same tag explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"com.annulusgames.lit-motion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion#v2.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"com.annulusgames.lit-motion.animation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/annulusgames/LitMotion.git?path=src/LitMotion/Assets/LitMotion.Animation#v2.0.2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It supports Edit Mode and Play Mode previews and is useful for short Prefab-specific animation tuning.&lt;/p&gt;

&lt;p&gt;I would still define a team boundary: keep shared duration, ease, and cancellation conventions in code or configuration, and leave only local presentation tuning to the Inspector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common production mistakes
&lt;/h2&gt;

&lt;p&gt;A few problems matter more than the exact tween API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to bind:&lt;/strong&gt; &lt;code&gt;LMotion.Create(...).WithEase(...)&lt;/code&gt; is only a builder until you call &lt;code&gt;Bind()&lt;/code&gt; / &lt;code&gt;BindTo...()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple motions write one property:&lt;/strong&gt; separate ownership or cancel the previous handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infinite loops have no shutdown:&lt;/strong&gt; combine them with object lifetime, a handle, or cancellation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pause UI stops:&lt;/strong&gt; use an ignore-time-scale scheduler only where appropriate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Zero allocation" is interpreted too broadly:&lt;/strong&gt; closures, strings, async code, and Unity systems still allocate or consume CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything goes into Sequence:&lt;/strong&gt; use async/state machines for branching and external waits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LitMotion Debugger (&lt;code&gt;Window &amp;gt; LitMotion Debugger&lt;/code&gt;) is useful for finding duplicate motions and missing cancellation. Use it in Editor Play Mode; profile final CPU/GC with the debugger disabled in a Player build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from DOTween
&lt;/h2&gt;

&lt;p&gt;Simple cases are easy to rewrite:&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;// DOTween&lt;/span&gt;
&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DOMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tweening&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&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="c1"&gt;// LitMotion&lt;/span&gt;
&lt;span class="n"&gt;LMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LitMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutCubic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindToPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real migration risk is behavior around the one-liner. Review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kill vs cancel behavior;&lt;/li&gt;
&lt;li&gt;whether cancellation keeps the current value or applies the final value;&lt;/li&gt;
&lt;li&gt;Sequence behavior;&lt;/li&gt;
&lt;li&gt;update timing and &lt;code&gt;Time.timeScale&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;relative tweens;&lt;/li&gt;
&lt;li&gt;DOTween Pro data;&lt;/li&gt;
&lt;li&gt;third-party integrations and internal wrappers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migrate feature by feature, not library-wide, and never let two tween systems own the same property at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adoption checklist
&lt;/h2&gt;

&lt;p&gt;Before standardizing LitMotion in a team, I would verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unity/Burst/Collections/Mathematics requirements;&lt;/li&gt;
&lt;li&gt;custom asmdef references for LitMotion, extensions, UniTask, and TMP;&lt;/li&gt;
&lt;li&gt;Git dependencies pinned to a tag or commit;&lt;/li&gt;
&lt;li&gt;current LitMotion Releases and PrimeTween Changelog;&lt;/li&gt;
&lt;li&gt;representative snippets compiled in a minimal Unity project;&lt;/li&gt;
&lt;li&gt;third-party assets and internal tooling dependencies;&lt;/li&gt;
&lt;li&gt;cancellation rules for destruction, reopening, and repeated input;&lt;/li&gt;
&lt;li&gt;which animations ignore &lt;code&gt;Time.timeScale&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Sequence vs async/await conventions;&lt;/li&gt;
&lt;li&gt;Player-build CPU and GC on target hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also test cancellation halfway through, object destruction, rapid reversal, and repeated clicks—not only normal completion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For a new Unity project in 2026 where tween logic is mainly written in C#, &lt;strong&gt;LitMotion is the first library I would evaluate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;Create -&amp;gt; With -&amp;gt; Bind&lt;/code&gt; model is consistent across Transform values, arbitrary data, TextMeshPro, Punch/Shake, Sequence, and UniTask integration. LitMotion v2 also addresses many of the practical features that once made mature alternatives easier to justify by default.&lt;/p&gt;

&lt;p&gt;PrimeTween remains a strong alternative, especially if target-oriented APIs fit your team better. DOTween is still the pragmatic choice for projects that already have a working DOTween ecosystem.&lt;/p&gt;

&lt;p&gt;Whichever library you choose, the production problems are usually property ownership, re-entry, cancellation, object lifetime, time scale, and the code around the tween—not the number of characters in the tween call.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official docs and source
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://annulusgames.github.io/LitMotion/" rel="noopener noreferrer"&gt;LitMotion documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/annulusgames/LitMotion" rel="noopener noreferrer"&gt;LitMotion GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/annulusgames/LitMotion/blob/v2.0.2/src/LitMotion/Assets/LitMotion/Runtime/Extensions/TextMeshPro/LitMotionTextMeshProExtensions.cs" rel="noopener noreferrer"&gt;LitMotion v2.0.2 TextMeshPro extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/annulusgames/LitMotion/blob/v2.0.2/src/LitMotion/Assets/LitMotion/Runtime/External/UniTask/LitMotionUniTaskExtensions.cs" rel="noopener noreferrer"&gt;LitMotion v2.0.2 UniTask extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KyryloKuzyk/PrimeTween" rel="noopener noreferrer"&gt;PrimeTween GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotween.demigiant.com/documentation.php" rel="noopener noreferrer"&gt;DOTween documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Releases and benchmarks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/annulusgames/LitMotion/releases" rel="noopener noreferrer"&gt;LitMotion Releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KyryloKuzyk/PrimeTween/blob/main/changelog.md" rel="noopener noreferrer"&gt;PrimeTween Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AnnulusGames/TweenPerformance" rel="noopener noreferrer"&gt;TweenPerformance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/6000.5/Documentation/Manual/upm-git.html" rel="noopener noreferrer"&gt;Unity Package Manager Git dependencies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>csharp</category>
      <category>gamedev</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
