This is the official RealXP Lab document that outlines how we write and structure Unity C# code. It's meant to promote clarity and consistency across our projects, and help externs build strong habits around clean, professional code.

This guide draws inspiration from:

Formatting Guidelines

Naming conventions
  • PascalCase: Names of namespaces, classes, structs, methods, enumerations, properties, files, directories, public fields, and constant fields

  • camelCase: Names of local variables and method parameters

  • _camelCase: Names of private, protected, internal and protected internal instance fields (no m_, s_, or k_ prefixes)

  • Always explicitly specify access modifiers (e.g. private, public, internal)

  • Variables: Use nouns. Be descriptive and avoid abbreviations. (e.g. playerHealth, enemyCount, scoreMultiplier)

  • Units: Add units to measurable quantities (e.g. rotationRadians, transitionSeconds, colorFactor0To1)

  • Booleans: Prefix with a verb (e.g. isAnimated, hasPowerUp, canJump)

  • Enums: Use singular nouns (e.g. WeaponType)

    • Bitwise enums marked with [System.Flags] are the exception — use plural names (e.g. AttackModes)

  • Avoid redundant naming: Don’t repeat the class name in member names unnecessarily (e.g. use Score instead of PlayerScore inside a Player class)

  • Methods: Prefix with a verb (e.g. StartGame(), GetDirection())

    • Methods returning bool should ask a question (e.g. IsGameOver(), HasStartedTurn())

  • Interfaces: Prefix with capital I followed by a descriptive adjective or phrase (e.g. IDamageable, ISaveable)

  • Events:

    • Use verb phrases to name events, and make sure they clearly describe the state change

    • Use the present participle (e.g. OpeningDoor) to indicate an event that occurs before the action completes

    • Use the past participle (e.g. DoorOpened) to indicate an event that occurs after the action has completed

    • Use the System.Action delegate by default. Only define custom EventArgs types when necessary

    • Prefix event raising methods with On (e.g. OnDoorOpened())

    • Prefix event handler methods with the subject name and underscore (e.g. GameEvents_DoorOpened)

  • MonoBehaviour files: File name must match the MonoBehaviour class. Only one MonoBehaviour per file

Whitespace rules
  • Use Allman style (brace on a new line)

  • Indentation: 4 spaces, no tabs

  • Keep line lengths under 120 characters

  • Always use braces, even for one-line if/else

  • One declaration per line: int health;

  • One statement per line

  • One assignment per statement

  • Space before flow conditions: if (x == y)

  • No space between method name and parentheses: Jump()

  • No space inside array brackets: items[i]

  • Space after commas: DoSomething(x, y, z)

  • No space after opening or before closing parentheses: DoSomething(x)

  • No space after a cast: var x = (float)y

Organization
  • Use namespaces to prevent naming conflicts with global or external types

  • Add using directives at the top of the file to avoid repeated namespace references

    • Always place System imports first, then sort the rest alphabetically

    • Strip unused using directives - keep only the minimally required set

  • Organize class members in the following order:

    1. Fields

    2. Properties

    3. Events

    4. Unity methods (Awake, Start, Update, OnControllerColliderHit, etc.)

    5. Public methods

    6. Private methods

    7. Nested classes or structs

  • Avoid #region unless absolutely necessary

Comments & inspector attributes
  • Avoid commenting obvious logic, prefer self-documenting names and structure

  • Assume the what is clear from the code; use comments to explain why—intent or reasoning

  • Start with a capital letter and avoid periods unless it's a full sentence

  • Use TODO comments to mark follow-ups, pair it with a ticket or owner when possible

  • Avoid leaving commented-out code in PRs, use version control

  • Use // --- SECTION NAME --- (ALL CAPS label, three hyphens each side) to mark logical sections if needed

    // ✅ Prefer this:
    if (player.IsAlive())
    
    // ❌ Instead of:
    // Check if player has enough health to continue
    if (player.health > 0)
    
    // Prevents jumping if already in the air
    if (!isGrounded) return;
    
    // Skip update if game is paused
    if (GameManager.IsPaused) return;
    
    // TODO(TASK-123): Replace with pooled object for performance
    Instantiate(explosionPrefab, transform.position, Quaternion.identity);
    
    // TODO(John Doe): Implement this method
    private void LevelUp
    
    


  • Use XML summary tags for all public types (classes, structs, enums, interfaces) and public methods

  • Use one inspector attribute per line. Place attributes immediately above the member, without blank lines

  • Prefer [Tooltip] over code comments for fields exposed in the Inspector

  • For auto-properties, use [field: SerializeField] to expose the backing field while maintaining encapsulation

Coding Guidelines

Constants & readonly
  • Use const where possible; otherwise use readonly

  • Prefer named constants to magic numbers

Collections & types
  • For method inputs, use the most restrictive collection types (IEnumerable<T>, IReadOnlyList<T>, etc.)

  • Use List<T> for outputs unless lazy evaluation is needed

  • Prefer List<T> over arrays unless size is fixed, data is multidimensional, or required

