Use when writing, reviewing or structuring Unity game code in C# for Unity 6 LTS โ GameObjects and the Transform hierarchy, the MonoBehaviour lifecycle, prefabs and variants, ScriptableObjects for data, scenes and additive loading, the Input System, coroutines versus async and Awaitable, Addressables, and build settings for 2D and 3D. NOT Godot or GDScript (that is `godot`), NOT Unreal Blueprint or C++ engine work (that is `unreal`), NOT the netcode wire protocol (that is `gamedev-multiplayer`).
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Use when writing, reviewing or structuring Unity game code in C# for Unity 6 LTS โ GameObjects and the Transform hierarchy, the MonoBehaviour lifecycle, prefabs and variants, ScriptableObjects for data, scenes and additive loading, the Input System, coroutines versus async and Awaitable, Addressables, and build settings for 2D and 3D. NOT Godot or GDScript (that is `godot`), NOT Unreal Blueprint or C++ engine work (that is `unreal`), NOT the netcode wire protocol (that is `gamedev-multiplayer`).
Idiomatic C# for the current Unity engine โ 2D and 3D. Owns engine-side patterns (components,
lifecycle, prefabs, scenes, data, input, async, builds). Not Godot (godot) or Unreal (unreal);
stops at the netcode wire ().
gamedev-multiplayer
Version contract โ read first
Target: Unity 6 LTS, versioned 6000.x. Current LTS at authoring is Unity 6.3 LTS (6000.3)
(supported to Dec 2027); Unity 6.0 LTS (6000.0) to Oct 2026. Unity 6 dropped the year-based names
(2020/2021/2022 LTS) for the 6000.x scheme. Confirm the project version in
ProjectSettings/ProjectVersion.txt before assuming an API exists.
Ban-list โ never emit these; use the modern replacement:
new Input System (com.unity.inputsystem): InputActionAsset / InputAction / PlayerInput โ ยง6
UnityEngine.Networking (UNet), NetworkServer/NetworkClient, its NetworkBehaviour
removed since 2022.2. โ Netcode for GameObjects (com.unity.netcode.gameobjects) + gamedev-multiplayer
Resources.Load(...) for game content
Addressables (com.unity.addressables) โ ยง8
FindObjectOfType<T>() / FindObjectsOfType<T>()
FindFirstObjectByType<T>() / FindAnyObjectByType<T>() / FindObjectsByType<T>(FindObjectsSortMode.None) (old ones obsolete since 2023.1)
WWW
UnityWebRequest
OnGUI / IMGUI for runtime game UI
UI Toolkit (runtime) or uGUI; keep IMGUI for editor tools only
Setting transform.position on a Rigidbody in Update
Rigidbody.MovePosition/MoveRotation in FixedUpdate โ ยง4
GameObject.Find/FindWithTag by string in Update
cache the ref in Awake, or [SerializeField] it
Prefer the new Input System even for single-player prototypes. Keep Debug.Log out of hot loops and
strip it from ships.
1. GameObject / Component model & Transform hierarchy
A GameObject is an empty container; behaviour comes from Components you attach
(Transform, Rigidbody, MeshRenderer, and your MonoBehaviour scripts). Every GameObject has
exactly one Transform (or RectTransform under a Canvas) giving position/rotation/scale.
Get components: TryGetComponent(out var rb) (no-alloc, preferred), GetComponent<T>(),
GetComponentInChildren/InParent<T>(). Cache in Awake โ GetComponent in Update is a perf sink.
Hierarchy: transform.parent, transform.SetParent(newParent, worldPositionStays: true),
transform.childCount, foreach (Transform child in transform). Child world = parent ร local.
Same object model for 2D and 3D: 2D uses Rigidbody2D/Collider2D/SpriteRenderer; 3D uses
Rigidbody/Collider/MeshRenderer. Never mix 2D and 3D physics components on one body.
A Prefab is a reusable GameObject template (an asset). A Prefab Variant inherits from a base
prefab and overrides selected properties/children โ like subclassing for assets (e.g. Enemy base โ
EnemyFast variant). Edit the base and variants inherit the change unless they override that value.
Spawn with Instantiate(prefab, position, rotation, parent); it returns the concrete type
(var e = Instantiate(_enemyPrefab); with [SerializeField] Enemy _enemyPrefab;). Store the
serialized reference; do not Resources.Load it.
Destroy with Destroy(gameObject) (end of frame) or Destroy(obj, delay). Never DestroyImmediate
at runtime. For frequently spawned objects (bullets, VFX) pool them (UnityEngine.Pool.ObjectPool<T>)
instead of Instantiate/Destroy churn.
Editor-only prefab authoring uses PrefabUtility (wrap in #if UNITY_EDITOR); absent in builds.
3. Scenes & scene management (additive loading)
A Scene holds a set of GameObjects. Register scenes in Build Profiles โ Scene List (Unity 6;
formerly Build Settings) so they can load by index or name.
using UnityEngine.SceneManagement;
// Additive: keep the current scene, layer another on top (e.g. UI, a level chunk).var op = SceneManager.LoadSceneAsync("Level_02", LoadSceneMode.Additive);
op.completed += _ => Debug.Log("Level_02 loaded");
await op; // Unity 6: AsyncOperation is awaitable// Unload just that scene later:await SceneManager.UnloadSceneAsync("Level_02");
LoadSceneMode.Single (default) unloads everything first; Additive composes scenes โ the basis of
streaming worlds, persistent manager scenes, and separate UI scenes.
SceneManager.MoveGameObjectToScene moves an object between loaded scenes; DontDestroyOnLoad objects
survive Single loads (use for a bootstrap/services scene). Prefer LoadSceneAsync to avoid a hitch.
4. MonoBehaviour lifecycle
Order per frame and where each concern belongs (full ordering in references/lifecycle-deep-dive.md):
Method
When
Put here
Awake
once, on instantiation (even if disabled)
cache GetComponent, self-init, no cross-object refs
OnEnable
each time the object/component enables
subscribe to events, register callbacks
Start
once, before first Update, only if active
cross-object wiring (other objects' Awake has run)
FixedUpdate
fixed timestep (physics tick), 0..n / frame
all physics โ Rigidbody forces/MovePosition, ApplyForce
Update
every frame
input reads, game logic, timers (scale by Time.deltaTime)
LateUpdate
every frame, after all Update
camera follow, look-at, anything tracking moved objects
OnDisable
each disable / before destroy
unsubscribe everything subscribed in OnEnable
OnDestroy
once, on destruction
release native/unmanaged resources, final cleanup
Rules that bite:
Physics goes in FixedUpdate, never Update. Reading input in FixedUpdate drops events โ
read input in Update, cache intent, apply forces in FixedUpdate.
Frame-rate independence: multiply per-frame movement by Time.deltaTime (Update) โ but
FixedUpdate already runs at a fixed Time.fixedDeltaTime.
Awake runs even on inactive objects; Start does not run until the object is active. Cross-object
references belong in Start, not Awake.
Every OnEnable subscription needs a matching OnDisable unsubscription or you leak and get
double-fires after re-enable.
5. ScriptableObjects for data/config; serialization
A ScriptableObject is a data asset that lives outside any scene โ ideal for config, tuning tables,
enemy/item definitions, and event channels. One asset is shared by reference (no per-instance copy),
so it saves memory and lets designers tweak values without touching code.
[SerializeField] private vs public: default to [SerializeField] private + a public
property/method. It shows in the Inspector and survives serialization without letting arbitrary code
mutate the field โ encapsulation intact. Use bare public only for genuine data bags.
Serialization rules (they trip everyone):
Unity serializes: public or [SerializeField] fields of supported types (primitives, string,
Unity types, enums, arrays/List<T> of those, and [Serializable] plain classes/structs).
Unity does not serialize: properties (use [field: SerializeField] public float X { get; private set; }),
static, readonly, const, Dictionary<,> (use two lists or a serializable wrapper), or
polymorphic references โ for interface/abstract fields use [SerializeReference].
Do not hold runtime mutable state on a shared ScriptableObject expecting it to reset โ it persists in
the Editor between play sessions. Keep SOs for config; put mutable per-instance state on MonoBehaviours.
6. Input System (new) โ with a legacy contrast
The new Input System (com.unity.inputsystem) is enabled by default in Unity 6 and is device-agnostic
and event-driven. Define an Input Action Asset with Action Maps (e.g. "Player", "UI") and
Actions (e.g. "Move" = Value/Vector2, "Jump" = Button). Generate a C# wrapper or use the
PlayerInput component. Full setup, PlayerInput, and rebinding in references/input-system.md.
// New Input System โ subscribe to the generated actions (no per-frame polling).using UnityEngine.InputSystem;
publicsealedclassPlayerController : MonoBehaviour
{
InputSystem_Actions _controls; // generated C# class from the .inputactions asset
Vector2 _move;
voidAwake() => _controls = new InputSystem_Actions();
voidOnEnable()
{
_controls.Player.Enable();
_controls.Player.Move.performed += ctx => _move = ctx.ReadValue<Vector2>();
_controls.Player.Move.canceled += _ => _move = Vector2.zero;
_controls.Player.Jump.performed += _ => Jump();
}
voidOnDisable() => _controls.Player.Disable();
}
Legacy contrast (do not write this in new code):
// LEGACY Input Manager โ string axes, per-frame polling, device-blind. Avoid.float x = Input.GetAxis("Horizontal");
if (Input.GetButtonDown("Jump")) Jump();
The new system also polls if you prefer: _controls.Player.Move.ReadValue<Vector2>() in Update.
7. Coroutines vs async/await vs Awaitable
Use
Reach for
Why
Frame-sequenced gameplay (timers, tweens, "wait then spawn")
Coroutine or Awaitable
frame-aware yields; auto-tie to object lifetime
Unity-native async in Unity 6 (default for new async gameplay)
Awaitable
main-thread, low-alloc (pooled), frame + thread awaits, cancels via destroyCancellationToken
True background CPU work / library Task APIs
async Task + await
thread pool; but marshal back โ Unity APIs are main-thread only
// Coroutine โ classic, MonoBehaviour-bound, stops when the object disables/destroys.IEnumerator SpawnWave()
{
yieldreturnnewWaitForSeconds(1f);
Instantiate(_enemyPrefab);
}
voidStart() => StartCoroutine(SpawnWave());
// Awaitable (Unity 6) โ the modern replacement. Cancels automatically on destroy.async Awaitable SpawnWaveAsync()
{
await Awaitable.WaitForSecondsAsync(1f, destroyCancellationToken);
await Awaitable.NextFrameAsync(destroyCancellationToken);
Instantiate(_enemyPrefab);
}
Rules:
Coroutines run only while the MonoBehaviour is enabled; they die silently on disable. No return
value; exceptions across yield are awkward.
Awaitable: never await the same instance twice (pooled โ undefined behavior). Pass
destroyCancellationToken so it stops when the object is destroyed. Switch threads with
Awaitable.BackgroundThreadAsync() / Awaitable.MainThreadAsync().
Task: does not auto-cancel on destroy โ thread a CancellationToken yourself, and never touch
Unity API off the main thread (it throws). Prefer Awaitable in Unity 6 unless you need Task.
8. Addressables (intro) + the Resources anti-pattern
Addressables (com.unity.addressables) is the modern content-loading system: assets get string
addresses, load asynchronously, and can ship in the build or from a remote CDN with dependency tracking
and memory-managed release.
using UnityEngine.AddressableAssets;
var handle = Addressables.InstantiateAsync("Prefabs/Boss");
GameObject boss = await handle.Task; // or handle.Completed += ...// when done: Addressables.ReleaseInstance(boss); // release to free memory
Resources/ is an anti-pattern for game content: everything under it is forced into the build,
loaded synchronously, bloats size/startup, and can't be patched or streamed. Reserve it for tiny
always-needed defaults; migrate real content to Addressables.
9. Build settings, platforms & player settings
Build Profiles (Unity 6, File โ Build Profiles) supersede the old Build Settings window: per-platform
scene list, target platform, and overridable player settings.
Scripting backend:IL2CPP (AOT โ required for iOS/consoles/WebGL, faster, harder to reverse)
vs Mono (JIT, Editor/desktop, faster iteration). Set per platform in Player Settings.
Player Settings: product/company name, bundle identifier, icons, orientation, API compatibility
level (.NET Standard 2.1 default), managed stripping. 2D vs 3D is a template choice, not a
different build path.
WebGL/mobile specifics and the shipping checklist: references/build-and-platforms.md + gamedev-shipping.
Guardrails / gotchas
==/!= null on a destroyed UnityEngine.Object works (overloaded), but ?. and cached refs treat
a destroyed object as non-null. Re-check with if (obj) after possible destruction.
Don't mix input systems: with PlayerInput, don't also Input.GetKey โ in "New" mode legacy calls throw.
One Rigidbody per body (child colliders fine); non-uniform scale breaks physics bodies.
Coroutines/InvokeRepeating stop on SetActive(false); async Tasks keep running unless cancelled.
No allocation (new, LINQ, boxing) in Update/FixedUpdate hot paths.
Related skills
gamedev-multiplayer โ networking/netcode (Netcode for GameObjects); this skill hands off the wire.
gamedev-physics โ deep physics tuning (joints, solver, determinism) beyond "put it in FixedUpdate".
gamedev-shaders โ Shader Graph / HLSL, URP/HDRP materials and rendering.