It has been some time since the last time I have posted. I am back with a topic that has quite a big importance: Modular Game Development.
I often see that beginner level game developers don’t really pay attention to the code quality. I’ve even seen some game code which is just one giant Player class. Some developers can even argue the necessity of quality code as long as the end product runs smoothly. Don’t get me wrong! I have also seen some game code which is over engineered. We don’t want it. Therefore, I think finding a middle ground is crucial. Having an organized and quality code base is a big advantage. Especially when the project is bigger.
Modularizing your code is easier than you think. In addition, It creates a base for quality code out of the box. It may increase the development time. However, it decreases the time spent on merge conflicts, finding root cause of the bugs and understanding a piece of code.
What is Modularization?
Modularization is one way to increase code quality. It is simply creating modular pieces in your project. So, you can easily see the difference of one piece from the other pieces. You can reuse the same pieces inside the other pieces as well.
We break up and organize the pieces based on the responsibility it has or the task it executes. Furthermore, we can also have sub-modules which is connected to a module. There are five main benefits of modularization;
- It will be easier to debug.
- You will have reusable code.
- Readability will be increased.
- The code will be more reliable.
- Control over visibility on high level.
Easier to Debug
Thinking in responsibility context makes the classes smaller and easy to control. In addition to that, if you design your classes with modularization in mind, you would have closely connected classes inside the same module. Therefore, when a bug appears, you would know at least which module to check first. Then, you would relate the problem with the responsibility of the classes and track to the root cause faster.
Imagine a scenario that your classes are too long. All the classes have a lot of major responsibilities. Possibly, you wouldn’t know where to look. Because your class organization is too complex. It would consume substantial amount of time.
In game development, reusability is even more important than in traditional software development. Especially in today’s conditions, delivering new games quickly is crucial for developers. If you are rewriting parts that are common among your games, you can’t be quick.
I can easily imagine a common services module for sending scores to a server, retrieving a leader-board, or showing ads. Depending on the tools that you are using, you can let your modules to have their own life-cycles. Meaning that you can release one module independently from the other one. As a result, this will prevent possible compatibility issues of using a module in different games.
Readability is a sure outcome of having highly organized code. When you keep the responsibilities single or small per unit, you would automatically increase the readability of the classes. Besides, if you have a modularized code base, you would also easily read or follow the whole project.
Complex software requires reliability and robustness. Games are complex too. Thankfully, having an easy to debug, readable and reusable code base automatically brings reliability to the table. The game will run much smoother and with less problems. Multiple people would be able to work on the project at the same time.
Control over visibility
Generally, you can make one module visible to another one. Unity provides this functionality as well. We can take the freedom to reach everywhere from one class and prevent future problems. Keeping the classes as less coupled as possible is a good software development practice.
I have tried to discuss the benefits shortly. Now, let’s see how can we achieve modularity with Unity.
Modular Game Development with Unity
Unity is a great tool. It supports what we need out of the box. Creating a new module is fairly easy. Let’s see a use case with an example.
Here is our task: We want to create a chess game. It will support both single player and multiplayer modes. We also want to have a leader-board and show ads times to times.
How to create an independent module?
Let’s know first our terms. Unity correspondence of what I have called here as module, is “Assembly Definition”. Unity organizes the scripts into assemblies when we create an Assembly Definition Asset in a folder. Here is how we create it;
Using the project window, move to the folder that you want to maintain as a separate module. Then click right on your mouse and select Create -> Assembly Definition on the menu. This will place a Assembly Definition asset to that folder. When Unity, sees this asset, it will generate a separate managed assembly for you.
So as you see I have created a core module which named as “Core.asmdef”. It is a simple text file in JSON format. We can edit the file or we can manage it using the inspector window.
This simple file gives us a lot to control as you can see. There are detailed explanation in the documentation, so I will not go into detail.
Dependency Management Between Modules
Let’s see how dependency works between assembly definitions in Unity. Let’s create four different modules along with their .asmdef files; Core, AI, Services, and Network.
You see that we have four different modules above. It does look good, right? We have a clear distinction between pieces. We see their full namespace and folder path. The classes you see, can’t see each other with the initial settings of the .asmdef files.
It is natural that AI shouldn’t be visible to the Network, but AI should be visible to the Core. So we can query the next move of the Chess bot. Let’s see how we can make AiFacade visible.
We will click to the Core.asmdef file. Then in the inspector window, we add Ai to the Assemble Definition References. As a result, AiFacade will be visible to the BoardManager immediately.
I hope this example gives you the idea. I tried to explain it as simple as possible. It is likely that you have seen Assemble Definition before. Now you also know, how to use it for the sake of modularity.
Separate interfaces and concrete implementations:
Obviously you can create modules in the way you want. I prefer to create a separate module for the public interfaces. In addition to that, I never give reference to the modules which contains concrete implementation. I give reference to the interface module instead. Yes, I always try to rely on abstraction. I think this is a good way but you can find your own way of course.
Use Dependency Injection:
Using a DI container would put a cherry top of the modularization pie. If you would like to learn about DI, please check this post.
Find a Traceable UI Development Asset:
Unity doesn’t give a nicely traceable UI framework. You create your UI entities in the scene tree and save them as prefabs. This is not suitable for modularization. There are some good UI frameworks which are file or graph based. You can take a look at XmlLayout asset.
In today’s game development world, it is really important to deliver games quickly. In addition to that, the performance should be good. However, this doesn’t mean that we should sacrifice the code quality. Unity gives us the technical background to create modular game development.
In conclusion, when you have modular code base, it will be a tremendous help. Multiple developers can work together with less merge conflicts. The code will be more readable, robust and reliable. Finding bugs will take less time. You will manage the visibility on high level and Unity won’t have to compile modules that didn’t change. So you can save some time with the modular game development approach.
I will write about how to make modular UI in Unity in my next post. In the meanwhile, could you please give me some feedback about the content or criticize the post.