โ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
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 .,
- Use camel case for parameters: Format parameters passed into the
- 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 .
GetDirection, FindTarget, etc .
method like local variables.
// 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:
- Name the event with a verb phrase: Use present or past participle for events "before" or "after" (e.g., "OpeningDoor" or "DoorOpened").
- 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.
- 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").
- 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."
- 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#
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:
- 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;
}
- 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;
}
}
- Control access with private setters: Make the setter private if you don't want to give write access.
- 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.
Naming Conventions ๐ค
To maintain a consistent naming convention for your Unity project, follow these guidelines:
- 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.
- 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.
- 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.
- Additional tips:
- Variables and Functions: Use camelCase for variable and Pascal Case for function names. Keep them descriptive and related to their purpose.
- Constants: Use PascalCase with a 'k' prefix to denote constants.
- Namespaces: Use PascalCase for namespaces, and keep them short and related to the specific functionality or module.
Examples: Scripts
, Materials
, Prefabs
, Textures
, Audio
.
Examples: PlayerController
, EnemySpawner
, ScoreManager
.
Examples: playerCharacter
, enemySpawner
, uiCanvas
, mainCamera
.
Examples: playerSpeed
, enemyHealth
, MovePlayer
, SpawnEnemy
.
Examples: kMaxHealth
, kSpeedMultiplier
.
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! ๐๐ป๐
Resources
- Create a C# style guide: Write cleaner code that scales โ Unity Guide
- Common C# Coding Conventions โ Microsoft C#.
- C# at Google Style Guide โ Google Style Guide
โ๏ธ Author
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!
Contact: marco@gladiogames.com