unity — Cursor rule: what it does, how to install
This rule sets Unity C# conventions for project layout, naming, lookups, and memory use. Add it to Cursor when you want consistent guidance across a Unity codebase.
.cursor/rules/unity.mdcWhat unity does
The rule covers folder and scene organization, naming, access modifiers, and XML comments. It recommends keeping project assets under _Project, separating third-party packages, and using prefabs for reusable objects. Public members use PascalCase; private fields start with an underscore.
For runtime performance, it advises caching singleton references instead of calling FindObjectOfType from Update, using IDs for Animator and Shader properties, and reusing frequently created objects through a pool. The pooling example uses UnityEngine.Pool, which requires Unity 2021 or later. Projects on older Unity versions cannot use that example as written.
In Cursor, save the rule as .cursor/rules/unity.mdc. Its **/* glob applies it across files, rather than limiting it to selected file types.
What it helps with
- organize Unity assets into project folders
- standardize C# class and field names
- document public members with XML comments
- avoid repeated object searches in
Update - reuse spawned objects to reduce allocations
Example prompts for the agent
- Reorganize this Unity project's folders and scenes according to the rule.
- Check the C# scripts for naming issues and add XML comments to public members.
- Change projectile creation to use an object pool, accounting for this project's Unity version.
How to install unity
Manually: put the files in place
.cursor/rules/unity.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/unity.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/unity.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/unity.mdManually: put the files in place
.clinerules/unity.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about unity
How do I install unity in Cursor?
Create .cursor/rules/unity.mdc and put the rule content in that file. Its **/* glob applies across files.
What does the unity rule do?
It gives Unity C# guidance on project structure, code conventions, and runtime performance practices.
Which Unity version does the pooling example require?
The example uses UnityEngine.Pool, which requires Unity 2021 or later. It will need changes for an earlier Unity version.
Source text
unity.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for Unity C# development, focusing on code organization, performance, and common pitfalls to ensure a clean, maintainable, and performant codebase.
globs: **/*
---
# unity Best Practices
This document outlines our definitive guidelines for Unity C# development. Adhere to these rules to maintain a high-quality, performant, and collaborative codebase.
## 1. Code Organization & Project Structure
### 1.1 Project Folder Hierarchy
Organize assets by type within a `_Project` root, separating custom assets from third-party packages. Avoid root-level clutter.
❌ BAD
```
Assets/
MyScript.cs
PlayerModel.fbx
Level1.unity
ThirdPartyAsset/
...
```
✅ GOOD
```
Assets/
_Project/
Audio/
Materials/
Models/
Prefabs/
Scenes/
Scripts/
Core/
Gameplay/
UI/
Textures/
Plugins/ // Reserved for external plugins
Standard Assets/ // Reserved for Unity's standard assets
ThirdPartyAsset/ // Leave imported packages intact
```
### 1.2 Scene Hierarchy Structure
Prefab everything. Group GameObjects logically within scenes for clarity and easier navigation.
❌ BAD
```
- Player
- Enemy1
- UI_HealthBar
- MainCamera
- Light
- Environment_Tree
```
✅ GOOD
```
- Managers
- GameManager
- AudioManager
- Cameras
- MainCamera
- Lights
- Directional Light
- UI
- Canvas
- HUD
- PauseMenu
- World
- Terrain
- Environment
- Trees
- Rocks
- Gameplay
- Actors
- Player
- Enemy (Prefab instances)
- Items
- Collectible (Prefab instances)
- _DynamicObjects // Parent for all runtime instantiated objects
```
### 1.3 Naming Conventions
Use PascalCase for public members and filenames, camelCase for private fields and local variables. Prefix private fields with `_`.
❌ BAD
```csharp
public class player_controller : MonoBehaviour {
public float movementSpeed;
private GameObject target;
void Start() { }
}
```
✅ GOOD
```csharp
public class PlayerController : MonoBehaviour
{
[SerializeField] private float _movementSpeed = 5f; // Serialized private field
private GameObject _target; // Private field
public float MovementSpeed => _movementSpeed; // Public property
void Start()
{
// Local variable
GameObject tempObject = new GameObject("Temp");
}
}
```
## 2. Common Patterns & Anti-patterns
### 2.1 Explicit Access Modifiers
Always declare access modifiers (`public`, `private`, `protected`). Do not rely on default `private`.
❌ BAD
```csharp
class MyClass {
int value; // Defaults to private, but implicit
void DoSomething() { }
}
```
✅ GOOD
```csharp
public class MyClass
{
private int _value;
public void DoSomething() { }
}
```
### 2.2 XML Documentation
Document all public classes, methods, and fields using XML `///` comments.
❌ BAD
```csharp
public class PlayerHealth : MonoBehaviour {
public int currentHealth;
public void TakeDamage(int amount) { /* ... */ }
}
```
✅ GOOD
```csharp
/// <summary>
/// Manages the player's health and handles damage/healing.
/// </summary>
public class PlayerHealth : MonoBehaviour
{
/// <summary>The current health of the player.</summary>
public int CurrentHealth { get; private set; } = 100;
/// <summary>
/// Applies damage to the player, reducing their health.
/// </summary>
/// <param name="amount">The amount of damage to apply.</param>
public void TakeDamage(int amount)
{
CurrentHealth -= amount;
if (CurrentHealth <= 0)
{
Debug.Log("Player defeated!");
}
}
}
```
### 2.3 Singleton Access
Cache references to singletons or use a dedicated manager. Avoid `FindObjectOfType` in `Update` or frequently called methods.
❌ BAD
```csharp
public class GameManager : MonoBehaviour {
void Update() {
// Repeatedly calling FindObjectOfType is a performance drain
PlayerController player = FindObjectOfType<PlayerController>();
if (player != null) {
// ...
}
}
}
```
✅ GOOD
```csharp
// PlayerController.cs
public class PlayerController : MonoBehaviour
{
public static PlayerController Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
}
}
// GameManager.cs
public class GameManager : MonoBehaviour
{
private PlayerController _player;
void Start()
{
_player = PlayerController.Instance; // Cache reference once
}
void Update()
{
if (_player != null)
{
// Use the cached reference
}
}
}
```
## 3. Performance Considerations
### 3.1 String vs. ID Lookups
Always use ID-based lookups for Animator, Material, and Shader properties to avoid runtime string hashing.
❌ BAD
```csharp
animator.SetBool("IsWalking", true);
material.SetFloat("_Glossiness", 0.5f);
```
✅ GOOD
```csharp
private static readonly int _isWalkingHash = Animator.StringToHash("IsWalking");
private static readonly int _glossinessID = Shader.PropertyToID("_Glossiness");
void SetProperties(Animator animator, Material material)
{
animator.SetBool(_isWalkingHash, true);
material.SetFloat(_glossinessID, 0.5f);
}
```
### 3.2 Garbage Collection (GC) Avoidance
Minimize per-frame allocations. Use object pooling, reuse collections, and avoid unnecessary boxing or string concatenations in hot paths.
#### 3.2.1 Object Pooling
Reuse frequently instantiated objects (e.g., projectiles, particles) instead of destroying and recreating them.
❌ BAD
```csharp
void FireProjectile() {
Instantiate(projectilePrefab, transform.position, Quaternion.identity);
}
```
✅ GOOD
```csharp
using UnityEngine.Pool; // Requires Unity 2021+
public class ProjectileSpawner : MonoBehaviour
{
public GameObject projectilePrefab;
private ObjectPool<GameObject> _projectilePool;
void Awake()
{
_projectilePool = new ObjectPool<GameObject>(
() => Instantiate(projectilePrefab),
(obj) => obj.SetActive(true),
(obj) => obj.SetActive(false),
(obj) => Destroy(obj),
collectionCheck: false,
defaultCapacity: 20,
maxSize: 100
);
}
public void FireProjectile()
{
GameObject projectile = _projectilePool.Get();
projectile.transform.position = transform.position;
// ... set up projectile, then return to pool when done
}
}
```
#### 3.2.2 Collection Reuse
Declare collections once and clear them for reuse instead of allocating new ones in loops.
❌ BAD
```csharp
void Update() {
List<Enemy> enemiesInRange = new List<Enemy>(); // Allocates every frame
// ... populate and use list
}
```
✅ GOOD
```csharp
private readonly List<Enemy> _enemiesInRange = new List<Enemy>(); // Allocated once
void Update() {
_enemiesInRange.Clear(); // Reuse existing list
// ... populate and use list
}
```
#### 3.2.3 Non-Allocating Physics APIs
Use `NonAlloc` versions of physics queries to prevent GC spikes.
❌ BAD
```csharp
RaycastHit[] hits = Physics.RaycastAll(transform.position, transform.forward, 10f);
```
✅ GOOD
```csharp
private RaycastHit[] _raycastHits = new RaycastHit[10]; // Pre-allocate array
void CheckForHits() {
int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _raycastHits, 10f);
for (int i = 0; i < numHits; i++) {
// Process _raycastHits[i]
}
}
```
### 3.3 `UnityEngine.Object` Null Comparisons
Comparisons against `UnityEngine.Object` subclasses are more expensive than plain C# objects. Cache references and avoid in tight loops.
❌ BAD
```csharp
void Update() {
if (myGameObject != null) { // Expensive null check every frame
// ...
}
}
```
✅ GOOD
```csharp
private GameObject _myGameObject; // Reference cached in Start/Awake
void Start() {
_myGameObject = GameObject.Find("MyObject");
}
void Update() {
if (_myGameObject) { // Unity's implicit bool conversion is optimized
// ...
}
}
```
### 3.4 Debug Logging
Wrap debug logs in `[Conditional]` attributes to strip them from non-development builds, preventing performance overhead and log spam in production.
❌ BAD
```csharp
void Update() {
Debug.Log("Player position: " + transform.position); // Always included
}
```
✅ GOOD
```csharp
using System.Diagnostics; // For [Conditional]
using UnityEngine;
public static class AppLogger
{
[Conditional("ENABLE_LOGS")] // Define ENABLE_LOGS in Player Settings for debug builds
public static void Log(string message)
{
Debug.Log($"[{Time.frameCount}] {message}");
}
[Conditional("ENABLE_LOGS")]
public static void LogWarning(string message)
{
Debug.LogWarning($"[{Time.frameCount}] {message}");
}
}
public class PlayerMovement : MonoBehaviour
{
void Update()
{
AppLogger.Log("Player position: " + transform.position); // Stripped in release builds
}
}
```
## 4. Common Pitfalls & Gotchas
### 4.1 Spaces in File/Folder Names
Avoid spaces in asset names. Unity's command-line tools and some platforms have issues with paths containing spaces. Use PascalCase.
❌ BAD
```
Assets/My Game Scene.unity
Assets/Player Controller.cs
```
✅ GOOD
```
Assets/MyGameScene.unity
Assets/PlayerController.cs
```
### 4.2 Modifying Third-Party Assets
Never modify imported third-party asset files directly. Copy them to your `_Project` folder and modify the copy. This prevents update conflicts.
❌ BAD
```
// Directly modifying a script inside Assets/ThirdPartyAsset/Scripts/EnemyAI.cs
namespace ThirdParty {
public class EnemyAI : MonoBehaviour {
// Your custom changes here
}
}
```
✅ GOOD
```
// Create Assets/_Project/Scripts/CustomEnemyAI.cs
namespace MyGame {
public class CustomEnemyAI : ThirdParty.EnemyAI { // Inherit if possible
// Your custom changes/overrides here
}
}
// Or, if inheritance isn't suitable, copy the file and modify the copy in _Project.
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc