Iron & Timber the economy is the militia — studio progress concept pitch site ↗

Unity 6.3 LTS — Current Best Practices

Last verified: 2026-09-12

Modern Unity 6 patterns that may not be in the LLM's training data. These are production-ready recommendations as of Unity 6.3 LTS.


Project Setup

Use Unity 6.3 LTS for Production

Choose the Right Render Pipeline


Scripting

Use C# 9+ Features (Unity 6 Supports C# 9)

// ✅ Record types for data
public record PlayerData(string Name, int Level, float Health);

// ✅ Init-only properties
public class Config {
    public string GameMode { get; init; }
}

// ✅ Pattern matching
var result = enemy switch {
    Boss boss => boss.Enrage(),
    Minion minion => minion.Flee(),
    _ => null
};

Async/Await for Asset Loading

// ✅ Modern async pattern
public async Task<GameObject> LoadEnemyAsync(string key) {
    var handle = Addressables.LoadAssetAsync<GameObject>(key);
    return await handle.Task;
}

Use Source Generators for Serialization (Unity 6+)

// ✅ Source-generated serialization (faster, less reflection)
[GenerateSerializer]
public partial struct PlayerStats : IComponentData {
    public int Health;
    public int Mana;
}

DOTS/ECS (Production-Ready in Unity 6.3 LTS)

Use ISystem (Not ComponentSystem)

// ✅ Modern unmanaged ISystem (Burst-compatible)
public partial struct MovementSystem : ISystem {
    public void OnCreate(ref SystemState state) { }

    public void OnUpdate(ref SystemState state) {
        foreach (var (transform, speed) in
            SystemAPI.Query<RefRW<LocalTransform>, RefRO<MoveSpeed>>()) {
            transform.ValueRW.Position += speed.ValueRO.Value * SystemAPI.Time.DeltaTime;
        }
    }
}

Use IJobEntity for Parallel Jobs

// ✅ IJobEntity (replaces IJobForEach)
[BurstCompile]
public partial struct DamageJob : IJobEntity {
    public float DeltaTime;

    void Execute(ref Health health, in DamageOverTime dot) {
        health.Value -= dot.DamagePerSecond * DeltaTime;
    }
}

// Schedule it
var job = new DamageJob { DeltaTime = SystemAPI.Time.DeltaTime };
job.ScheduleParallel();

Input

Use Input System Package (Not Legacy Input)

// ✅ Input Actions (rebindable, cross-platform)
using UnityEngine.InputSystem;

public class PlayerInput : MonoBehaviour {
    private PlayerControls controls;

    void Awake() {
        controls = new PlayerControls();
        controls.Gameplay.Jump.performed += ctx => Jump();
    }

    void OnEnable() => controls.Enable();
    void OnDisable() => controls.Disable();
}

Create Input Actions asset in editor, generate C# class via inspector.


UI

Use UI Toolkit for Runtime UI (Production-Ready in Unity 6)

// ✅ UI Toolkit (replaces UGUI for new projects)
using UnityEngine.UIElements;

public class MainMenu : MonoBehaviour {
    void OnEnable() {
        var root = GetComponent<UIDocument>().rootVisualElement;

        var playButton = root.Q<Button>("play-button");
        playButton.clicked += StartGame;

        var scoreLabel = root.Q<Label>("score");
        scoreLabel.text = $"High Score: {PlayerPrefs.GetInt("HighScore")}";
    }
}

UXML (UI structure) + USS (styling) = HTML/CSS-like workflow.


Asset Management

Use Addressables (Not Resources)

// ✅ Addressables (async, memory-efficient)
using UnityEngine.AddressableAssets;

public async Task SpawnEnemyAsync(string enemyKey) {
    var handle = Addressables.InstantiateAsync(enemyKey);
    var enemy = await handle.Task;

    // Cleanup: release when destroyed
    Addressables.ReleaseInstance(enemy);
}

Benefits: Async loading, remote content delivery, better memory control.


Rendering

Use RenderGraph API for Custom Passes (URP/HDRP)

// ✅ RenderGraph API (Unity 6+)
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) {
    using (var builder = renderGraph.AddRasterRenderPass<PassData>("My Pass", out var passData)) {
        // Setup pass
        builder.SetRenderFunc((PassData data, RasterGraphContext context) => {
            // Execute commands
        });
    }
}

Replaces: Old CommandBuffer.Execute() pattern.


Performance

Use Burst Compiler + Jobs System

// ✅ Burst-compiled job (massive performance gain)
[BurstCompile]
struct ParticleUpdateJob : IJobParallelFor {
    public NativeArray<float3> Positions;
    public NativeArray<float3> Velocities;
    public float DeltaTime;

    public void Execute(int index) {
        Positions[index] += Velocities[index] * DeltaTime;
    }
}

