#begin
Recently I was able to grab an early review copy of Harrison Ferrone’s new book: Learning Design patterns with Unity3D. This is not the first book by Harrison that I read. I read another book of his called learning C# by developing games in unity. Both books are from Packt publishing and they have this early review program where they ask experts in the field for doing reviews. I volunteered on a couple of them and now it’s time to check out Harrison’s new book.
Before we dive into it let’s quickly discuss the topic and structure of the book. So the subject is kind of obvious. It’s about learning and applying software design patterns in unity3D with c#. In my opinion design patterns are still very relevant and every developer should have very detailed knowledge about them so you know what they mean and especially when NOT to apply them. I bet you have encountered some codebase that was sprinkled with design patterns for no apparent reason other than the fact that “we can”. Design patterns are really powerful when applied correctly so the better you understand them the better you know when and how to apply them. I also like Martin fowler’s take on design patterns and that is that they are supposed to be guidelines, not hard rules. So adapting a design pattern’s implementation to your specific use case is often the best approach. Spoiler alert, this is exactly what Harrison does and suggests in his book.
So, with that out of the way; let’s discuss the structure of the book. The book is of course based on the original patterns in the Gang of Four (GoF) book. It also includes some patterns that are not in the book, and I’ll point them out once I get to them. The book is separated in three parts, the creational, behavioural and structural patterns. Then, each chapter starts out with some technical requirements which list the code you need to clone from the git repo that’s provided with the book. Harrison also breaks down each pattern with its intent and pro’s and cons. Each pattern, in turn, gets implemented more or less as described in the Gang of Four book, and then adapted and evolved to better match the Unity (event) lifecycle and integration with common Unity architecture like ScriptableObjects (SO). Each chapter ends with a summary of what we learned and some additional information to read. Here he often refers to the documentation of either Unity, the C# language spec or both.
Now, let’s discuss some patterns already!
Chapter 2: The Singleton
So, the first chapter dives into what I think is the most abused design pattern in a unity3D context; the Singleton. The most ironic aspect of the singleton pattern is that you EVER only need one, yet Unity projects often experience a wild growth of singletons. Fortunately, Harrison does a wonderful job explaining this exact problem. The first implementation of the singleton as shown in the book matches the GoF. Then he evolves the pattern by making a generic baseclass which can be inherited to make creating others straightforward. This, in essence should not be needed, because it will provide easy access to this wild growth problem I described earlier. Another adaption he does is to move away from MonoBehaviour and toward SO’s. This is a very welcomed step since it will break the singleton’s dependency on Unity lifecycle.
Chapter 3: The Prototype pattern
The prototype pattern is another pattern you’ll often encounter in Unity projects. This is a great pattern for many reasons. The main reason is of course the memory footprint. Especially since unity is somewhat optimised for it. I remember a talk at Unite Amsterdam, back in 2014 or so where some dude from Unity showed that copying an object’s hierarchy is quite a bit faster than instantiating new objects from prefabs. I’ll try to hunt down the talk and drop it in the here. Harrison also addresses the concept of shallow vs. deep copies. I really like that attention to detail because it’s an absolute must to understand this. The chapter end by extending the prototype pattern with some generic types which makes it a bit more versatile. Great stuff!
Chapter 4: The factory method pattern
The factory method pattern is a pattern what I think is less widely used than other patterns. I suppose because people will often implement the Abstract Factory Pattern instead. Spoiler alert, it’s described in the next chapter. Nonetheless, it’s a nice addition to add to the book because it’s a basically a lightweight version of the abstract factory pattern. It depends on your needs and sometimes this will fit nicely as well. The chapter follows the trend of the previous two where the basic GoF pattern is described and implemented and then adapted for a better fit with Unity. So, it’s extended with SO’s. Harrison also describes that the implementation of the pattern can grow quickly, and one can battle this growth by adding some reflection into the mix. Personally, I’m not a big fan of using run-time reflection because it can lead to all kinds of issues. It’s very cryptic, hard to maintain because you cannot leverage the compiler for type checks etc. its slow and you can run into all kinds of weird exceptions due to code stripping levels. Most often, use of reflection is caused by a design flaw. So, I always try to avoid it. But of course, like any other concept in computer science; don’t be dogmatic about it since there are some valid use-cases. That being said, Harrison mentions problems related to reflection which should be taken to heart. The chapter ends with some more flexibility added to the pattern by parameterizing the creation methods and allowing to spawn GameObjects.
Chapter 5: The Abstract Factory Pattern
The Abstract Factory Pattern is one that is often used in OO languages. There’s this meme that goes like: “I have a problem and I’m using <insert OO language here>, and now I have a problem factory”. I still think it’s funny, and I think it highlights the popularity of this pattern. The example in the book involves creating factories for a crafting and shop system. There’s nothing much to say about this chapter since it showcases how to implement the abstract factory pattern in its full glory. There are some mentions of reflection again, but I already discussed that.
Chapter 6: The Builder Pattern
Another great pattern that is often used, the Builder Pattern. Maybe less so in Unity but certainly in imperative languages overall. Builder patterns are sometimes called “Fluent API’s” and will often show up in query builder objects of some sort. If you have used the Unity UI-Toolkit you will have encountered this pattern with the .Query API. And of course, LINQ and the QueryBuilder in EFCore are very popular builder patterns. The example in the book uses a builder pattern to construct tanks and drones. Harrison does a wonderful job explaining the pattern and transitions from the regular builder pattern to a fluent version to emphasize the point. I like that! Great stuff!
Chapter 7: Object Pooling
Ok, so; here we have the first pattern that’s not originally in the GoF book. Yet, the Object Pool pattern is probably second nature to any game developer. It’s such a powerful pattern that I wouldn’t know how to live without it. Introducing a pooling system in your game is a fantastic way to optimize resource usage. I also like how Harrison warns about threading issues that might pop up with this pattern and provides a solution to avoid it. Trust me, I’ve been there :D. He also does a minor tweak to the pool by adding a queue, which I think was a very nice touch. It provides the reader with just that extra depth to understand a new data-structure. At the end of the chapter, Harrison points out the obvious and that is the fact that due to the high popularity of the Object Pool Pattern, there’s a built-in pool object in Unity. This gives the reader the understanding about how the pattern works, yet he/she can now leverage the built-in pool. I really like that. Personally, I’m a ‘bottom-up’ learner so I like to understand the underlying abstraction before I move up to higher level things. I get that not everyone likes to learn this way, but I feel that having an understanding about the internals of the pattern will make usage of the built-in pool straightforward.
Chapter 8: The Command Pattern
The command pattern is probably my favourite design pattern. Such a simple pattern but very useful. I’ve built all kinds of systems with the command pattern; from simple ‘method as objects’ kind of systems to entire workflows with undo-redo pipelines with hooks and events capabilities. The strength of this pattern lies in its simplicity. Harrison describes the basic usage of the command pattern and quickly expands on the concept by introducing reusable vs. single-use commands. This is again a very welcomed detail since knowing about this distinction makes the user capable of implementing more interesting solutions. Both reusable and single use commands are implemented in the book as well, leaving the reader with concrete examples of both. Harrison then also mentions the fact that modern languages support lambda’s which might replace the command pattern in some codebases. He also says that using just lambdas can lead to cryptic implementations with a lot of duplication. And I agree. I remember a talk by Uncle Bob where he says that: “The command pattern is a poor man’s lambda”. Haha.
Chapter 9: The Observer Pattern
The Observer Pattern is very widely used in Unity3D. Even when people don’t know about it. When you are assigning events in the Unity inspector, on let’s say a Button, you are essentially using the observer pattern. I’m not really a fan of using UnityEvents, but still. It is a powerful pattern to switch dependencies around and will vastly improve performance. Generally, you want to move away from ‘pulling’ mechanisms and embrace ‘pushing’ mechanisms. This means you’ll be moving more towards a reactive programming model, which is good. In the context of real-time systems, which all games are, performance is very important and using the observer pattern will allow you to move away from doing constant checks for changes in let’s say the Update loop of Unity. This is a huge performance gain! In the book Harrison will also touch upon push and pull mechanics but the solely in the context of the observer pattern. He describes how you can design the communication strategy of the observer pattern itself, which I’ll leave for you to find out when you read the book 😉 He also explains about the very important fact that you should unsubscribe observers. You’ll encounter all kinds of nice exceptions when you don’t.
Chapter 10: The State Pattern
The State Pattern is another quintessential pattern used in game development. It’s such a common pattern that you will encounter it everywhere. If you are familiar with the Unity animator you are familiar with the state pattern. I’m also a big fan of the state pattern because of its simplicity when applied correctly. Plus, FSM’s have this very cool attribute of being able to proof them mathematically by using μ-calculus, otherwise called temporal logic. So, if you need to be absolutely sure that your state machine is sound you can use that. I’ve once used the state pattern as the core mechanism to express everything in a unity app. It used I/O enabled FSM’s with nesting and able to suspend states to create some really cool, and most notably, easy to understand, control flow. Harrison, again, does a wonderful job explaining this pattern. We set out to create a simple turn-based game which of course is a great match for the state pattern. He then goes on by explaining hierarchical FSM’s and how to support concurrent FSM’s to avoid race conditions.
Chapter 11: The Visitor Pattern
The visitor pattern is a pattern I rarely use to be honest. I’m not sure why, but I guess I prefer other behavioural patterns over this one, like the strategy pattern. For me, there’s too much ‘stuff’ to write to implement this pattern. Nonetheless, Harrison does a nice job explaining it with some examples to implement a saving system. The saving system uses the visitor to save weapon and player stats to the PlayerPrefs in unity. I like the fact that he introduces the PlayerPrefs because it’s a simple and (for the reader) new concept he/she can add to her toolbelt. Harrison explains what the PlayerPrefs are for and also warns about not to save massive amounts of data in there. It’s not made for that, it says “prefs” for a reason. He then also implements sort of a buffing or upgrade system using the visitor pattern to further extend on the weapon and player stats mechanisms. I think that is pretty cool because it’s a simple showcase of the versatility of the pattern.
Chapter 12: The Strategy Pattern
As I mentioned before, the strategy pattern is a pattern I have used regularly. I can’t really think of a recent usage of it but looking back over my career and personal projects I can definitely pick out numerous instances of where I used it. I like the strategy pattern exactly because of its definition which is something along the lines of; “the ability to swap out entire – or parts of – algorithms at design and run-time”. It’s a pattern that can provide just the right amount of flexibility. The example Harrison decides to implement in the book is a sorting algorithm. I think this is kind of the canonical example for the state pattern. By using the strategy pattern, we set out to implement sorting for a leaderboard. We use the strategy pattern to add alphabetical, score, and unsorted algorithms. I like how he uses LINQ in the examples because it’s a great use-case for it. I used to use LINQ everywhere but currently, I no longer do because the performance is just really, really horrible. The same is to say for ‘foreach’ loops. The fact that these algorithms create iterator classes means they generate garbage. And if you want to maintain a steady frame-rate, that garbage is going to make you suffer. But in the case of this leader board, we are only executing the algorithm once, so the impact is very low. We’re not re-sorting the entire thing in the Update() loop. Harrison than introduces another usage of the strategy pattern and he adapts it towards using SO’s. The second example entails creating some basic algorithms for bots/npc’s in a game. The strategy pattern is used to make several kinds of variations.
Chapter 13: The Type Object Pattern
The type object pattern is a pattern you will also encounter a lot in the wild with Unity. Maybe not under its canonical name, but since Unity is inherently a component-based system, this pattern also favours composition over inheritance. The type object pattern however describes data, not behaviour. In the book we implement a simple RPG like system with different kind of playable types, enemies and spell-types. This is indeed a great opportunity for the type object pattern to shine. Creating massive inheritance trees is a big-ass red flag so we want to move towards a compositional approach. This is exactly what Harrison shows in this chapter. What’s also great about his explanation is the fact that he recognizes that, if implemented poorly, the type object pattern can leave a massive memory footprint. So, he extends the pattern a bit to showcase how to adapt the classes to share data. Again, I like the attention to detail and giving the reader some tangible advice because these things can make or break your game’s performance.
Chapter 14: The Memento Pattern
Hmmmmm, the Memento Pattern. This is a pattern I barely remember, and I have never encountered in the wild. So… I grabbed my copy of the original GoF book and checked the implementation there as well. The example in the Gof book explains how to use the memento pattern to add constraints about moving graphical objects across the screen. Harrison shows an example of taking snapshots of objects by using the memento to access internal state. He also adapts his pattern to use MonoBehaviours, plus he shows how to make C# Properties accessible for Unity inspector serialization. I bet this will surely help starting unity developers a lot. Once you know how to serialize properties in the inspector, you no longer need a base-class and can implement interfaces. (Yes, if you are a Java developer, C# allows for Properties in interfaces!) This will allow you to create some very interesting designs. Apart from that, I don’t have much else to say about this chapter.
Chapter 15: The Decorator Pattern
I truly hate this pattern. In my opinion there are many things wrong about it. First of all, the identity of objects will get all messed up if not implemented correctly. When you implement the decorator patter, make sure to override the GetHashCode and Equals() functions to preserve identity if you need it. Second, the abstraction just does not make sense. The canonical example of the decorator pattern is often, the pizza. So, you ‘decorate’ your pizza with all kinds of toppings. But a topping, is not a pizza, yet that’s exactly how the decorator creates its flexibility. I don’t want to be dogmatic about this pattern. I know it has its place and it is actually pretty popular. I just don’t like it very much. The example in the book adds modifications to a BaseCharacter class. So, we decorate the BaseCharacter with a StrengthAmulet. But a StrengthAmulet is not a BaseCharacter… Don’t get me wrong, I’m not criticizing Harrison right here. He implements the pattern just as its intended. It’s flawless. I just don’t like this pattern overall… What I do like is his extensions towards SO’s but I’ll leave it for you to find out the implementation details for yourself.
Chapter 16: The Adapter Pattern
The adapter pattern is a pattern I do really like. It’s often the most sensible way to bind your own code with third party code without taking a direct dependency on it. You’ll still have the dependency in some integration layer but in this case its more easily managed. That’s proper OO design. It’s also great to combine a couple of adapters into a Façade for better access, but we will take a look at the façade pattern later. Harrison shows a different use-case of the adapter pattern in the book. Where he uses it to couple two incompatible classes, from the same codebase, into each other. I think that’s valid too. You’ll often see that systems in games are isolated; for example, a shop system and a gearing system in an RPG can be written in isolation and through some integration layer be coupled. This integration layer could consist of a collection of adapters that take care of this concern. The example in the book entails a character controller with move and jump capabilities. Through an adapter a teleport action is added. I think it’s a cool example how to bind new behaviour to persisting code. Great job!
Chapter 17: The Façade Pattern
The façade pattern is also a pattern you’ll see used very often in Unity Projects. You’ll find that most Singleton’s in Unity will fit the façade pattern near perfection. And, that’s both a pro and a con, because a façade often breaks the Single Responsibility Pattern (SRP). I won’t go into the SRP or SOLID principles, but I’ve written blogs about them before. So, you can just hit the search bar and look for them. The façade pattern is a very simple one yet Harrison is still able to present an interesting use-case. We set out to implement a barebones saving system. This is nice because, probably every game needs a saving system anyway. And if you are just starting out with Unity, this chapter will give you some ideas to implement one. Nothing else much to say about this chapter. It’s well written with a great example.
Chapter 18: The Flyweight Pattern
The Flyweight Pattern is one of these patterns that you often use without even knowing it. It’s such a common solution to data-sharing / memory optimization problems that you might come up with this design yourself, without ever knowing the design pattern exists. The pattern boils down to sharing, often immutable, state among objects instead of copying it. Harrison does a really great job explaining it with an example how one naively adds trees to an environment in Unity. You might copy paste many trees into a scene, where each individual tree has a unique instance of its coordinates, materials and textures. But we want to optimize it to only have unique coordinates but share at least the texture and maybe the material as well. It’s such an easy example to understand so kudos for this one! The example we implement in the book is a simple tiling system to generate ground, water, grass and edge tiles. If you’ve ever written like a 2D platformer, or maybe a Pacman clone you’ll have encountered this such algorithms. The tiles share some data structures indicating what TerrainType they are and the edges are examples of the unshared kinds.
Chapter 19: The Service Locator Pattern
The service locator pattern is in my opinion a great way to battle singleton wild growth. So let me explain; I mentioned before, if you have a singleton, you only ever need one. What you can do then, is use that singleton as the service locator where you can register and unregister services that can later be accesses through the service locator. Harrison also rightly mentions that the service locator and singletons attached to it should be context-free (and better yet, stateless). This means you can always use them without having to be worries about side-effects. Harrison also mentions that you should not have to register services when it doesn’t make sense in the first place. The example he gives in the book is where you should not have to register a MultiPlayerService when you are doing singleplayer content. This all being said, the pattern is very straightforward. The implementation presented in the book reflects this and Harrison does a great job extending the pattern with generics. The generics will make for a more versatile implementation and will remove all kinds of run-time exceptions.
Chapter 20: The Road Ahead
Chapter 20 marks the end of the book. Harrison talks about the fact that lots of patterns that are in the GoF book are not covered in this book because we’ll end up with a massive Thome of Design pattern goodness. He says that maybe in some next edition he’ll add a (couple) new pattern(s). I suggest that the Event Queue pattern is a great pattern to add in a future edition knowing the popularity of UniRX for example. Another one, that’s popular in the DotNet ecosystem is the Mediator pattern. It’s a great pattern for decoupling.
Conclusion: Awesum!
I think that this book falls in this category of “I wish I had when I got started”. It’s a really great summary of patterns often used in a Unity Context. Where the GoF book is written in an academic tone, Harrison’s, Learning Design Patterns with Unity is written in a hands-on tone giving newbies to the Unity scene (pun intended) some tangible reference material. What’s also well thought, is the structure of the book. Chapters are strategically placed meaning we started out with the Singleton pattern and in subsequent patterns there’s often this notion of a Manager-esque class. The chapters have this natural flow to them where reading it from start to finish will use previously acquired knowledge fit into the next chapters. This is true for both the patterns themselves and for example data-structures that are used. Harrison does a wonderful job explaining the patterns with their pro’s and con’s and I like how each pattern is extended upon to better fit the Unity lifecycle and architecture. That’s what makes the book really great for beginners. Yet, it’s also great for more experienced developers who want to refresh their knowledge of design patterns or might not be entirely sure how to fit certain design patterns to Unity3D.
In some chapters Harrison will refer to using reflection as the final boss for flexibility and extension. Although this is true, I’m not sure if this is the best advice. There can be many issues with run-time reflection like, the lack of checks by the compiler, no support for automated refactoring tooling, performance issues and code stripping levels. In my opinion, resorting to reflection often indicates a design flaw. But, as I mentioned before, never be dogmatic about anything in computer science because it’s often a bad idea. Reflection has its place but should not be at the center of your design.
Then there is another point of constructive feedback I would like to give and that is, testing. Literally all design patterns, except for the Singleton pattern (derp), are about separation of concerns. This makes testing a lot easier, yet there is no mention what-so-ever about testing in the entirety of the book, except for in the closing thoughts about the singleton pattern. I know, testing is a book of its own, but it might be a good idea to have a section or maybe a dedicated chapter that shows some examples of how this separation of concerns leads to more testable code. We should be able to write unit-tests for individual strategies of the strategy pattern, unit-tests for observers, or maybe showcase the problems a singleton can have in a testing context. This might go far beyond the scope of the book, but it’s still an important topic to highlight.
Bottom line: It’s a great read and can be used as a reference work in any Unity developer’s day to day work. There are lots of examples that can be implemented directly, and the book includes many references to increase the reader’s understanding about the topics involved. I think Harrison hit the homerun on this one and I’m looking forward to the second edition. Great job!
#end
01010010 01110101 01100010 01100101 01101110
Recent Comments