Tuples & return types
  • Prefer named classes over Tuple<> for return types

  • Consider return objects for complex/multi-value returns

Strings
  • Prefer string interpolation for readability (e.g. $"Score: {playerScore}")

  • Prefer a reused StringBuilder in hot paths that build large/variable text.

LINQ & delegates
  • Avoid long LINQ chains, prefer readability

  • Prefer method/extension syntax over LINQ query expressions (e.g. Where() over from...in...where)

  • Avoid .ForEach() on lists unless it's a one-liner

  • Avoid LINQ in methods that run every frame (Update, FixedUpdate, etc.)

  • Use ?.Invoke() to call delegates

Extension methods
  • Prefer adding directly to class, use extension methods only when modifying source is not possible

  • Limit extensions to general-purpose methods

Structs vs classes
  • Use structs only for small, immutable, short-lived types

  • Default to class unless performance or memory constraints justify struct

Miscellaneous
  • Use == null for null checks over is null or object.ReferenceEquals(x, null)

  • Use var only when the type is obvious from context

  • Prefer switch over long if-else chains when appropriate

  • Avoid excessively long methods and large parameter lists

  • Avoid method overloading unless necessary

Example

// Using directives are sorted: System first, then alphabetically.
using System;
using UnityEngine;

// Enums use singular nouns.
/// <summary>
/// The state of the player's current action.
/// </summary>
public enum PlayerState
{
    Idle,
    Walking,
    Jumping
}

// Bitwise enums use [Flags] and plural nouns.
/// <summary>
/// Bitwise flags representing the player's available attack modes.
/// </summary>
[Flags]
public enum AttackModes
{
    None = 0,                          // 000000
    Melee = 1,                         // 000001
    Ranged = 2,                        // 000010
    Special = 4,                       // 000100

    MeleeAndSpecial = Melee | Special  // 000101
}

// Interfaces are prefixed with 'I'.
/// <summary>
/// Defines an entity that can receive damage.
/// </summary>
public interface IDamageable
{
    // Public properties use PascalCase.
    int Health { get; }

    // Methods returning bool ask a question.
    bool CanBeDamaged();

    // Method parameters use camelCase.
    void ApplyDamage(float damageAmount);
}

/// <summary>
/// A gold-standard example demonstrating the C# style guide.
/// </summary>
public class StyleGuideExample : MonoBehaviour, IDamageable
{
    // Prescribed member order: Fields -> Properties -> Events -> Unity Methods -> etc.

    // 1. FIELDS
    [Header("Player Stats")]
    [Tooltip("The time elapsed, measured in seconds.")]
    [SerializeField] 
    private float _elapsedTimeInSeconds;

    [Tooltip("Is the player currently immune to damage?")]
    [SerializeField] 
    private bool _isInvincible;

    // Private fields are _camelCase.
    private PlayerState _currentState;

    // 2. PROPERTIES
    [Tooltip("The current health of the player.")]
    // This is the preferred pattern for a field that can be set in the
    // Inspector but should only be changed internally via code.
    [field: SerializeField]
    public int Health { get; private set; } = 100;

    // 3. EVENTS
    public event Action<int> HealthChanging;
    public event Action<int> HealthChanged;

    // 4. UNITY LIFECYCLE METHODS
    private void Update()
    {
        // Keep Unity lifecycle methods clean by calling helper methods.
        HandleInput();
    }

    // 5. PUBLIC METHODS (implementing the interface)
    /// <summary>
    /// Checks if the player is currently able to take damage.
    /// </summary>
    /// <returns>True if the player's health is above 0 and is not invincible.</returns>
    public bool CanBeDamaged()
    {
        return Health > 0 && !_isInvincible;
    }

    /// <summary>
    /// Applies a specified amount of damage to the player.
    /// </summary>
    /// <param name="damageAmount">The amount of damage to apply.</param>
    public void ApplyDamage(float damageAmount)
    {
        if (!CanBeDamaged())
        {
            return;
        }

        // var is used here because the type (int) is obvious.
        var damageAsInt = Mathf.RoundToInt(damageAmount);

        OnHealthChanging(damageAsInt);
        Health -= damageAsInt;
        OnHealthChanged();
    }

    // 6. PRIVATE METHODS (helpers, event raisers, etc.)
    private void HandleInput()
    {
        // Braces are always used for clarity.
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // TODO(TASK-451): Refactor jump logic into its own method.
            Debug.Log("Jump key pressed.");
        }
    }

    // Event-raising methods are prefixed with 'On' and are typically private.
    private void OnHealthChanging(int amount)
    {
        // Use the null-conditional operator to safely invoke events.
        HealthChanging?.Invoke(amount);
    }

    private void OnHealthChanged()
    {
        HealthChanged?.Invoke(Health);

        // String interpolation is preferred for readability.
        Debug.Log($"Player health changed to: {Health}"

RealXP Lab

Share