Style Guide For Unity and C#: Write Clean And Understandable Code
๐ŸŽฃ

Style Guide For Unity and C#: Write Clean And Understandable Code

image
โ€œANY FOOL CAN WRITE CODE THAT A COMPUTER CAN UNDERSTAND. GOOD PROGRAMMERS WRITE CODE THAT HUMANS CAN UNDERSTAND.โ€

โ€“ Martin Fowler, author of Refactoring

Through years of hands-on experience developing Unity projects, both independently and for various clients, I've discovered that one of the most challenging aspects is not just defining an efficient coding style, but adhering to it consistently.

๐ŸŒŸ This guide is the culmination of my journey. It's a comprehensive handbook that underscores principles for writing simple, effective, and readable code.

It offers direction on code style, interface usage, method naming conventions, event handling, code architecture and the use of properties, among others.

In addition, it presents valuable tips for maintaining a unified and well-structured Unity project. It's an ideal resource for both budding programmers and seasoned developers!

๐Ÿ“’ Note 01โ†’ The following guide reflects the style that I've truly appreciated and have been utilizing for years. However, feel free to adapt it to your personal style.

Principles to follow for a clean C# style guide

image

KISS (keep it simple, stupid) ๐Ÿง

Letโ€™s face it: Engineers and developers can overcomplicate things, even though computing and programming are hard enough . Use the KISS principle of โ€œkeep it simple, stupidโ€ as a guide for finding the simplest solution to the problem at hand .

Donโ€™t reinvent the wheel ๐Ÿ›ž

Thereโ€™s no need to reinvent the wheel if a proven and simple technique solves your challenge. Why use a fancy new technology just for the sake of using it? Unity already includes numerous solutions in its Scripting API . For example, if the existing Hexagonal Tilemap works for your strategy game, skip writing your own. The best code you can write is no code at all.

The YAGNI principle

The related YAGNI principle (โ€œyou arenโ€™t gonna need itโ€) instructs you to implement features only as you need them. Donโ€™t worry about features that you might need once the stars align .

โžก๏ธBuild the simplest thing that you need now and build it to work.

C# Code Style ๐ŸŠ

The code style you adopt plays a crucial role in maintaining readability and understanding of your codebase.

This section showcases how to handle public and private variables, along with naming conventions that enhance clarity.

// EXAMPLE: public and private variables
public float DamageMultiplier = 1.5f;
public float MaxHealth;
public bool IsInvincible;
private bool _isDead;
private float _currentHealth;
// parameters
public void InflictDamage(float damage, bool isSpecialDamage)
{
    // local variable
    int totalDamage = damage;
    // local variable versus public member variable
    if (isSpecialDamage)
    {
       totalDamage *= DamageMultiplier;
}
    // local variable versus private member variable
    if (totalDamage > _currentHealth)
{
/// ...
} 
}
  • Avoid redundant names: If your class is called Player, you donโ€™t need to create member variables called PlayerScore or PlayerTarget. Trim them down to Score or Target .
  • Prefix Booleans with a verb: These variables indicate a true or false value. Often they are the answer to a question, such as โ€“ is the player running? Is the game over? Prefix them with a verb to make their meaning more apparent. Often this is paired with a description or condition, e .g ., isDead, isWalking, hasDamageMultiplier, etc .

Interfaces

// EXAMPLE: Interfaces
public interface IKillable
{
    void Kill();
}
public interface IDamageable<T>
{
    void Damage(T damageTaken);
}

Methods ๐Ÿš€

Methods perform actions, so apply these rules to name them accordingly:

  • Start the name with a verb: Add context if necessary . e .g .,
  • GetDirection, FindTarget, etc .

  • Use camel case for parameters: Format parameters passed into the
  • method like local variables.

  • Methods returning bool should ask questions: Much like Boolean variables themselves, prefix methods with a verb if they return a true-false condition. This phrases them in the form of a question, e .g IsGameOver, HasStartedTurn .
// EXAMPLE: Methods start with a verb
public void SetInitialPosition(float x, float y, float z)
{
    transform.position = new Vector3(x, y, z);
}
// EXAMPLE: Methods ask a question when they return bool
public bool IsNewPosition(Vector3 currentPosition)
{
    return (transform.position == newPosition);
}

Events and event handlers ๐Ÿ’ซ