// Schedule
var job = new ParticleUpdateJob {
    Positions = positions,
    Velocities = velocities,
    DeltaTime = Time.deltaTime
};
job.Schedule(positions.Length, 64).Complete();

20-100x faster than equivalent C# code.


Use GPU Instancing for Repeated Objects

// ✅ GPU Instancing (thousands of objects, minimal draw calls)
Graphics.RenderMeshInstanced(
    new RenderParams(material),
    mesh,
    0,
    matrices // NativeArray<Matrix4x4>
);

Memory Management

Use NativeContainers (Not Managed Arrays in Jobs)

// ✅ NativeArray (no GC, Burst-compatible)
NativeArray<int> data = new NativeArray<int>(1000, Allocator.TempJob);
// ... use in job
data.Dispose(); // Manual cleanup required

// ✅ Or use using statement
using var data = new NativeArray<int>(1000, Allocator.TempJob);
// Auto-disposed

Multiplayer

Use Netcode for GameObjects (Official)

// ✅ Unity's official netcode
using Unity.Netcode;

public class Player : NetworkBehaviour {
    private NetworkVariable<int> health = new NetworkVariable<int>(100);

    [ServerRpc]
    public void TakeDamageServerRpc(int damage) {
        health.Value -= damage;
    }
}

Replaces: UNet (deprecated), MLAPI (renamed to Netcode for GameObjects).


Testing

Use Unity Test Framework (NUnit-based)

// ✅ Play Mode Test
[UnityTest]
public IEnumerator Player_TakesDamage_HealthDecreases() {
    var player = new GameObject().AddComponent<Player>();
    player.Health = 100;

    player.TakeDamage(25);
    yield return null; // Wait one frame

    Assert.AreEqual(75, player.Health);
}

Debugging

Use Logging Best Practices

// ✅ Structured logging (Unity 6+)
using UnityEngine;

Debug.Log($"Player {playerName} scored {score} points");

// ✅ Conditional compilation for debug code
#if UNITY_EDITOR || DEVELOPMENT_BUILD
    Debug.DrawRay(transform.position, direction, Color.red);
#endif

Summary: Unity 6 Tech Stack

Feature Use This (2026) Avoid This (Legacy)
InputInput System packageInput class
UIUI ToolkitUGUI (Canvas)
ECSISystem + IJobEntityComponentSystem
RenderingURP + RenderGraphBuilt-in pipeline
AssetsAddressablesResources
JobsBurst + IJobParallelForCoroutines for heavy work
MultiplayerNetcode for GameObjectsUNet

Sources:


Unity 6.3-Specific Additions (Best Practices)

*Added 2026-09-12 from live WebSearch of the 6.3 LTS release notes and Upgrade Guide. The sections above cover the broad 2022 LTS → Unity 6 migration; this section covers deltas introduced in 6.3 itself, filtered for Iron & Timber (2D isometric, PC, single-player).*

2D Renderer: Mesh + Sprite Interop

Unity 6.3's 2D Universal Render Pipeline supports using **Mesh Renderer and Skinned Mesh Renderer alongside 2D sprites in the same scene**, with a compatible shader. These meshes can:

Relevance to this project: if a future prop (a 3D-modeled cart, animal, or building generated via the connected Meshy/Tripo tools) needs to sit convincingly in the isometric settlement alongside 2D sprite settlers, this is the native 6.3 path — no manual billboard/fake-lighting workaround needed. Verify shader compatibility (must be a shader that supports 2D Lights) before relying on this.

Shader Graph: Terrain Materials, Unified Compiler

Shader Graph in 6.3 can author terrain materials/shaders for both URP and HDRP through the same unified compiler. For the seasonal palette system (gradient-map/LUT approach recommended by prior architecture research — see docs/research/ or the concept doc's Technical Considerations), this means:

Bloom Performance: Kawase / Dual Filtering

URP's Bloom Volume Override now offers Kawase and Dual filtering options, faster than the previous default — Dual for general mobile-class performance, Kawase as the fastest option at low resolution.

Relevance: low priority for a PC-only target, but worth using if bloom is part of the Weathered Almanac visual treatment (e.g., forge glow, kiln smoke highlights) and frame budget is tight — see technical-preferences.md's 16.6ms frame budget.

Build Profiles UI

The Build Profiles window is reworked with an Add Settings button to select only the specific settings to configure per profile, rather than exposing the full settings surface. Relevant when /test-setup configures the CI build pipeline — use profile-scoped settings rather than global Player Settings edits where possible.

Networking: HTTP/2 Default

UnityWebRequest now defaults to HTTP/2 on all platforms when the server supports it (early Android tests: ~40% server load reduction, ~15-20% on-device CPU reduction). Not currently relevant — this project has no networking per technical-preferences.md — but worth knowing if telemetry, analytics, or a future online leaderboard is added.

Sources