White Shining Rock Logo
White Shining Rock Logo

Game Code Design

November 9, 2018 | Dukus | 19 Comments

I don't quite know how to write game code properly. It's a bit of a mystery because I'm still new at it.

My experience working professionally on games for the last 17 or so years had mostly been writing systems. My main job used to be writing graphics engines for consoles, but I also wrote collision, physics, and tool chains for content creators. And because part of my job was getting the entire game engine running on consoles, I had exposure to the audio, networking, i/o, asset management, and input systems. I like systems. They all have definable expected results.

Despite the complexity of any one of the major systems in a game engine, it mostly takes some data, does some stuff to it, and outputs a result. The systems make the artwork appear on screen, make the physics behave correctly and perform well, make the audio sound just so. Given requirements for what you want a system to do, it's usually easy(ish) to design and implement. Especially when there are clear lines of separation between systems.

Before starting my own games, I never saw the game code. It was someone else's job to implement it. Sure I knew the basics of what was in the code - state machines, entities, various channels of communication, and scripts that made things happen. And there was also AI code, responding to input, user interface code, and more. And it was all very tightly tied together. And the games tended to be iterative - after initial implementation, features got tweaked until the game shipped.

How do you organize that sort of code and make it easy to refactor? And is it possible to keep the entire game design in mind when implementing things initially?

I'd never done it before, so for Banished, the answer was no. The design of my last game was iterative until near completion, so I just kept adding things to the code base as needed without an overall plan. Things could have been implemented in a cleaner and more productive way, had I been able to stand back and look at the whole picture. Sure I occasionally refactored things when it got hard or messy to continue forward, but a full rearchitect of the game level code wasn't in the cards.

So now that I get to mostly start over, I'm trying to take a more systems level approach to the game code. And while my design isn't fully written out and all the little details aren't set, I know the overall shape and size of the games features. Luckily since the new project I'm working on is similar to Banished in that there's indirect control over some people, I can take a lot of what I learned and apply it.

Entities

I had the concept of entities in Banished. In other game engines these are known as objects, pawns, actors, things, etc. It's basically just something that does something. It could be the player character, a torch, a chest with treasure, a tree, or an monster manager that is spawning zombies. But by itself, it does nothing. In my implementation, I add components to entities which adds functionality - a model to display, audio to play, an ai and movement controller, and many other things. Basically each component gets a chance to do something on creation, update, and removal.

