Awake() for self-initialization: singleton setup, internal state, component caching.
Start() only when initialization depends on other objects being ready.
OnEnable() / OnDisable() for registering / unregistering listeners — prevents stale references.
Prefer FixedUpdate() for physics and time-critical logic.
Avoid per-frame Update() when a custom update cycle (via a central manager) is available.
Singleton Pattern
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
Rules:
Guard against duplicates in Awake() with Destroy(this) or Destroy(gameObject).
Call DontDestroyOnLoad(gameObject) for persistent managers.
Never use singletons for data that should be per-scene.
Serialization & Inspector
Use [SerializeField] for private fields that need Inspector exposure — never make them public just for the Inspector.
Use [System.Serializable] for nested data classes shown in the Inspector.
Avoid exposing runtime-only fields to the Inspector.
Use [Header("Section")] and [Tooltip("...")] to organize complex inspectors.
Performance (Hot Paths)
Zero allocations in Update, FixedUpdate, and listener callbacks.
Cache component references in Awake() — never call GetComponent<T>() per frame.
Pre-allocate List<T> with expected capacity.
Prefer foreach over LINQ in hot paths — LINQ allocates enumerators.
Use object pooling for frequently instantiated/destroyed objects.
Prefer CompareTag("Enemy") over gameObject.tag == "Enemy" (avoids string alloc).
Interfaces & Custom Update Cycles
public interface IUpdateListener
{
void OnTick(float deltaTime);
}
// Register in OnEnable, unregister in OnDisable
private void OnEnable() => UpdateManager.Instance.Register(this);
private void OnDisable() => UpdateManager.Instance.Unregister(this);
Benefits:
Decoupled communication between systems.
Controlled execution order via the manager.
Easy to pause/resume groups of listeners.
ScriptableObject for Data
Use ScriptableObject for shared configuration, game balance data, and event channels.
Create assets via [CreateAssetMenu(fileName = "New Config", menuName = "Game/Config")].
Prefer ScriptableObject events over direct references for loose coupling.
Assembly Definitions
Use .asmdef files to split code into assemblies for faster compile times.
Separate Editor code into its own assembly with Editor platform only.
Keep test assemblies separate with [TestAssembly] references.
Code Style
One primary responsibility per script/class.
Keep methods under ~30 lines — extract helpers for complex logic.
Use enums for fixed value sets (e.g., UpdateCycle, DamageType).
Use #region sparingly — prefer small classes over large regions.
Always add <summary> XML doc comments on public types and methods.
Common Pitfalls
Coroutine leak: Always StopCoroutine or StopAllCoroutines in OnDisable.
Null after Destroy: Unity overloads == on UnityEngine.Object — use if (obj != null) or the null-conditional pattern, but never rely on C# is null.
Awake order: Don't depend on Awake() order between scripts — use Start() or explicit initialization order via [DefaultExecutionOrder].
String-based APIs: Avoid SendMessage, Invoke("MethodName") — use direct calls or events.
Raw content
Copy into your project — e.g. .instructions.md, .agent.md, or SKILL.md
## Naming Conventions
- PascalCase for public fields, properties, methods, and class names.
- camelCase for private fields, local variables, and parameters.
- Prefix interfaces with `I` (e.g., `IUpdateListener`).
- Use descriptive names — avoid abbreviations unless universally understood (`fps`, `id`).
## MonoBehaviour Lifecycle
- `Awake()` for self-initialization: singleton setup, internal state, component caching.
- `Start()` only when initialization depends on other objects being ready.
- `OnEnable()` / `OnDisable()` for registering / unregistering listeners — prevents stale references.
- Prefer `FixedUpdate()` for physics and time-critical logic.
- Avoid per-frame `Update()` when a custom update cycle (via a central manager) is available.
## Singleton Pattern
```csharp
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
```
Rules:
- Guard against duplicates in `Awake()` with `Destroy(this)` or `Destroy(gameObject)`.
- Call `DontDestroyOnLoad(gameObject)` for persistent managers.
- Never use singletons for data that should be per-scene.
## Serialization & Inspector
- Use `[SerializeField]` for private fields that need Inspector exposure — never make them public just for the Inspector.
- Use `[System.Serializable]` for nested data classes shown in the Inspector.
- Avoid exposing runtime-only fields to the Inspector.
- Use `[Header("Section")]` and `[Tooltip("...")]` to organize complex inspectors.
## Performance (Hot Paths)
- **Zero allocations** in `Update`, `FixedUpdate`, and listener callbacks.
- Cache component references in `Awake()` — never call `GetComponent<T>()` per frame.
- Pre-allocate `List<T>` with expected capacity.
- Prefer `foreach` over LINQ in hot paths — LINQ allocates enumerators.
- Use object pooling for frequently instantiated/destroyed objects.
- Prefer `CompareTag("Enemy")` over `gameObject.tag == "Enemy"` (avoids string alloc).
## Interfaces & Custom Update Cycles
```csharp
public interface IUpdateListener
{
void OnTick(float deltaTime);
}
// Register in OnEnable, unregister in OnDisable
private void OnEnable() => UpdateManager.Instance.Register(this);
private void OnDisable() => UpdateManager.Instance.Unregister(this);
```
Benefits:
- Decoupled communication between systems.
- Controlled execution order via the manager.
- Easy to pause/resume groups of listeners.
## ScriptableObject for Data
- Use `ScriptableObject` for shared configuration, game balance data, and event channels.
- Create assets via `[CreateAssetMenu(fileName = "New Config", menuName = "Game/Config")]`.
- Prefer ScriptableObject events over direct references for loose coupling.
## Assembly Definitions
- Use `.asmdef` files to split code into assemblies for faster compile times.
- Separate Editor code into its own assembly with `Editor` platform only.
- Keep test assemblies separate with `[TestAssembly]` references.
## Code Style
- One primary responsibility per script/class.
- Keep methods under ~30 lines — extract helpers for complex logic.
- Use enums for fixed value sets (e.g., `UpdateCycle`, `DamageType`).
- Use `#region` sparingly — prefer small classes over large regions.
- Always add `<summary>` XML doc comments on public types and methods.
## Common Pitfalls
- **Coroutine leak**: Always `StopCoroutine` or `StopAllCoroutines` in `OnDisable`.
- **Null after Destroy**: Unity overloads `==` on `UnityEngine.Object` — use `if (obj != null)` or the null-conditional pattern, but never rely on C# `is null`.
- **Awake order**: Don't depend on `Awake()` order between scripts — use `Start()` or explicit initialization order via `[DefaultExecutionOrder]`.
- **String-based APIs**: Avoid `SendMessage`, `Invoke("MethodName")` — use direct calls or events.