I didn’t add much in the way of new stuff this week. I spent a lot of time refining and polishing and bug-fixing, hoping to get a new build out to my testers* before the weekend. I’m spending a lot of time fussing with stuff I’ve already written about.
* For the record: I’m SLOWLY adding to my list of testers. I know people will get sick of playing the same broken game week after week, so I’m trying to add new people as the old ones lose interest.
So! Let’s talk about something controversial. Even better: Let’s talk about something incredibly controversial to programmers, and completely tedious, esoteric, and impenetrable to non-coders! Let’s talk about programming paradigms. Some people have asked about how my program is structured, and since I know the answer will leave some people deeply offended, I might as well discuss the “why” before I get to the “what”.
Just to bring normal people up to speed:
At first, basically all programming was imperative. Programs would be structured in the form of do thing1, do thing2, do thing3, etc.
UseCover (space_marine); DoShooting (space_marine, machine_gun); DoYelling (space_marine, gravel_voice); DoGrumpyFace (space_marine);
The idea being that in the above code, space_marine is probably a structure of properties. Like:
space_marine.name = "Mister Chief"; space_marine.bullets = 999; space_marine.hitpoints = 100; space_marine.run_speed = 1; space_marine.height = 1.8; space_marine.dead = false;
By calling this code, you’re doing things TO the space marine. Also that code might also make changes to other things that are NOT space marines. Basically, you can’t look at this code and know what parts of the game state might change. Maybe nothing. Maybe many things. The space_marine might die. It might win the game. It might trigger a change to the next level. It might destroy a dozen other in-game things. As a programmer, you just don’t know how the state of the game might be changed or what sort of things might happen. You have to read the source. This is technically true of all languages except for a few built around the functional programming paradigm, but its particularly true of imperative languages.
The problem we have is that in a big project, our marine will end up having a LOT of properties. In Good Robot, each robot has over 50 properties like this that keep track of what the AI is thinking, when the weapons are ready to fire, how it’s moving, etc. And this is a simple little 2D game. In a 3D game with more complex geometry, multiplayer, tons of weapons, vehicles, and multiple game modes, we’re likely to have a lot more.
In imperative programming, we’re likely passing around this space_marine variable to every system that needs it, and any system can make changes to any part of it. So maybe some other programmer comes along and writes the grenade code. Not understanding this structure I made, he just subtracts damage from hitpoints and calls it a day. The problem is that he doesn’t update the “space_marine.dead” variable. So now we have a bug where grenades can damage you but they can’t kill you.
Maybe he wants the grenade to blast you up in the air, so he just adds a bit to the “space_marine.height” variable. Except, that’s not how that variable is used. It’s not your height off the ground, it’s how tall you are. So now we have a bug where getting blasted with a grenade makes you incrementally taller.
The problem here is that ANYONE (any programmer working on any section of the code) can make changes to a space_marine. And thus everyone needs to have complete knowledge of how a space marine works, what all the variables mean, and how they inter-operate. You end up needing every programmer to understand every system in perfect detail, and that’s just not reasonable. It’s not even possible in most cases.
Worse, you’ve got a lot of side-effects going on. When someone notices that one character is slightly taller than another, they do some testing and find their space marine is 30cm taller than he should be. How did that happen? Anything that interacts with a space marine would have access to those variables. Which system is fiddling with the height? Saving the game? Starting a new match? Entering or exiting vehicles? Something with networking? Running, jumping, using cover, picking up weapons, dying, using guns, melee attacks, taunts, dying, leveling up, rendering, being healed, being damaged, chatting, or performing emotes? It could be ANYWHERE in the game. Have fun finding it.
So then we have Object-Oriented programming. The idea is that we lock away all those marine properties and other programmers can only interact with the marine through the interface:
So other programmers can’t modify hit points directly. They don’t need to know that the variable “hitpoints” exists, how it’s used, or about the “dead” flag. They just send damage values to the marine and the marine object takes care of the fussy details. It knows to kill itself if its hitpoints drop below 1, and it knows what animations to kick off, scores to change, weapons to drop, etc. Other programmers don’t need to know any of it. If another programmer wants to blow the space marine up in the air with an explosion, they won’t have access to the “height” variable but they will see something like…
…and realize THAT’S what they should be using to shove the marine around the environment. The object’s interface will guide this other programmer and stop them from doing something destructive.
This process of hiding the inner workings of an object is called encapsulation. This is a good solution to the above problems. It is not, however, a good solution to every problem.
Java adheres to the Object-Oriented paradigm with unwavering strictness. Everything is an object, owned by an object, owned by an object, yea, even unto the nth generation shalt thy objects be owned.
So you end up with this thing where you’ve got a “game” object. Just one. And it owns a “game session” object, which owns a “level” object, which owns a list of “space marine” objects.
Okay, fine. Now we’ve sealed everything off so there aren’t any side-effects, every object is owned by another, and objects can’t change each other. The problem is that there aren’t any side-effects, every object is owned by another, and objects can’t change each other.
Like, if I want my space marine to create a little cloud of dust when he stomps on the ground, who owns that? If the marine owns it, then killing the marine will make all of his dust particles instantly vanish, which would look odd. If he kicks off a sound effect, I might not want that sound effect to die with the marine. If he’s supposed to drop his weapon on death, then marines need to be able to give the object to some other system.
Obviously we can’t have marines owning their own visual effects, sound effects, and particle effects. So we have to… what? Make a “sound effect” object and have it owned by… who? The game? The level? Should we create some master sound-owning object to act as an MC for all the sounds the game is making? Should we make a particle wrangler that keeps track of all the particles?
If we make one particle wrangler object that’s in charge of all particles, then we’ve basically re-created all the old C-style imperative design but now it’s just WAY less convenient to use. We can’t just call PaticleCreate (stuff), we have to get the game object, which will give us the session object, which will give us the level object, which will give us the particle wrangler.
Also, my space marines need to collide with each other. Collision is incredibly complex. To collide A with B I need access to the polygonal structure of both, the skeleton of both, the current state of both, what each one is doing (I shouldn’t be able to shove someone if they’re attached to a mounted turret or behind the wheel of a car) and how they’re currently moving. Do I have the level object (which owns all space marines) run the tests and make the marines bounce off each other? But then we would need to expose all of the inner workings and logic of the space marine, which is exactly the thing we were trying to hide. Or do we have marines themselves try to bump into each other, in which case each marine needs to be able to find all others, meaning marines need access to the data structures of their owner. We also need a way to avoid duplicate tests so that once we test for A colliding with B we don’t waste CPU testing if B has hit A.
No matter which way you go, you have to poke some major holes in your encapsulation to do this efficiently, and we’ve just scratched the surface.
Weapons need pointers to the marines carrying them, vehicles need two-way access to marines so they can both be driven by and run over them. Marines need access to each other and all the other objects in the game. They all need pointers to the game object so they can access game rules, but they also need a pointer to the current level so they can do collision with the level geometry. And even though the game owns the level, you probably want direct pointers to both since drilling down through levels of pointers owned by pointers can cost you performance if you do it enough times (many objects) or if the layers of objects-owning-object-owning object is annoyingly deep.
In the end, we’ve imposed this rigorous top-down structure and then we spent days or weeks writing code to get around or through it. In a world of pure theory, an object hierarchy makes perfect sense and there’s always a “right” way to do things. Objects can hide behind their interfaces and we never need to worry about their innards. In practice, the right way is not always clear and even if you’re lucky enough to nail it, you may find it requires many transgressions against object-oriented design. You can end up with something that’s just as unsafe and exposed as imperative programming, while also being more cumbersome, convoluted, and perhaps even slower.
In a lot of cases, those “protective layers” of OO design don’t actually protect you from anything, because you have to poke so many holes in it that the thing no longer serves the intended purpose.
All of this is to say: I am not a purist when it comes to OO design.
For years I thought I was bad at OO programming. But then I realized that some problems just don’t lend themselves to an OO solution. In my own code, I manage to infuriate all parties by mixing C++ style object-oriented design with C style imperative. If I’m going to make many of something (robots, sprites, lasers, explosions) then those things are a class. If I’m making ONE of something (like the game, or the gameworld) then it doesn’t use any kind of class interface at all.
This makes it easy to manage large groups of things, but also prevents me from accidentally making more than one of something when more than one would violate the design and common sense.
A good example is the player. Right now you call
PlayerUpdate () to process player input. This performs non-repeatable actions and creates state changes in the I/O. Sure, I could make a
class Player p; and call
p->Update (); but that would make no sense. Like, if you had more than one player object, the first one would eat all of the mouse and keyboard input, leaving no clicks, mouse movement, or key presses for the other. Having more than one player is an impossible and nonsense state, so why make it possible? The presence of
PlayerUpdate () makes it really clear how this should be used.
Q: But Shamus, what if you wanted to make the game multiplayer?
A: Then I’d have to re-write the player code. That would be true regardless of whether or not I was using a class interface. Since multiplayer isn’t even being considered, I don’t see any percentage in spending time making it easier to add features that I’m not going to add. That’s like spending money to put a boat hitch on every car you own, just in case you end up buying a boat someday.
Boom! Crappy car analogy. It’s been too long since the last one.
Getting back to classes. Even when we DO want more than one of something, that doesn’t mean I’m going to make it a full-fledged class.
If something is a container for variables (like XYZ vector data or RGBA color data) then I make it a struct with public members. Making wrappers for individual entries in this case makes no sense to me. When I was using Qt, I didn’t bother with their built-in vector types because some silly OO zealot thought that this code…
pos.x = pos.x - (pos.y * pos.z);
Would be SO much better as:
pos.SetX ( pos.GetX ( ) - (pos.GetY ( ) * pos.GetZ ( ) );
Oh man. That still drives me crazy just looking at it. What’s the purpose of that stupid interface, anyway? If that’s a good idea, then why not:
int a.Set (b.Get () + c.Get ());
a = b + c? So much safer! Screw convenience and compact clarity, we must mindlessly adhere to class-based orthodoxy!
My rule of thumb is that if variables change each other (say, setting “m_age” will impact the value of
bool m_is_senior_citizen) then it needs wrappers and the members should be encapsulated. But if the members are independent then it’s just a container for values and the other programmer should be trusted to know what they’re doing. A class interface is there to hide complexity. But if there is no complexity then the class interface might hide the fact that there is no complexity!
So to answer the question:
Robots are a single class. The only “fancy” object inheritance stuff I’m doing is I’ve got a base class from which bullets, missiles, explosions, and powerups, are derived. Objects in the program can create these entities and then hand them off to the world management code in world.cpp.
So that’s it. Mostly C-style structure with a few dozen C++ classes being thrown around. In places where I do use classes, the interfaces are very tight.
What did web browsers look like 20 years ago, and what kind of crazy features did they have?
The Biggest Game Ever
How did this niche racing game make a gameworld so massive, and why is that a big deal?
Batman: Arkham City
A look back at one of my favorite games. The gameplay was stellar, but the underlying story was clumsy and oddly constructed.
Skyrim Thieves Guild
The Thieves Guild quest in Skyrim is a vortex of disjointed plot-holes, contrivances, and nonsense.
Video Compression Gone Wrong
How does image compression work, and why does it create those ugly spots all over some videos and not others?