Today, we’ll talk about Unity Coding Standards. We’ll cover things to do, things to avoid, and general tips to keep your projects clean, maintainable, and standardized.
Things to avoid
I want to prefix this by saying that the thoughts in this post are guidelines and not meant to be a criticism of anyone. These are personal preferences and things I’ve picked up from experience across a variety of different projects.
If you find that you commonly do and use some of these things, don’t be offended, just try to be conscious of the issues that can arise. With that little disclaimer, here are some of the key things I always fight to avoid and recommend you do your best to limit.
Public Fields
I won’t go deep into this as I think I’ve already covered it here. Just know that public fields are generally a bad idea. They often tend to be a precursor to code that’s difficult to read and maintain.
If you need to access something publicly, make it a property with a public getter. If you really need to set it from another class, make the setter public too, otherwise use a property that looks like this:
public string MyStringThatNeedsPublicReading { get; private set; }
Large Classes
I’ve seen far too many Unity projects with class sizes that are out of control. Now I want to clarify that this is not something only specific to unity, I’ve seen classes over 40k lines long in some AAA game projects. I’ve seen .cs & .js files in web apps over 20k lines long.
That of course does not make them right or acceptable.
Large classes are hard to maintain, hard to read, and a nightmare to improve or extend. They also always violate one of the most important principals in Object Oriented Programming. The principal of Single Responsibility.
As a general rule I try to keep an average class under 100 lines long. Some need to be a bit longer, there are always exceptions to the rules. Once they start approaching 300 lines though, it’s generally time to refactor. That may at first seem a bit crazy, but it’s a whole lot easier to clean up your classes when they’re 300 lines long than when they reach 1000 or more. So if you hit this point, start thinking about what your class is doing.
Is it handling character movement? Is it also handling audio? Is it dealing with collisions or physics?
Can you split these things into smaller components? If so, you should do it right away, while it’s easy.
Large Methods
Large classes are bad. Large methods are the kiss of death.
A simple rule of thumb: if your method can’t fit on your screen, it’s too long. An ideal method length for me is 6-10 lines. In that size it’s generally doing one thing. If the method grows far beyond that, it’s probably doing too much.
Some times, as in the example below, that one thing is executing other methods that complete the one bigger thing. Make use of the Extract Method refactoring, if your method grows too long, extract the parts that are doing different things into separate methods.
Example
Take this Fire() method for example. Without following any standards, it could easily have grown to this:
Original
protected virtual void Fire() { if (_animation != null && _animation.GetClip("Fire") != null) _animation.Play("Fire"); var muzzlePoint = NextMuzzlePoint(); if (_muzzleFlashes.Length > 0) { var muzzleFlash = _muzzleFlashes[UnityEngine.Random.Range(0, _muzzleFlashes.Length)]; if (_muzzleFlashOverridePoint != null) muzzlePoint = _muzzleFlashOverridePoint; GameObject spawnedFlash = Instantiate(muzzleFlash, muzzlePoint.position, muzzlePoint.rotation) as GameObject; } if (_fireAudioSource != null) _fireAudioSource.Play(); StartCoroutine(EjectShell(0f)); if (OnFired != null) OnFired(); if (OnReady != null) OnReady(); var clip = _animation.GetClip("Ready"); if (clip != null) { _animation.Play("Ready"); _isReady = false; StartCoroutine(BecomeReadyAfterSeconds(clip.length)); } _currentAmmoInClip--; if (OnAmmoChanged != null) OnAmmoChanged(_currentAmmoInClip, _currentAmmoNotInClip); RaycastHit hitInfo; Ray ray = new Ray(muzzlePoint.position, muzzlePoint.forward); Debug.DrawRay(muzzlePoint.position, muzzlePoint.forward); if (TryHitCharacterHeads(ray)) return; if (TryHitCharacterBodies(ray)) return; if (OnMiss != null) OnMiss(); if (_bulletPrefab != null) { if (_muzzleFlashOverridePoint != null) muzzlePoint = _muzzleFlashOverridePoint; Instantiate(_bulletPrefab, muzzlePoint.position, muzzlePoint.rotation); } }
This method is handling firing of weapons for an actual game. If you read over it, you’ll see it’s doing a large # of things to make weapon firing work. You’ll also notice that it’s not the easiest thing to follow along. As far as long methods go, this one is far from the worst, but I didn’t want to go overboard with the example.
Even so, it can be vastly improved with a few simple refactorings. By pulling out the key components into separate methods, and naming those methods well, we can make the Fire() functionality a whole lot easier to read and maintain.
Refactored
protected virtual void Fire() { PlayAnimation(); var muzzlePoint = NextMuzzlePoint(); SpawnMuzzleFlash(muzzlePoint); PlayFireAudioClip(); StartCoroutine(EjectShell(0f)); if (OnFired != null) OnFired(); HandleWeaponReady(); RemoveAmmo(); if (TryHitCharacters(muzzlePoint)) return; if (OnMiss != null) OnMiss(); LaunchBulletAndTrail(); }
With the refactored example, a new programmer just looking at the code should be able to quickly determine what’s going on. Each part calls a method named for what it does, and each of those methods is under 5 lines long, so it’s easy to tell how they work. Given the choice between the 2 examples, I’d recommend #2 every time, and I hope you’d agree.
Casing
The last thing I want to cover in this post is casing. I’ve noticed in many projects I come across, casing is a mess. Occasionally, project I see have some kind of standard they’ve picked and stuck to. Much of the time though, it’s all over the place with no consistency.
The most important part here is to be consistent. If you go with some non-standard casing selection, at least be consistent with your non-standard choice.
What I’m going to recommend here though is a typical set of C# standards that you’ll see across most professional projects in gaming, business, and web development.
Classes
Casing: Pascal Case
public class MyClass : MonoBehaviour { }
Methods
Casing: Pascal Case (No Underscores unless it’s a Unit Test)
private void HandleWeaponReady()
Private Fields
Casing: camelCase – with optional underscore prefix
// Either private int maxAmmo; // OR my prefered private int _maxAmmo;
This is one of the few areas where I feel some flexibility. There are differing camps on the exact naming convention to be used here.
Personally, I prefer the underscore since it provides an obvious distinction between class level fields and variables defined in the scope of a method.
Either is completely acceptable though. But when you pick one for a project, stick with it.
Public Fields
It’s a trick, there shouldn’t be any! 😉
Public Properties
Casing: Pascal Case
public int ReaminingAmmoInClip { get; private set; }
These should also be Automatic Properties whenever possible. There’s no need for a backing field like some other languages use.
Again you should also mark the setter as private unless there’s a really good reason to set them outside the class.
Wrap Up
Again, this is just a short list of a few things that I think are really important and beneficial for your projects. If you find this info useful, drop in a comment and I’ll work to expand out the list. If you have your own recommendations and guidelines, add those as well so everyone can learn and grow.
Thanks, and happy coding!