Learning Architecture, MessagePipe, and VContainer by Creating a Game Requested by My Own Daughter
What is this all about?
One day, my (almost) 3 years old daughter requested me to create a game. After a while, I found out that a messaging pipeline (Pub/Sub) library called MessagePipe has been released. Then, there’s Dependency Injection library called VContainer that I’ve been curious about. While I’m also on a Journey to find a good-enough architecture for Unity, I take this chance to do it all together.
The game itself is a simple number guessing game named Tebak Angka (Indonesian for “number guessing”). Given a number, pick the correct number from selection of numbers. As a game itself, it should be easy to be made. However, my main focus here is to try an architecture concept while also learn MessagePipe and VContainer.
I’ve been learning Clean Architecture for a while now. I once mistakenly thought that the Clean Architecture as a framework, and I learned the hard way that it is not. That mistake made me detest Clean Architecture. As I’ve tried other Architectures, Design Patterns, Frameworks, etc., I realized that Clean Architecture is actually a concept and never a framework.
As for my understanding, Clean Architecture emphasize on independency from other frameworks, tools, or library that might be broken at any given time. However, nowadays it is really hard to not dependent to other library as it is actually very helpful to development in general. So, in my understanding, it is OK to hand pick few of the most useful framework or library. This time, it is MessagePipe and VContainer. Anything else, should be designed to be replaceable.
To begin, above are the (simply designed) class diagram for this project. I tried to follow the Clean Architecture concept. I tried to use layers of Domain, Presentation, and View. I also add MessagePipe layer as a bridge between Domain and presentation layer. Now let’s discuss on each of the layer.
This layer contains the business logic of the application. This layer makes the application very specific and different from other application. For this project, this layer made it clear that this is a number guessing game.
This layer actually has two sub-layers: Game Model and Game State. Game Model only contains data while Game State contains no data but processes data from Game Model. In Clean Architecture’s definition this would be equivalent to Entity and Use Case layer respectively.
The Game State represents the flow of the game. Game starts with level generation state where the question and answer set is generated along with level progression and difficulty. Next, the game progresses into user input state where the app waits for the answer from the user. Finally, the game check for the user’s answer. Game state flow is managed by using a Game State Engine.
The logic of the game is clear enough. Generate question and answer, wait for answer, check for answer. At this point, It could be said that the game is actually completed. However, it is still a logic only, a very boring game without anything displayed and without anything interactive. There’s no implementation nor mentioning of how to display the numbers, how are the animation, or how the user will submit their answer. These are outside the business logic scope and will be handled in the presentation and/or view layer.
Below are the implementation of the domain layers.
This layer will handle information from Domain Layer then forward it to the View layer. If a conversion of information is necessary, it will also be handled in this layer.
According to Clean Architecture, Ideally you should use Interface to communicate with the View Layer. However, I decided that it is OK to reference directly to Unity’s classes as this is an actual Unity Projects. The reason being I had no intention to made this project convertible outside Unity, so why made the code complex by adding unnecessary Interfaces. I tried to follow the KISS principle here.
The idea of presentation layer is to present the domain layer to the user. In this case, I’d like to let the user know about the question and the available numbers. Then wait for the answer from the user. After answer had been submitted, present to the user whether the answer is correct or wrong.
In Clean Architecture term, Presentation layer is equivalent to the Interface Adapter layer. Referring to Clean Architecture, I add sub-layers in Presentation Layer: Presenter and Controller. Presenter manages outflow data to view objects such as animation, visibility, effects, etc. On the other hand Controller manages inflow or input data such as button press event.
Below are the implementation of the presentation layers. Note that I did not attach all the codes since it would be too complex to do so.
The class diagram made it looks like the View/Unity part smaller. In reality however, many parts are happening there, such as animation, audio source, particles, scene management, etc. This layer is all about Unity basics. Components, Classes that derived from MonoBehavior, UI, etc. would categorized to this layer. This separation between logical and design/UX could be helpful when working on a team with designer, who would not want to touch any of programming part.
The main idea of this layer is the visible or interactable part of the application. Personally, the characteristic of the View layer would be when left alone it can has very little to none correlation to the whole app. For example, a number in this app would mean nothing alone, but had a meaning of a choice in this specific app. Who knows maybe i want to use it in another app as a score display.
Below are the sample implementation of the view layers.
Architecture is not a framework, nor it is a Design Pattern. Architecture is how to structure a project. Within a project architecture it is possible to use more than one design pattern. This is where I made my biggest mistake on learning Clean Architecture. Since I’ve thought an architecture as a framework, every single part of the project must follow certain rules or design pattern. And that is definitely not good. Use an appropriate design pattern wherever necessary.
In this project, I used The State Pattern on Domain Layer. Basically, State limits any process within the scope of the state. Any other process will have to wait the state to change. I separated the gameplay into states. The states then flow from generating level, waiting user input, checking result, then start over from generating level.
Though implicitly, I also used The MVP pattern on Presentation layer. To be precise, the Presenter are controller class and the presenter class itself, the View are Unity’s Objects and classes derived from MonoBehavior, while the Model are value received from MessagePipe via subscription into IAsyncSubscriber.
MessagePipe are used as a bridge between domain layer and presentation layer. In this project I use
IAsyncSubscriberpair so that Game State can (a)wait for the Presentation and View layer as a mark when the current state is finished.
The reference in the actual class are only the interface. The actual implementation of the interface are within the MessagePipe library and are injected using a Dependency Injection technique. In this project the Dependency Injection library are VContainer.
I don’t do anything specific on the MessagePipe part other than made reference to the interface and handle its injection, but it has quite an impact to the actual project.
Dependency Injection library that is lightweight and quite powerful. I’ve used other DI library for Unity before, but I personally prefer VContainer out of simplicity.
What VContainer do is basically hold references to the classes used in the project, then inject them to the classes that need it. This is very helpful as managing references by our own would be quite a chore.
Below are the sample implementation of the VContainer class in this project.
I understand that I’m far from perfect. There’s a lot that I don’t understand and I’d continue learning. Few points that I still have doubts are below :
- The architecture in this project made reference primarily to Clean Architecture, but I’m not sure as to call it Clean Architecture as It might also had reference to DDD, and others. But one thing for sure that it is a Layered Architecture.
- It is hard to decide whether CardView and ResultView should be considered a View Layer or Presentation Layer. Personally, I think they have a similar role.
- I Personally think that the DI part can be separated either by Game State (Use Case) or Presenter, instead of one big DI of GameplayLifetimeScope.
- The project in this writing are simple. In a larger project, there might be difficulties on separation of layer, roles, etc. This doesn’t guarantee a perfect layering example for other projects. But I personally think that this is “good enough”.
- Clean Architecture is never a framework, it is a concept. Don’t make the same mistake.
- It is OK to depend on few hand picked framework or library. In this case MessagePipe and VContainer.
- It is OK to use any suitable Design Patterns in any part of the layer. In this project I used The State Pattern on Domain layer.
- This project use a layered architecture and has a Domain Layer, Presentation Layer, View Layer.
- Domain Layer handles business logic and defines the project as a specific application.
- Presentation Layer forwards and translates information from Domain Layer to View layer.
- View Layer handles the visible, hearable, and interactable part of the application.
- MessagePipe is a Pub/Sub Messaging Pipeline library that useful as a bridge between Domain Layer and Presentation Layer in this project.
- VContainer is a Dependency Injection library that is lightweight and quite powerful.
Thanks for reading! Any constructive feedback is highly appreciated!