Fix release for 1.1.1: the minimal path end to end, engine commands that run on Godot, Unity and Unreal, hook warnings you can see, and verdicts that say when they could not check. Details in CHANGELOG.md; upgrade steps in UPGRADING.md (v1.1.1 → v1.1.2). Fixes #67 Fixes #131 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WuJn2FdynEybfL5KC697nJ
9.9 KiB
Unity 6.3 LTS — Current Best Practices
Last verified: 2026-02-13
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
- Tech Stream (6.4+): Latest features, less stable
- LTS (6.3): Production-ready, 2-year support (until Dec 2027)
Choose the Right Render Pipeline
- URP (Universal): Mobile, cross-platform, good performance ✅ Recommended for most games
- HDRP (High Definition): High-end PC/console, photorealistic
- Built-in: Deprecated, avoid for new projects
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.
This is URP's API. RecordRenderGraph overrides URP's ScriptableRenderPass.
HDRP custom passes are a separate API: a CustomPass subclass, run from a Custom
Pass Volume, overrides Setup(ScriptableRenderContext renderContext, CommandBuffer cmd),
Execute(CustomPassContext ctx) and Cleanup() (HDRP 17.3, Create a Custom
Pass in a C# script:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.3/manual/Custom-Pass-Scripting.html).
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);
}
Command Line — Tests
Documented (Unity Manual, Run tests from the command line; Test Framework
command-line reference):
<Unity editor> -runTests -batchmode -projectPath <path> -testPlatform <platform> -testResults <file>.
-batchmode removes the need for user input. -testPlatform takes EditMode,
PlayMode (Play Mode tests in the Editor) or a player platform; with no value,
tests run in Edit Mode. -testResults defaults to the project root, and the
file follows NUnit's XML format: the <test-run> element carries
testcasecount, result, passed and failed, and each <test-case> a
fullname and a result of Passed, Failed, Inconclusive or Skipped.
Unity defines no common exit codes for test runs.
Observed on Unity 6000.3.23f1 — not in the docs:
-runTestsexits 0 when every test passes and 2 when one fails; 1 is a compile error (no results file is written), 3 a failed run, 4 an unknown-testPlatform. It exits by itself — no-quit.- Tests outside
Assets/are never compiled: the run reportstestcasecount="0",result="Passed"and exits 0 — a false pass. - Without the
com.unity.test-frameworkpackage,-runTestshangs rather than failing. UnityonPATHmay be Unity's separate CLI, not the editor; it rejects-batchmodeand-versionwith exit 2. Name the editor executable by its full path.-batchmode -quit -projectPath . -logFile -exits 1 on a compile error and 0 when clean.
Sources:
- Run tests from the command line: https://docs.unity3d.com/6000.3/Documentation/Manual/test-framework/run-tests-from-command-line.html
- Test Framework command-line arguments: https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html
- NUnit test result XML format: https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html
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) |
|---|---|---|
| Input | Input System package | Input class |
| UI | UI Toolkit | UGUI (Canvas) |
| ECS | ISystem + IJobEntity | ComponentSystem |
| Rendering | URP + RenderGraph | Built-in pipeline |
| Assets | Addressables | Resources |
| Jobs | Burst + IJobParallelFor | Coroutines for heavy work |
| Multiplayer | Netcode for GameObjects | UNet |
Sources: