👩💻 Monogame - Defining Game Objects As "Units"
So in switching to Monogame, I've had to start over from scratch again. And really from scratch, too. Godot and Unity have the benefit of being a game-making framework, with methods, logic, etc. already set up for you to play with. I used velocity (ie: movement) as an example in my last post, but there's so much more available to really let you just jump right into making a game without doing the heaviest of the lifting.
But Monogame doesn't have that luxury. Rather than being a framework, it's a library. It allows you to start from scratch and, in theory, create your own framework. So that's what I'm doing! I'm making a backbone that I'm hoping will be reusable over multiple projects. By doing this, I can define the framework to work how I want and expect it to, and know the how's and why's of the inner workings as well.
I am taking some notes from Unity and Godot, though. For example, Godot uses "Scenes" for most (all?) things. The player, the environment, a chair in the room, etc. I never liked that they're called "scenes" since making a character a "scene" is not very intuitive - a scene sounds like it should be a stage/level. (I think Unity uses "actors", if I remember correctly? Which works better.)
I've decided to call mine "units". I don't like "scenes" because a person is not a scene. I don't like "actors" because a chair is not an actor, it's an object. Unit is more generic and makes my brain happier.
The Units
Right now I have three major types of Units that have their own subclasses: CollisionUnit, BackgroundUnit, and GuiUnit. These all inherit the interface1 Unit.
Collision units are your basic character/object unit. If a unit can be touched by the player character, or... really any character, it's a collision unit. It responds to physics and is limited by other collision units. Collision CAN be turned off though; like if there's a ghost character, you might have it flying around and going through certain objects, but it can still interact with the player in other ways. The ground and walls are also collision units.
A Background Unit strips away the collision physics. Think of this as decorations you can't interact with; the sky visible behind the player, trees in the distance that aren't part of the background image, a picture frame or window on the wall, etc. Ideally these have no actions that can be used or used on them, otherwise they would need to be a collision unit.
A GUI unit is the user interface, obviously. Your health/mana bar, text on the screen, etc. Some of these can be interacted with by the player, but some are just visual. The player character doesn't really touch them, and by default they'll overlay the rest of the screen.
What Makes A Unit?
The Unit holds all the data it needs to know about itself. Its sprite image, size, position/coordinates on the screen, etc. Whether or not this unit can be clicked, or feels collision or gravity, any color overlays on it, etc. If it has a name, it knows it. They even have their own Draw and Update methods so they can handle any unique logic they may want to apply themselves.
There were many times when putting my units together that I realized I needed to add a property, and thought about how Scenes in Godot already were doing something similar (if not exactly the same) and it clicking for me why Godot had done it. That's something I like about writing my own framework, is coming to understand the whole process better. Maybe if I wanted, one day I could go back to Godot and take another swing at it, and be more forgiving since I understand some of the design choices better now.
Accessing Units
Since I've been a software developer for ~10 years, I'm organizing my code more like how one might organize a typical software project rather than a game project. When I was working with Unity and Godot, I noticed I wasn't able to just pass data around the way I wanted. I don't remember Unity specifically, but Godot uses a "signal" system which is sort of like raising events, but... dumber? Maybe it's fine once you're used to it, but I kept forgetting how it works and just struggling with it.
So I'm using something that's closer to an MVC setup. I have controllers2 that hold my data and the main "form" (a custom Monogame Game.cs class I've extended) passes these references around as needed. I can always access any unit I need to in child classes, and clear out units that are no longer needed myself. Units in particular are held in several lists in its controller (the UnitController) and the game can search a list whenever it needs one or more. When drawing to the screen, or updating units, it iterates through based on the unit's type.
protected override void Update(GameTime gameTime)
{
foreach (Unit unit in Units.ObjectUnits)
{
unit.Update(gameTime);
}
foreach (Unit unit in Units.BackgroundUnits)
{
unit.Update(gameTime);
}
ActiveView.Update(gameTime, this);
base.Update(gameTime);
}
This is cleaned up from what my code actually looks like, but it communicates what I'm talking about. Also, the
Draw()method looks similar: It iterates through lists of Units and draws each one!
Side note, but if you know what you're looking for here, I try not to handle my user input via the
Update()method. That's handled by theWindow.KeyDownevent! I feel like that's not standard, at least looking at tutorials online? Which is wild to me. But maybe that's just how tutorials are to explain keydown reading, and not how people do things in actual practice.
In some cases, I'm not creating my units in the Game class, but in a "View" (which I may talk about in another post). In these cases, I then pass the unit back to these controllers in my Game class so they can always be tracked and managed the same as all other units. The View may continue to keep tabs on these units, but they always need to be collected by the controllers. Something I disliked about Godot was losing track of where my scenes were - which, again, I'm sure someone more experienced in Godot can do that easily, but I struggled with.
The Wrap-up
Anyway, I think that's the bulk about Units. I might just go through and talk about some major pieces that I've made in my game before sharing some more fun code snippets. We'll see how it all goes.
"Interface" is a C# concept, for anyone reading this but unfamiliar. If you make a class that inherits an interface, it's like a promise to set up that class in a certain way, which makes it easier to access its properties/etc. Multiple classes can inherit the same interface, which lets you use them in similar ways to each other (and sometimes interchangably.)↩
"Controllers" are a programming concept of a class that holds data. You could have a lot of classes that hold data all over the place, or you can have a controller that acts as the main hub for your data so it's easier to pass around and find when you need it. One is definitely easier than the other once you have it set up.↩