Exploring DDD in Game Dev
- 13 min readI thought I’d write this post while I’m still fresh with some experience from GitHub Game Off 2016, Ludum Dare 37 and the Entelect Jam #2. I’ve had some interesting approaches to solving each of the unique problems and also had some help with some of the solutions, but I’m just going to explore some of the first ideas that came up.
One thing to note though is that this article is not so much the how to solve a problem using DDD, but rather to show some of the challenges with having to interact with a domain in a minimalistic enough way as to ensure that you don’t end up bleeding implementation details across boundaries. With that said I’m also nowhere near an expert on the topic of DDD, but it does make out a lot of my day to day job so I’m always interested to see how certain concepts work with regards to Game Dev.
GGO 2016
For this jam I attempted to make a little physics space exploration game. I was faced with doing some gravity calculations and ended up doing it as follows:
|
|
A keen observer will note that to construct a Body you require an IMassComponent and IPositionComponent. I decided on this approach because it was the simplest way for me to make some of the entities configurable through the Unity editor. For instance I just slapped the IMassComponent interface onto my ShipMass script then handed the object for that script to the body that needs to be constructed. This object now managed to grab onto the Rigidbody2D component of my ship which is a Unity provided component for physics. The same goes for the IPositionComponent as the ship will end up moving quite a bit inside the game word and I needed a way for my domain to hook into that.
This approach required quite a bid of coordination from the Unity scripting side, but it managed to still get the job done. It also allowed me to now calculate a netto force that all other bodies exert on the ship which could then just be applied to the ship’s Rigidbody2D component again and then have the physics engine do the heavy lifting of moving the ship about. This approach does mean that you end up building quite a bit of interfaces to be used by script objects, but if you can keep it all in nice little components the risk of having domain logic leak into your scripts become low enough.
If you do look at the ShipGravity script component I wrote that actually applies the gravitational forces you’ll note that there are some “scary” singletons floating about.
|
|
This was something that I just had no idea how to solve and I just ended up deciding to leave it as that because I don’t require anything else. Ideally I would like to have my Unity scripts not require so much intimate knowledge about my domain, but I was still figuring this whole thing out.
Ludum Dare 37
On 9 December 2017 my cousin and I set out to partake in this amazing jam. The theme was One Room so we hashed out a simple Spy vs. Spy type game as it “fit” the theme with each player’s camera only showing one room at a time. This required a bit of a different approach as we decided to abstract only the players being present in certain rooms and moving between them. We designed the domain in such a way that we only define the core state related concepts of our game which means players moving about in a room was of no concern to the domain. Players attacking each other though was a domain concern so we would run damage through there.
|
|
Above is a code snippet of our player class. This was a game we made directed purely towards our cousins so we took some liberties in the naming of things. Looking at the vast list of EventHandlers on this class it’s blaringly obvious that we ended up having to do some horrible things in order for our UI and such to work properly. What these EventHandlers did allow us to do though was to only handle updates to our state and apply it to our UI elements when needed instead of polling for it on Update in some MonoBehaviour script.
The thing was that for our Cousin objects these registrations were still quite straightforward. Problems arose when multiple different elements required access to the object in order to subscribe for these events as we either had to pass the object around or had to have some way to be able to “query” for the correct object. It’s also the reason why we ended up with this GameController singleton that ended up doing so much that it became a bit difficult reasoning about some of the scripts and how they work. Now this was a weekend jam and we did manage to write a LOT of code in order to get this game to work so it was understandable to have the results we’ve had. I’m still happy with what we achieved on this jam in such a short time as we actually managed to scope the project way beyond just the weekend and still delivered something playable.
Barring all this it might have been a better choice to rather have all the EventHandlers be static references that could then be used to distinguish where events are coming from. Another possible answer would have been to abstract the handlers behind a single event queue that can be consumed globally so that any emitted events will be processed where they are needed.
Entelect Jam 2
This jam was a very recent one where we put together a very quick and dirty 2D platformer seed project for everybody to use. I’ve been toying around with trying out something similar to the behind the scenes turn-based play that Star Wars: Knights of the Old Republic so I thought I might try it with a fighting platformer. It was a very ambitious idea, but I was armed with some new knowledge that I’ve gained from doing some Android development I decided to see how the Model-View-Presenter pattern treats me.
There was initially quite a bit of friction for me to get everything setup correctly and in the end I still had to opt for a Singleton presenter to work with, but that wasn’t the biggest problem in the world for the scope of this project. The following is the script responsible for handling player input:
|
|
From the above I have decided not to put a View interface on the script as it was only providing input. Next up is how the presenter would handle horizontal input:
|
|
The players dictionary is just to ease the process of working with the correct Player domain object. This is where things become a tad interesting as well as I’ve had to find a balance of where to put what logic. In the case of movement there were certain criteria that had to be met that would have the player provided input actually result in the player moving. In this case it was whether the player was in such a state that he can move of his own accord. Next I had to check if the input provided was for moving or stopping as no horizontal input would result in the player standing still as well as some other use cases. This check also included something that could have been pushed more towards the domain and that was related to having player input be able to stop the players movement. Lastly if all these cases have not been met we’ll pass the input along as a value to be used later for calculating the movement speed:
|
|
Now it’s important to note that previous experience has taught me to handle input on frame draws as we as humans like to react to what we see. I only handle movement in the physics loop as I’ve found it to provide the most consistent feel for the input provided. This is now where one of the Views will start to be used:
|
|
So the basis of what’s happening here is that the script locates the Rigidbody2D component attached to the player’s character, then for each physics step it will make a call to the presenter to step the player’s physics related calculations. These calculations included not just horizontal movement, but also jumping as well as the effects of kicking another player.
|
|
It’s quite basic as I’m just checking some flags and the calling the correct methods on the view to ensure that the physics engine can go and apply all the velocities and forces exerted on the player characters. I took the same approach for most of the inputs to ensure that inputs only have the effects they should when the model/domain is in the correct state. This allowed me to implement a quick jump handler in the presenter using a check on the domain on if the jump should happen or not. Then it was about 3 lines of extra code in the domain logic to implement a double jump.
As I’m writing this however I’m noting more and more logic that I could have pushed into the domain that would likely have had me be able to fix bugs more easily during crunch time, but I do still think this was quite an elegant solution. There are certain other implementation details that I’m not too sure of on if my implementation would cause further problems down the line. One example of this is the controller script not really being tied to a View interface. One way to possibly get around this would have been to have a bit of a basic one that would only specify which player it was attached to. That way I could get rid of the playerView dictionary in my presenter and rather hand the applicable view/s to the presenter when making calls to it. The main thing is that I’m not sure about this yet and will have to experiment a bit more.
Conclusion
Out of all 3 these implementations I personally would suggest the MVP approach. It might add some initial complexity, but I do believe that it helped me so much with not having a bunch of interdependent scripts that I don’t quite get to run in the correct order. In the end I might end up still using some events strewn throughout my domain implementation, but I’ll likely opt for using it in conjunction with some event bus where the different presenters can subscribe to. My initial approach can also work as long as you keep the interfaces attached to the script objects very light and not have them implement domain heavy logic.
This has been quite a journey of discovery the past few months and I’ve learned quite a lot from both my day job as well as the dabble attempts I’ve made. One thing that I have noted is that while my DDD skills do lie quite strongly with being able to design a given process, I do struggle to approach a game idea and then finish it. I usually get lost on some implementation detail that sends me spiraling towards adding more and more gameplay features instead of making a game that can actually be played and finished. I do believe that having seen these interactions with a domain in Unity is very possible and will definitely continue developing games like this as it’s had an overall improvement on the types of frustrations I’ve had when finding and fixing bugs.