I've kept this for my new project as it's one of the things I got right - but I'm extending it further. In Banished there were a lot of things that weren't entities - the terrain, the sunlight, camera, object selections, object placement, player toolbars, the map data, clock, menus, minimap, and the weather system. And they all required extra manual code (and sometimes repeated code!) to use them in a bunch of places. (If you're clever you'll notice those are the things that are global to the game, and there's only one of them at a time.)

In my new project things like that are now entities as well, since they only do things on creation, update and removal. Having a unified system for all game objects also makes writing other things easier, like save games, since everything fits into the same mold. I also had separate game loop code for loading screens vs the main menu vs the main game. Now it's just one game loop that can do anything based on the entities used.

Hardcoded Data

Early on in Bansished's development I got things working too quickly. Things like professions and types of raw material were coded in C++, rather than configured as data. As you can imagine, late in the game adding a new professions or item type was painful! I had to touch many source files and make sure everything worked and nothing broke. I eventually made professions configurable through data, but the item types were so ingrained in the code that changing it to data instead of code was a task I didn't want to take on.

This is not a mistake I'll make again - any game concept is now made generic and configurable. My rule of thumb is if I think of creating variable names with nouns or descriptors, (Pot, Clay, Bronze, Edible, etc), it's probably something that should be data, not code.

Hierarchal State Machines

State machines in general are useful. The idea is some object is in some state, and while in that state only does certain things. Some event may occur that causes a transition to another state, which has its own things it does. And so on. But they caused me a lot of headache in Banished. With a hierarchical state machine, you can override a state with something new. So let's say you have an entity that is a Box of Treasure. It's normally in the closed state, and when you interact with it, it transitions to the opening state, which plays an animation, and when that's done, there's a transition to the open state, and it gives you some gold. Now lets say I want a Trapped Treasure Box. I only have to override the open state, and instead of giving treasure, I write code to shoot an arrow at the player. I didn't have to rewrite code for the closed state, or the opening state.

In Banished this worked well for adding components to the building. Each component added could implement a state machine, or overrides various states. There were lots of these - partial state machines for gathering resources, building, handing out jobs, being on fire, being diseased, being destroyed, etc. The problem was not all of these were on each building or field, so I had to make them work regardless of which were present or not. It made it exponentially hard to write - should the parent state be called? Should it not? If there's not transition function between states, should it call the transition function in parent states? Does it work if the components are in different orders? This was a huge source of bugs that took a long time to work out.

In my new code, I've got state machines being used, but I'm building them more carefully, and avoiding hierarchies, especially deep ones. Once state machines get to large, they're hard to manage and think about.

Character AI

Ah, this was a mess in Banished. Some hand written code made overall decisions about what to do. It was prone to breaking. What to do if a character is hungry, diseased, and his house is on fire all at the same time? Which has priority? Does it depend on what everyone else is doing? Have I handled all the permutations? I'd add a new concept like being cold or being happy, and that would break something (like not starving) that had been working because the decision process changed or I didn't add the proper checks in the right places. Once the decision was made, a list of actions for the character to carry out would be generated - sometimes by a different chunk of code, like the global general work list, or a building state machine that was handing out jobs. Something like - walk to storage, pick up logs, walk back to workplace, drop logs, etc. Which might be interrupted at any time because the AI deciding to do something else important. The code was spread out over too many places and was a bit prone to breaking.

This time I'm implementing a system that's a bit more configurable and unified. What I'm building now is an overall priority system that weights each characters needs. So things like food, water, warmth, sleep, shelter, companions, possessions, daily schedule, working, helping others, needs of the village, emergencies, and special events will be weighted based on the current situation, and the best one will be selected.

For each of those a behavior tree will drive how each character achieves each need to allow many ways to solve an issue. For example, there should be maybe ways to find food. Is it in my inventory? Is prepared food available nearby? Is preserved food available in my home? Can I ask my neighbors for some? Do I have to get it from storage? Can I ask a hunter to prioritize getting food? Should I walk out into the woods looking for mushrooms and berries myself? Can't find any after a while? Hmm, time to leave the village for a better one that has food.

This sort of decision making and planning will be mostly data driven, so that adding new behaviors requires little to no code. At least I can hope so.

User Interfaces

I'm pretty happy with the way the user interface in Banished turned out. My only issue with it was that there was a lot of code to make it work. Something like 20% of the game code for Banished was UI. If I designed a UI with a button on it, I had to write some UI code to find the button by name, configure it to receive an event, and then when the event occured, call some function on an entity.

So I've rewritten the UI code to be able remove the need for the in-between code that manages that UI widgets - I can just create a UI layout with a button widget that binds itself directly to the function on the entity. The intermediate control code isn't needed. This works for all sorts of widgets and values, as well as text and sprites that appear on the UI. While this won't reduce UI code to nothing, it should help to reduce the amount of code to manage.

Another UI change I've made is that I've separated the way the UI looks from the way it behaves. This way I can easily restyle the UI and also create widgets and layouts in code and have them styled the same way as everything else.

Performance

For the new project, I'm planning farther ahead to deal with performance issues and use more CPU cores. When I started my game engine, I consciously chose to limit multithreading to keep things simple - after all I was working solo and wanted to get initial implementations running quickly. Some things did end up in different threads, like fine grained pathfinding, but everything else - updating entities, drawing, coarse pathfinding, searching for locations, etc was done sequentially.

This needs to change this time around to make a more scalable game.

While a lot of other current engines are running entity updates in parallel, I'm not choosing this route. Threading updates where multiple entities depend on each other is hard. Really hard. The goal is not to use any locks or thread synchronization. This requires really breaking up updates into small chunks, limited or no direct access to other entity data, sending messages to other entities, and waiting for responses. This makes designing the code hard, makes it hard to debug, and hard to modify later if you forget whats going on.

I'm also not sure that updating the entities will be the bottleneck this time around. Entities are now just decision makers and controllers for engine level systems, and most of them don't update every frame. It will require profiling once it's in place to know for sure, but I know other things are going to show up with significant time use on the profiler first.

I'm preparing for moving all the heavy lifting into systems that can be easily parallelized. All animation, character movement, particle systems, pathfinding, ray casts, spacial searches, spacial subdivision updates, and more, are all fully separate systems and can run on different CPUs easily without dependencies. If the AI needs to wait on a search for nearby objects, or how to get from A to B, or other expensive operation, they can just idle a bit until the result comes back from a lower level system.

Additionally the entire rendering pipeline can run start to finish on a different thread if it runs a frame behind the updates. As I don't plan on making games requiring quick twitch input response, I don't believe this will ever be an issue or even be noticed. I've done this before on consoles engines, and it can free up a ton of frame time, making it available for updates. If required I can parallelize culling and command buffer generation within the rendering, but I'm not sure there will be huge gains there unless I'm also supporting DX12 and/or Vulkan.

The Plan

Anyway, that's the plan for this time around - I'm sure that these changes to the way I structure the game code will help to make the code easier to use and update, make a more extensible game engine, and teach me new things as I go along. But I'm also sure at about 80% complete on the game, the code is going to get start to get messy again in the push to finish as I implement all the small items I didn't consider ahead of time. Which, obviously, I'll fix in the game after this one.

Leave a Reply

Your email address will not be published.

19 comments on “Game Code Design”

  1. I am a software developer and if you need help or if you have a seat open for individual like me then email me and we can make a better game for fans

  2. Always get choice anxiety at this stage of development myself, searching for some kinda holy grail of design that will blind all spectators in its simplicity.

    There isn't one of course, but I always enjoy reading someone else sweating through it for me.

    Hope you decide to give Vulkan a bash, the reduced overhead gives quite a hefty bump in CPU performance on my FX series.

  3. >> Which, obviously, I’ll fix in the game after this one.

    Ha, so recognizable ;-). Thanks for the read!

  4. All the talk about treasure chests had me a little excited about a Majesty clone in a Banished world.

  5. Got a suggestion for threading updates.
    Split the update step in two.

    The first part does pathfinding, decision making, and puts a list of whatever actions the entity will do into an array.
    This can then be threaded, if another part of the entity needs to controll where to pathfind, simply make the decision about where to go this frame, and path it next frame.

    The second part is sequential foe all entities and applies changes. From health damage, to movement.
    This allows you to strip out all parallelizable work and get the most benefit from the CPUs.

  6. Thanks for the read. It helped me to understand how Banished was set to work. I do hope State machines will help you to keep things at their proper place and while you were talking about UI and about it has alot of code.... there were also tons of code that were set not at the good spot. Like you explained to me at some point about the merchant code was tightly bound to the trading post UI abd it was impossible to call the TP UI without triggering the merchant code... i do hope everything will be set at their proper place and not bound to anything else. i am sure there was a tons of things set in the townhall UI that wasnt the best option to be there and would have been better to be set at its own spot.

    but i have a question. Do you plan to allow mods this time ?

    Continue your good work, Luke 🙂
    we loved Banished and still enjoying it.

    -Red K

  7. Hi there,

    I am not a game dev. I mostly do ui development for the web. I have just started using statecharts to model the more complex parts of my application. They are state machines on steroids. Here are some link if you are interested.

    https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf

    https://statecharts.github.io/

    https://xstate.js.org/docs/

    Anyway, I really like your updates and enjoyed Banished.

    — Ferdinand

  8. Love these posts - getting a notification for them is like Christmas! Thank you for writing up your experience and approaches so thoughtfully.

  9. Fellow developer here (burdenofcommand.com). Nice job explaining some of the lessons learned and where to go next. Always a learning curve isn't it!
    Luke

  10. Your explanation of the writing of a game made it understandable even to me...A person who has never written code or anything similar since the days of DOS and then not very well.
    I really appreciate the time and effort you take to keep us, (your fans), and I know I speak for many others when I say, "We are waiting with bated breath for your next offering".

  11. What an amazing read!

    I know nothing about code, in fact "Software Dummy" should be tattooed on my forehead to warn people! ... but after reading your latest thoughtful missive, I was able to puzzle through a lot of it and understand the process a teeny bit. Fascinating!

    As Katie remarked .. like Christmas.

    Thank you so much.

    A rabid Banished fan!

  12. Reading these updates always remind me that I want to become better at coding and create my own game.

    But then I remember that I tend to be lazy and would always rather just play an existing game instead!!

    Thanks so much !

    p.s. Please have your brother create the music for the new game ! I still listen to the Banished soundtrack as often as I can when not playing the game !

  13. This is so interesting to read. I just extremely recently started to be more serious about game development, but unfortunately I'm still at a beginner level.

    I did read a bit of Game Coding Complete and it's always nice to see other game developers talk about things like Entity Component System or States and somewhat knowing what they are talking about and what they are going through. I hope that someday I'll be good enough to be even remotely at your level.

  14. 🙂

    Mostly addressing the first couple of paragraphs. I think that is because there is a difference between building a bridge and painting a picture.

    Not a lot of room for iterating in the process of constructing a (singular) bridge and I've never come across an artist who starts in one corner and works systematically over the canvas to the opposite side and then puts it down.

  15. Hey! I'm a huge Banished fan from Brazil and very much would like to know when can we expect more news on your new project. I spent 2k+ hours on Banished and at this point I'll support anything Shining Rock publishes =)

  16. "Is it in my inventory? Is prepared food available nearby? Is preserved food available in my home? Can I ask my neighbors for some? Do I have to get it from storage? Can I ask a hunter to prioritize getting food? Should I walk out into the woods looking for mushrooms and berries myself?"

    Could you explain how this is data, or am I misunderstanding? Sounds like code that's going to be tightly bound to a limited number of "address hunger" states.

  17. I'm so happy to see that you're working on a new project! I really enjoyed Banished, and can't wait to see what you make next!

    P.S. Link to your dev blog in Banished news! I think it'd be a great way to reach out to fans, I had no idea you were making a new game until googling you out of curiosity. ^ ^

More Posts

Code Rot

April 17, 2022
1 2 3 47
Back to devlog
Back to devlog
© Copyright 2021 Shining Rock Software
Website Design & Branding by Carrboro Creative
menu-circlecross-circle linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram