There’s probably a bit more to do on isolating dungeon building from dungeon running, but the low-hanging tasty fruit has mostly been harvested. As for larger design improvements, I am at a loss. No exciting resolution … yet.
I asked for opinions and advice from my betters on a private Slack that I belong to, regarding design ideas for the Dung[eon] program, particularly designs that I might now prefer and that I might be able to refactor toward. I learned at least one new thing, the notion of Entity-Component-System architecture.
As I understand it, and having never built it or even used it, my understanding is surely incomplete and perhaps entirely mistaken, the ECS architecture treats all the objects in a game as “entities”. A entity is typically no more than a simple identifier, perhaps just a number. Each entity can have zero or more “components”, each one typically relating to a small cohesive chunk of knowledge or behavior that the entity might have, such as position, velocity, health, whatever. If an entity does not have such a component, that’s fine. Then there are “systems”, which are interested in just one component. Systems independently loop over all the entities, and for those having the component of interest, the system does whatever is appropriate, such as move the position by a velocity step, or draw the entity at its current position, and so on. The code in each system, it seems to me, is pretty much the same code as a more object-oriented design would place in the entities that move, or display, or whatever.
The purported advantage of ECS is that entities can have any combination of components, and only the components they have will be exercised. In an object-oriented design using inheritance, it’s generally not possible to create a hierarchy that allows for all combinations, or when you do, things get messy.
ECS proponents tout this as a big advantage. A competent OO person, if we had one in the room, which assumes facts not in evidence, would point out that inheritance is only one of the cards in our hand, and as Kent Beck put it, since it can only be played once, it is best to keep it in your hand as long as possible. Generally, we would use “pluggable behavior”, “strategy object”, and, in general, OO “composition” to allow for more behavioral flexibility than pure inheritance would grant us.
ECS is popular in game systems, and I am told that Unity, for example, is an ECS-based system.
The joys of ECS notwithstanding, our hierarchy isn’t bothering us, and so there’s nothing beyond curiosity to drive us in that direction.
The notion of a Vision object was offered, the idea being that entities have a Vision and can ask it “What direction to the Princess” and such things. The Vision has finite range and will reply accordingly, either with the direction or, probably, “I see no Princess here”.
That’s an interesting idea that might allow the Monsters to behave more realistically, but it’s not a sort of design change to refactor toward, unless, of course, we think of some game behavior that Vision might serve.
Another idea was to make everything in the game an event. All the objects would subscribe to whatever events interested them and when an event occurred, it could publish including the position where it occurred, and subscribers could decide whether they were interested.
This is a bit more juicy for my purposes, which are to see whether there are designs that I “wish” we had used. The biggest issue that I have with the notion is that there are nearly 10,000 Tiles in a dungeon, and processing 10,000 events seems like it would be heavy. We already process all the tiles at least once, no, twice, in
function Dungeon:drawMap(tiny) for pos,tile in self.map:pairs() do tile:draw(tiny) end end
It’s twice, because we draw the large map and tiny map separately. We could of course unroll that so that we only iterate the tiles once, if it were an issue. Our game is not a real-time sort of thing, and unless the human player began to see lag, we wouldn’t be concerned. In a different kind of game, we might need to be more clever.
Be that as it may, I do like the event idea, and it has proven to be useful in our
EventBus, so we might want to push for more such behavior. As an overall design, interesting but not compelling.
A wild idea was offered, which was to represent everything that happens as a transaction that changes the game state. Objects would influence the game by pushing transactions onto a stack (FIFO I think) to be applied. The idea was that the game would run through all the transactions applying them, all the time. Clearly this could be optimized if need be.
The more I think about this, the weirder it gets. If everything is a transaction, then presumably there’d be a transaction for each tile, placing it at some x,y location. Later there’d be a transaction placing a monster on one of the tiles. Processing the transactions, we would encounter each tile transaction, draw the tile. Later the monster, draw that wherever. When it moves, a transaction deletes the monster from the one tile, puts monster on another.
Yeah. I don’t know. Weird. It was intended to be a weird idea and that was fully accomplished.
The inventor of this idea called it Domain Driven Design, and then Inception. The idea is that one would define areas of responsibility, such as a kind of Monster, or the Princess, and code each of those as if one were a separate department in charge of that thing. Discover commonalities over time.
While that way lies more madness than I have room for in my head, However, a notion close to that is the idea of a team building the game engine and a team building assets, or even a few teams building assets, and we know that in a “real” program, we’d want a better way to do those things. The “Making App” idea applies here. We might even have several different Making Apps.
Therefore …. What?
Therefore … I have no ideas in hand that make me say “Oh, wow, now I see what we should have done!”. While there is surely plenty of code that could be improved, and probably many misplaced responsibilities, I have no overall idea that seems to be where we’d have gone had we only known what we know now.
I do have smaller ideas.
I suspect that we could make better use of our
EventBus, to reduce coupling. We can’t easily use it to ask questions, but we can certainly use it to tell our objects things that they need to know. So we should look for opportunities like that.
I think that we might want to push further on building up dungeons, in the direction of a Making App. That would probably be rather challenging.
I feel that I should continue with the Hex map idea. I don’t want to. It seems tedious, and I’d have to do a lot of art before I could draw a credible hex map, and the learning would be limited.
Relatedly, UberGoober on the Codea list is trying to separate drawing from operating enough to build a “3d” game, a platform with cuboid kinds of players on it, not unlike this:
I wish him luck with that. I think he’ll find it challenging. And that surely identifies a design flaw in the program.
Ideally, the drawing of the dungeon should be entirely independent of the rest of the game. The dungeon would have all the tiles and such, and we could render it in any desired fashion. We might be far from that. We might be near. Maybe I should get interested in that.
But Therefore … WHAT??
Honestly, I don’t know.
When we talk with product people about an incremental approach, we advise them to do the most important things first, then next most important, and so on. They often ask “How will we know when we’re done”, and we answer “When you run out of things you’re willing to pay the time and money for, you’re done”.
They’re not always receptive to that idea, because they are used to specifying the product in full detail. But it’s true, nonetheless: when you can’t think of anything you want to do, you’re done.
Maybe we’re there, or nearly there. I’ve been dithering for a few articles now, looking for big things to do, new problems to solve, and the only one at hand, hex maps, I don’t want to solve.
Of course, to make a real game, there is a huge amount of work to be done. We need more monsters, more NPCs, more quests, more interesting rooms, more treasures, more more more. But is there anything truly different that we need, anything truly challenging?
Perhaps not. I’m not here to do a year’s tedious creation of assets. I’m here to explore how to build code in an incremental evolutionary style, and to discover what goes well and poorly as I do that, and to perhaps digure out what I do that tends to work well, and what tends not to work well.
Perhaps I need a new problem. Right now, I don’t see anything that seems juicy enough to entice me to go after it.