From unity
Unity 6 C# scripting guide. Use when writing MonoBehaviour scripts, handling lifecycle events (Awake, Start, Update, FixedUpdate), using coroutines or async/await (Awaitable), working with ScriptableObjects, events, delegates, or core APIs like Vector3, Quaternion, Time, Debug. Based on Unity 6.3 LTS documentation.
npx claudepluginhub cdata/aria-skills --plugin unityThis skill uses the workspace's default tool permissions.
C# scripts (`.cs` files) are stored in the `Assets` folder. Scripts gain Unity functionality by inheriting from built-in types:
Provides Unity C# scripting patterns covering MonoBehaviour lifecycle, coroutines, async/await, physics APIs, raycasts, collisions, animations, NavMesh, pooling, singletons, ECS, Jobs, and Burst.
Unity 6 core concepts and architecture guide. Use when working with GameObjects, Components, Transforms, Scenes, Prefabs, ScriptableObjects, or Unity project structure. Covers the entity-component architecture, object hierarchy, tags, layers, and project conventions. Based on Unity 6.3 LTS documentation.
Generates production-ready Unity C# templates for MonoBehaviour, ScriptableObject, Editor scripts, and tests with best practices. Useful for new scripts or project setup.
Share bugs, ideas, or general feedback.
C# scripts (.cs files) are stored in the Assets folder. Scripts gain Unity functionality by inheriting from built-in types:
Scripts operate in two contexts:
UnityEngine namespace)UnityEditor namespace, place in Editor folders)MonoBehaviours always exist as a Component of a GameObject. The lifecycle event functions execute in a strict order. You cannot rely on the order in which the same event function is invoked for different GameObjects unless configured via Script Execution Order settings.
INITIALIZATION
|
v
[Awake] ---------> Called when script instance loads (once per lifetime)
|
v
[OnEnable] ------> Called when object/component becomes enabled
|
v
(SceneManager.sceneLoaded fires here -- after OnEnable, before Start)
|
v
[Start] ---------> Called before first frame Update (once per lifetime)
|
|
| +===========================================+
| | PHYSICS LOOP (fixed timestep) |
| | |
+->| [FixedUpdate] --> Internal Physics ------>|
| | | |
| | [yield WaitForFixedUpdate resumes] |
| +===========================================+
|
v
[Update] --------> Called once per frame
|
v
[yield null / yield WaitForSeconds resumes]
|
v
(Internal Animation Update)
| [OnAnimatorMove]
| [OnAnimatorIK]
|
v
[LateUpdate] ----> Called after all Update functions complete
|
v
RENDERING
| [OnWillRenderObject]
| [OnPreCull] [OnBecameVisible/Invisible]
| [OnPreRender]
| [OnRenderObject]
| [OnPostRender]
| [OnRenderImage]
|
v
[OnGUI] ---------> Called for GUI rendering events
|
v
[yield WaitForEndOfFrame resumes]
|
v
DEACTIVATION / TEARDOWN
|
v
[OnDisable] -----> Called when component/object is disabled
|
v
[OnDestroy] -----> Called before object destruction
| Callback | Timing | Use For |
|---|---|---|
Awake() | Script instance loads | One-time init, cache references |
OnEnable() | Component enabled | Subscribe to events |
Start() | Before first Update | Init that depends on other Awake() calls |
FixedUpdate() | Fixed timestep (default 0.02s) | Physics calculations, Rigidbody forces |
Update() | Every frame | Input, non-physics game logic |
LateUpdate() | After all Update calls | Camera follow, post-Update adjustments |
OnDisable() | Component disabled | Unsubscribe from events |
OnDestroy() | Before destruction | Final cleanup |
// 3D Physics
void OnCollisionEnter(Collision collision) { }
void OnCollisionStay(Collision collision) { }
void OnCollisionExit(Collision collision) { }
void OnTriggerEnter(Collider other) { }
void OnTriggerStay(Collider other) { }
void OnTriggerExit(Collider other) { }
// 2D Physics
void OnCollisionEnter2D(Collision2D collision) { }
void OnTriggerEnter2D(Collider2D other) { }
| Property | Purpose |
|---|---|
destroyCancellationToken | Token raised when MonoBehaviour is destroyed (for async cancellation) |
didAwake | Whether Awake has been called |
didStart | Whether Start has been called |
runInEditMode | Allow script execution in editor |
Full lifecycle reference: references/monobehaviour-lifecycle.md
Unity supports two patterns for operations spanning multiple frames.
Methods that suspend with yield return and resume based on the yield instruction.
IEnumerator Fade()
{
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
{
c.a = alpha;
renderer.material.color = c;
yield return new WaitForSeconds(0.1f);
}
}
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine(Fade());
}
}
Yield Instructions:
yield return null -- Resume next frame (after Update)yield return new WaitForSeconds(t) -- Resume after t secondsyield return new WaitForFixedUpdate() -- Resume after FixedUpdateyield return new WaitForEndOfFrame() -- Resume after renderingyield return new WaitUntil(() => condition) -- Resume when condition is trueyield return StartCoroutine(other) -- Wait for nested coroutineImportant: Coroutines run on the main thread. Disabling the MonoBehaviour via enabled = false does NOT stop coroutines. Deactivating the GameObject or destroying the MonoBehaviour does stop them.
Awaitable is Unity's custom async type -- usually more efficient than iterator-based coroutines. It is pooled to limit allocations.
async Awaitable SampleSchedulingJobsForNextFrame()
{
await Awaitable.EndOfFrameAsync();
var jobHandle = ScheduleSomethingWithJobSystem();
await Awaitable.NextFrameAsync();
jobHandle.Complete();
}
Awaitable Methods:
Awaitable.NextFrameAsync() -- Resume next frameAwaitable.FixedUpdateAsync() -- Resume at next FixedUpdateAwaitable.EndOfFrameAsync() -- Resume at end of frameAwaitable.WaitForSecondsAsync(float) -- Resume after delayAwaitable.MainThreadAsync() -- Force continuation on main threadAwaitable.BackgroundThreadAsync() -- Force continuation on background threadCritical constraint: Awaitable instances are pooled -- never await the same instance more than once. Multiple awaits cause undefined behavior.
| Feature | Coroutine | Awaitable |
|---|---|---|
| Return values | No | Yes (Awaitable<T>) |
| Thread switching | No | Yes |
| Memory allocation | Per-yield overhead | Pooled, minimal |
| Cancellation | Manual StopCoroutine | CancellationToken support |
| Error handling | No try/catch | Full try/catch/finally |
Full async reference: references/coroutines-and-async.md
public class Health : MonoBehaviour
{
public event System.Action<float> OnDamageTaken;
public event System.Action OnDeath;
private float _hp = 100f;
public void TakeDamage(float amount)
{
_hp -= amount;
OnDamageTaken?.Invoke(amount);
if (_hp <= 0f)
OnDeath?.Invoke();
}
}
public class UIHealthBar : MonoBehaviour
{
[SerializeField] private Health _health;
void OnEnable()
{
_health.OnDamageTaken += HandleDamage;
}
void OnDisable()
{
_health.OnDamageTaken -= HandleDamage;
}
private void HandleDamage(float amount)
{
// Update UI
}
}
using UnityEngine.Events;
public class GameManager : MonoBehaviour
{
public UnityEvent OnGameStart;
public UnityEvent<int> OnScoreChanged;
public void StartGame()
{
OnGameStart?.Invoke();
}
}
[CreateAssetMenu(menuName = "Events/Void Event Channel")]
public class VoidEventChannel : ScriptableObject
{
private System.Action _onEventRaised;
public void RaiseEvent()
{
_onEventRaised?.Invoke();
}
public void Subscribe(System.Action listener) => _onEventRaised += listener;
public void Unsubscribe(System.Action listener) => _onEventRaised -= listener;
}
ScriptableObjects are serializable Unity types derived from UnityEngine.Object. They exist as independent project assets, not attached to GameObjects. Use them for shared data, configuration, and event channels.
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
public string prefabName;
public int numberOfPrefabsToCreate;
public Vector3[] spawnPoints;
}
Key behaviors:
EditorUtility.SetDirty()Full ScriptableObject reference: references/scriptableobjects.md
Unity serializes fields that meet ALL conditions:
public OR has [SerializeField] attributestatic, const, or readonlySerializable types: primitives, enums (32-bit or smaller), Unity built-in types (Vector2, Vector3, Rect, Color, AnimationCurve, etc.), [Serializable] custom classes/structs, UnityEngine.Object references, List<T> and arrays of any above type.
Key attributes:
[SerializeField] private float _speed = 5f; // Serialize private field
[field: SerializeField] public float Speed { get; private set; } // Auto-property
[NonSerialized] public float tempValue; // Exclude from serialization
[HideInInspector] public float hiddenValue; // Serialize but hide from Inspector
[SerializeReference] private IMyInterface _impl; // Polymorphic serialization
Not supported: Multidimensional arrays, jagged arrays, dictionaries, nested containers. Use ISerializationCallbackReceiver for custom serialization of unsupported types.
// Static direction shortcuts
Vector3.zero; // (0, 0, 0)
Vector3.one; // (1, 1, 1)
Vector3.up; // (0, 1, 0)
Vector3.forward; // (0, 0, 1)
Vector3.right; // (1, 0, 0)
// Common operations
float dist = Vector3.Distance(a, b);
float dot = Vector3.Dot(a.normalized, b.normalized);
Vector3 cross = Vector3.Cross(a, b);
Vector3 smoothed = Vector3.Lerp(from, to, t);
Vector3 moved = Vector3.MoveTowards(current, target, maxDelta);
Vector3 projected = Vector3.ProjectOnPlane(velocity, groundNormal);
Properties: magnitude, sqrMagnitude (use for comparisons -- avoids sqrt), normalized.
// Creation
Quaternion.identity; // No rotation
Quaternion.Euler(0f, 90f, 0f); // From Euler angles (degrees)
Quaternion.LookRotation(direction, Vector3.up); // Face a direction
Quaternion.AngleAxis(45f, Vector3.up); // Rotate around axis
Quaternion.FromToRotation(Vector3.up, normal); // Rotation between directions
// Interpolation
Quaternion.Slerp(from, to, t); // Spherical interpolation
Quaternion.Lerp(from, to, t); // Linear interpolation
// Operations
float angle = Quaternion.Angle(a, b); // Angle in degrees (0-180)
Vector3 rotatedPoint = rotation * point; // Rotate a vector
Quaternion combined = rotA * rotB; // Combine rotations
Never modify x, y, z, w directly. Use Euler(), AngleAxis(), or LookRotation().
Time.deltaTime // Seconds since last frame (use in Update)
Time.fixedDeltaTime // Fixed timestep interval (use in FixedUpdate)
Time.time // Time since game start
Time.timeScale // 0 = paused, 1 = normal, 2 = double speed
Time.unscaledDeltaTime // Ignores timeScale (for UI animations during pause)
Debug.Log("Message");
Debug.LogWarning("Warning");
Debug.LogError("Error");
Debug.DrawRay(origin, direction, Color.red, duration);
Debug.DrawLine(start, end, Color.green, duration);
public class PlayerMovement : MonoBehaviour
{
private Rigidbody _rb;
private Transform _transform;
void Awake()
{
_rb = GetComponent<Rigidbody>();
_transform = transform; // Cache the transform property
}
void FixedUpdate()
{
// Use cached references -- never call GetComponent in Update/FixedUpdate
_rb.AddForce(Vector3.up * 10f);
}
}
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
public class AsyncExample : MonoBehaviour
{
async void Start()
{
try
{
await LoadAndProcessAsync(destroyCancellationToken);
}
catch (OperationCanceledException) { }
}
async Awaitable LoadAndProcessAsync(CancellationToken token)
{
await Awaitable.BackgroundThreadAsync();
// Heavy computation here (off main thread)
var result = ComputeExpensiveData();
await Awaitable.MainThreadAsync();
// Back on main thread -- safe to use Unity API
ApplyResult(result);
}
}
public static async Awaitable AwaitableUntil(Func<bool> condition, CancellationToken token)
{
while (!condition())
{
token.ThrowIfCancellationRequested();
await Awaitable.NextFrameAsync();
}
}
| Anti-Pattern | Problem | Fix |
|---|---|---|
GetComponent<T>() in Update() | Allocates and searches every frame | Cache in Awake() |
GameObject.Find() in Update() | Expensive string search every frame | Cache reference or use serialized field |
Repeated new Vector3() in hot paths | Unnecessary constructor overhead each frame | Use Vector3.zero, Vector3.one, or cache reusable values |
Modifying Quaternion.x/y/z/w directly | Produces invalid rotations | Use Euler(), AngleAxis(), LookRotation() |
Physics logic in Update() | Inconsistent at variable framerates | Use FixedUpdate() for Rigidbody forces |
Time.deltaTime in FixedUpdate() | Works (Unity returns fixedDeltaTime implicitly) but is unclear to readers | Use Time.fixedDeltaTime explicitly for clarity |
Forgetting to unsubscribe events in OnDisable | Memory leaks, null reference errors | Always unsubscribe in OnDisable() |
await-ing same Awaitable twice | Undefined behavior (pooled instances) | Await once, or wrap with .AsTask() |
Empty Update() / FixedUpdate() methods | Unity still calls them (overhead) | Remove empty event functions |
String-based Invoke("MethodName", t) | No compile-time safety, breaks on rename | Use coroutines or Awaitable instead |
ObjectPool<T>), see references/object-pool.md