└── README.md /README.md: -------------------------------------------------------------------------------- 1 | Unity3D Good Practices 2 | ================== 3 | 4 | _Work in progress, please contribute or fix things as you see fit_ 5 | 6 | Interested in other platforms? Our [Best Practices in Android Development][android-best-practices], [Windows App Development Best Practices][windows-app-development-best-practices] and [iOS Good Practices][ios-good-practices] documents have got you covered. 7 | 8 | [android-best-practices]: https://github.com/futurice/android-best-practices 9 | [windows-app-development-best-practices]: https://github.com/futurice/windows-app-development-best-practices 10 | [ios-good-practices]: https://github.com/futurice/ios-good-practices/ 11 | 12 | ## Contents 13 | 14 | If you are looking for something specific, you can jump right into the relevant section from here. 15 | 16 | 1. [Tutorials](#tutorials) 17 | 1. [Recommended assets](#recommended-assets) 18 | 1. [Coding conventions](#coding-conventions) 19 | 1. [Optimizing](#optimizing) 20 | 1. [Testing](#testing) 21 | 1. [Tools](#tools) 22 | 23 | ## Tutorials 24 | 25 | Here's a list of recommended tutorials for getting to know Unity. 26 | 27 | * [Catlike Coding][catlike-coding] offers scripting basics, programmatic meshes, shaders and more. 28 | * [Udemy][udemy-link] has paid but thorough tutorials on making entire games in Unity. 29 | 30 | [catlike-coding]: http://catlikecoding.com/unity/tutorials/ 31 | [udemy-link]: https://www.udemy.com/unitycourse/ 32 | 33 | ## Recommended assets 34 | 35 | This section will list some highly recommended but perhaps not always relevant assets. Not mentioned in this section is Reactive Extensions for Unity which we will cover later. To get an overview of a lot of open-source assets, check out [AssetLoad][asset-load] 36 | 37 | [asset-load]: http://assetload.com/browse?c=unity 38 | 39 | ### DOTween - Animating from scripts 40 | 41 | You could be spending hours upon hours writing your own [easing functions][easing-functions] in some Update-loop or UniRx Observable - or you could use a tweening library. 42 | We recommend using [DOTween][dotween]. It's easy to learn, has a lot of functionality and it's [faster than the others][dotween-fast]! 43 | This is extremely handy when you're trying to implement some simple UI animation for example. Do not use this for organic animation, e.g. characters. Refer to [Unity's built-in animation system][unity-animation] in that case. 44 | 45 | Here's a quick example on how to move something to a new position over two seconds with a quint easing, and finally logging to the console: 46 | ```csharp 47 | transform.DOMove(new Vector3(1,2,3), 1).SetEase(Ease.InOutQuint).OnComplete(() => 48 | { 49 | Debug.Log("Hello world"); 50 | }); 51 | ``` 52 | [easing-functions]: http://easings.net/ 53 | [dotween]: http://dotween.demigiant.com/ 54 | [dotween-fast]: http://dotween.demigiant.com/#enginesComparison 55 | [unity-animation]: https://unity3d.com/learn/tutorials/s/animation 56 | 57 | ### TextMesh Pro - Better text for your games 58 | 59 | Ever think 'The text in Unity is so bad...'? Worry no more, [TextMesh Pro is here to save you][textmesh-pro]. 60 | 61 | [textmesh-pro]: https://www.assetstore.unity3d.com/en/#!/content/84126 62 | 63 | There's not much to say here, it's "just" text, done right. You can use text as a mesh in your 3D world, or use it as text for your canvas. 64 | 65 | ## Coding conventions 66 | In this section we'll talk about raising the quality of your code. Some of these tips may affect performance, so be mindful. 67 | 68 | ### C# version 69 | Unity builds to a runtime version of dotnet 3.5 by default. 70 | You can switch to 4.6 by going into Player -> PC, Mac and Linux -> Other Settings -> Configuration -> Scripting Runtime Version. Here's an example of a getter property in 3.5: 71 | 72 | ```csharp 73 | private int _foo; 74 | public int Foo 75 | { 76 | get 77 | { 78 | return _foo; 79 | } 80 | } 81 | ``` 82 | And here's the same in 4.6: 83 | 84 | ```csharp 85 | private int _foo; 86 | public int Foo => _foo; 87 | ``` 88 | 89 | For more information, see [here][scripting-runtime-upgrade]. 90 | 91 | 92 | [scripting-runtime-upgrade]: https://docs.unity3d.com/Manual/ScriptingRuntimeUpgrade.html 93 | 94 | ### Code style 95 | To keep your code maintainable and readable by yourself and others as well, it's recommended to determine a specific [style][programming-style] for your scripts. It's up to you to determine what's best for yourself and your teammates. Here's one way to do it: 96 | 97 | ```csharp 98 | namespace MyNamespace 99 | { 100 | public class MyClass : MonoBehaviour 101 | { 102 | // private fields start with underscore and then lowercase 103 | // public fields start with no underscore and uppercase 104 | // variables start with no underscore and lowercase 105 | [SerializeField] private float _myPrivateEditorField = 1f; // starts with default value of 1, gets overriden in editor 106 | [HideInInspector] public float MyPublicHiddenField = 2f; // can't be seen in inspector, but other scripts need this variable 107 | private float _myPrivateField = 3f; // cannot be seen in inspector 108 | public float MyPublicField = 4f; // is automatically serialized and can be seen in the inspector 109 | 110 | public float MyPrivateFieldAccessor => _myPrivateField; // other scripts can now read _myPrivateField 111 | 112 | public void MyFunction(float myParameter) 113 | { 114 | var myVariable = 5f; 115 | } 116 | } 117 | } 118 | ``` 119 | 120 | [programming-style]: https://en.wikipedia.org/wiki/Programming_style 121 | 122 | ### LINQ 123 | Language-integrated query, or [LINQ][linq-docs] for short, is a way of handling collections in dotnet. 124 | 125 | Let's say you want to find a gameobject in a list that has some specific tag: 126 | ```csharp 127 | GameObject gameObjectToFind = null; 128 | foreach(var go in yourListOfGameObjects) 129 | { 130 | if(go.tag == "fooBar") 131 | { 132 | gameObjectToFind = go; 133 | break; 134 | } 135 | } 136 | ``` 137 | 138 | We can do this much shorter with LINQ: 139 | ```csharp 140 | var gameObjectToFind = yourListOfGameObjects.FirstOrDefault(go => go.tag == "fooBar"); 141 | ``` 142 | 143 | As with loops, you should be careful of using these sort of statements in frequently run scopes like Update or FixedUpdate. LINQ in particular allocates more memory than a trivial for-loop. Internally, LINQ still uses for-loops - but your code will be easier to read, at least once you get to know LINQ. 144 | See more examples of LINQ in Unity [here][linq-example] 145 | 146 | 147 | [linq-docs]: https://docs.microsoft.com/en-us/dotnet/standard/using-linq 148 | [linq-example]: https://unity3d.college/2017/07/01/linq-unity-developers/ 149 | 150 | ### Coroutines vs reactive extensions 151 | 152 | By default, Unity offers [Coroutines][unity-coroutines] for handling future execution. In this section we'll go through the cons of that, and introduce an alternative featuring reactive programming. 153 | 154 | Here's an example of a timed execution in Unity: 155 | 156 | 157 | ```csharp 158 | 159 | void Start() 160 | { 161 | StartCoroutine(DoOnDelay(5f)); 162 | } 163 | 164 | IEnumerator DoOnDelay(float afterTime) 165 | { 166 | yield return new WaitForSeconds(afterTime); 167 | // stuff that happens after time 168 | } 169 | ``` 170 | 171 | There's a couple of problems about this. Why are we dealing with [IEnumerators][ienumerator]? What's with all this boilerplate for a simple timer? 172 | There are better and easier ways to do this. Don't do this either though: 173 | 174 | ```csharp 175 | bool timerStarted = false; 176 | bool doExecuted = false; 177 | float dt = 0; 178 | 179 | void Start() 180 | { 181 | timerStarted = true; 182 | } 183 | 184 | void Update() 185 | { 186 | if(timerStarted) 187 | { 188 | dt += Time.deltaTime; 189 | if(dt >= 5f && !doExecuted) 190 | { 191 | doExecuted = true; 192 | // stuff that happens after time 193 | } 194 | } 195 | } 196 | ``` 197 | 198 | This is even more boilerplate. We have to try something much more shorthand. We recommend [Reactive Extensions For Unity][unirx], UniRx for short. 199 | You can download UniRx from the [Asset Store][unirx-asset] 200 | 201 | Here's the same example as above, but written with UniRx: 202 | 203 | ```csharp 204 | Observable.Timer(TimeSpan.FromSeconds(5f)).Subscribe(_ => 205 | { 206 | // stuff that happens after time 207 | }); 208 | ``` 209 | 210 | UniRx is based on [the reactive programming paradigm][rx-wiki]. In this case, we start a timer that lasts five seconds and we subscribe to that timer stream. Subscription means that we're listening to when the timer is done. 211 | In reactive programming, whenever you do not wish to use the value handed to you from the publisher (here, the timer), you name the input parameter of your [lambda expression][lambda-expression] as underscore. 212 | 213 | You can also create oscillator-type timers that trigger every X TimeSpans by supplying a second parameter in timer like so: 214 | 215 | ```csharp 216 | Observable.Timer(TimeSpan.FromSeconds(5f), TimeSpan.FromSeconds(5f)).Subscribe(_ => 217 | { 218 | // stuff that happens after every time 219 | }); 220 | ``` 221 | 222 | Please refer to the UniRx github page for more examples. 223 | 224 | [unity-coroutines]: https://developer.apple.com/ios/human-interface-guidelines/ 225 | [ienumerator]: https://msdn.microsoft.com/en-us/library/system.collections.ienumerator(v=vs.110).aspx 226 | [unirx]: https://github.com/neuecc/UniRx 227 | [unirx-asset]: https://www.assetstore.unity3d.com/en/#!/content/17276 228 | [rx-wiki]: https://en.wikipedia.org/wiki/Reactive_programming 229 | [lambda-expression]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions 230 | 231 | ## Optimizing 232 | If your game is getting laggy, try reading some tips below. 233 | 234 | ### Object pooling 235 | If you're instantiating/destroying a lot of objects, try using an object pool. The principle is simple 236 | * You have X amount of objects instantiated before-hand in the scene, set as inactive 237 | * Instead of instantiating, you pull from the pool and set as active 238 | * Instead of destroying, you set as inactive and return to the pool 239 | 240 | This will make life much easier for your CPU. See a detailed tutorial [here][object-pooling] and [here][object-pooling-cat] 241 | 242 | [object-pooling]: https://www.raywenderlich.com/136091/object-pooling-unity 243 | [object-pooling-cat]: http://catlikecoding.com/unity/tutorials/object-pools/ 244 | 245 | ### Profiling 246 | _Coming soon..._ 247 | 248 | ## Testing 249 | 250 | ### Unit tests 251 | _Coming soon..._ 252 | 253 | ### Platform dependent compilation 254 | Switching platforms for sections of your scripts is done like so: 255 | ```csharp 256 | void Start() 257 | { 258 | #if UNITY_EDITOR 259 | Debug.Log("Hello Unity Edtor"); 260 | #else 261 | Debug.Log("Hello something else"); 262 | #endif 263 | } 264 | ``` 265 | This is great if you want to skip parts in your application/game. See more [here][platform-compilation]. 266 | 267 | 268 | [platform-compilation]: https://docs.unity3d.com/Manual/PlatformDependentCompilation.html 269 | 270 | ## Tools 271 | 272 | ### IDEs 273 | 274 | This is a matter of taste, but other than the built-in MonoDevelop, you can try out Rider, Visual Studio or Visual Studio Code. There's a lot of different IDEs and different arguments for which one is the best. 275 | 276 | Personal experiences: Visual Studio Code does not work very well for debugging 277 | --------------------------------------------------------------------------------