Events in C# implement the Observer pattern, where a subject (publisher) notifies a list of observers (subscribers) without tightly coupling the objects involved. Here are some best practices for events and related methods:

  1. Name the event with a verb phrase: Use present or past participle for events "before" or "after" (e.g., "OpeningDoor" or "DoorOpened").
  2. Use System.Action delegate for events: Action<T> delegate can handle events with 0 to 16 input parameters and a void return type, saving code.
  3. Prefix event-raising method (in the subject) with "On": The subject that invokes the event should have a method prefixed with "On" (e.g., "OnOpeningDoor" or "OnDoorOpened").
  4. Prefix event-handling method (in the observer) with the subject's name and underscore (_): If the subject is "GameEvents," the observers can have a method called "GameEvents_OpeningDoor" or "GameEvents_DoorOpened."
  5. Create custom EventArgs only as necessary: Create a new type of EventArgs to pass custom data to your event, either inherited from System.EventArgs or from a custom struct.

Using Properties in Unity C#

image

Properties in Unity C# provide a flexible way to read, write, or compute class values.

They behave like public member variables but are special methods called accessors. Properties encapsulate data, hiding it from unwanted changes by the user or external objects, and can have read-write, read-only, or write-only access.

To keep properties consistent in your code, follow these guidelines:

  1. Use expression-bodied properties for single-line read-only properties: This returns the private backing field. Example:
public class PlayerHealth
{
    private int maxHealth;
    public int MaxHealth => maxHealth;
}
  1. Use the { get; set; } syntax for all other cases: If you want to expose a public property without specifying a backing field, use the Auto-Implemented Property. Example:
public class PlayerHealth
{
    private int _maxHealth;
    public int MaxHealth
    {
        get => _maxHealth;
        set => _maxHealth = value;
    }
}
  1. Control access with private setters: Make the setter private if you don't want to give write access.
  2. Align closing braces: For multi-line code blocks, align the closing brace with the opening brace.

Class Organization in Unity

Welcome to the world of Unity class organization! ๐ŸŒ๐ŸŽฎ Efficient structuring of classes in Unity is key to a cleaner codebase and smoother game development process.

image

Naming Conventions ๐Ÿค

To maintain a consistent naming convention for your Unity project, follow these guidelines:

  1. Folders:
    • Use PascalCase (capitalize the first letter of each word).
    • Keep names short and descriptive.
    • Avoid spaces and special characters.
    • Group related assets in subfolders.
    • Examples: Scripts, Materials, Prefabs, Textures, Audio.

  2. Scripts:
    • Use PascalCase for file names and class names.
    • Make the file name and class name the same.
    • Keep names descriptive and related to the script's purpose.
    • Examples: PlayerController, EnemySpawner, ScoreManager.

  3. GameObjects in the scene:
    • Use camelCase (capitalize the first letter of each word except the first).
    • Prefix with a category or type for easy identification.
    • Keep names short and descriptive.
    • Avoid spaces and special characters.
    • Examples: playerCharacter, enemySpawner, uiCanvas, mainCamera.

  4. Additional tips:
    • Variables and Functions: Use camelCase for variable and Pascal Case for function names. Keep them descriptive and related to their purpose.
    • Examples: playerSpeed, enemyHealth, MovePlayer, SpawnEnemy.

    • Constants: Use PascalCase with a 'k' prefix to denote constants.
    • Examples: kMaxHealth, kSpeedMultiplier.

    • Namespaces: Use PascalCase for namespaces, and keep them short and related to the specific functionality or module.
    • Examples: Audio, InventorySystem, UI.

By following these naming conventions, you'll maintain a clean and organized project structure that is easy to understand and navigate for yourself and any team members working on the project.

๐Ÿ“’ Note 02 โ†’ Most of the content in the following guide is derived from the official Unity's free e-book on creating a C# style guide. Make sure to check it out for further insight.

๐ŸŽŠ Conclusion

In the end, good code style is all about ensuring your code is not just functional, but also readable and maintainable. So, embrace simplicity, clarity, and consistency in your coding journey. Happy coding! ๐Ÿš€๐Ÿ’ป๐ŸŒŸ

๐Ÿ’ก
Decide a consistent naming scheme for your team and implement those rules in your style guide.

Resources

  1. Create a C# style guide: Write cleaner code that scales โ†’ Unity Guide
  2. Common C# Coding Conventions โ†’ Microsoft C#.
  3. C# at Google Style Guide โ†’ Google Style Guide

โœ๏ธ Author

image

Marco Mignano ๐Ÿ‘‹ โžก๏ธ Passionate Unity Game Developer Marco - coding aficionado, video game enthusiast, and self-proclaimed piazza addict. Constantly conquering new challenges, one line of code at a time. Got an exciting project? Let's make your game next game together!

You may also like ๐Ÿ’™