ame Programming Patterns states the 3 goals of game architecture:
- We want nice architecture so the code is easier to understand over the lifetime of the project.
- We want fast runtime performance.
- We want to get today’s features done quickly.
These goals are at least partially in opposition…
Let's take a look at some of the ways to make this possible.
What is Architecture And Why is it Hard?
Software Architecture consists of the fundamental structural choices that make up a program, and thus are very costly to change. Its designs aim to model a world/problem in a performant manner in software and in our minds. Thus the architecture is the backbone upon which the rest of implementation lies, and can ease cognitive load when designing features or fixing bugs.
In other words…software is complex. Games are especially complex. Thinking and remembering is hard. Organization makes things easier, and getting it right early can make life a lot easier.
Design patterns and paradigms help by breaking architecture into more understandable chunks. They exist because at some point, some dude encountered a problem modeling or abstracting an issue, came up with some ideas about how to fix it, and shared. Other people had similar problems and followed suite. You will not encounter every design problem that ever existed, and don't need perfect knowledge nor application of every pattern.
As a developer the goal is to ship your product. Architecture should only intervene to make that task easier. Don't get stuck on the minutiae, don't ignore it, and remember, the important thing is to keep iterating.
So stay tuned, take this info with a grain of salt, and let's examine some ways to make software & games easier to model.
Some Basic Software Principles
Keep it Simple Stupid (KISS)
KISS - simple is better.
Don't Repeat Yourself (DRY)
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". - DRY
DRY principles are violated all the time, but that doesn't make it OK. Web programming often requires modifications on HTML, CSS, and JS when updating sites - some libraries attempt to improve this by inlining HTML and CSS in JS components. Magic Numbers are numerical values littered throughout code with no explanation or variabilization. Modifying an uncommented, non-variable number in code is a dangerous process, why was that number used? What other places use this number and also must be changed?
Global variables, databases, and external data files are better at storing data as we can use a single point of control. Modularity via self-contained components is better than re-implementation. Abstractions filter out low-level details and keeps focus on goals. These methods save the pain of having to make many updates throughout a code base due to one simple change. Be sure to comment or write self-documenting code to ensure that this point of control is not ambiguous
Inevitably, if your code isn't DRY it's WET - Waste Everyone's Time.
You Aint Gonna Need It (YAGNI)
YAGNI minimizes upfront design to keep focus on implementation that will actually be used. How can you be sure that the hours/days/weeks of design/implementation are going to be utilized if they are not yet needed? Software needs can change over time, and are often difficult to know in the first place. Keep your audience, goals, and vision in mind, and iterate over one module at a time to a high level of polish. Remember, keep it simple.
And there are more. They all work to remind you of the importance of simplicity, reusability, focusing on your goals and focusing on your audience.
Typical Game Architectures
Common software architectures like MVC, MVP, or MVVM are not used commonly used in games, likely due to the tighter coupling between these concerns than a GUI app. Instead, games typically use one of the three following systems, with ECS's being most popular:
Object Oriented Programming
OOP is a paragidm where classes composed of data/fields/attributes and procedures/methods/functions interact with one another. There are 3 main pillars of OOP design - Encapsulation, Inheritance, and Polymorphism (Abstraction too depending who you ask), and a variety of design patterns.
Usually OOP is not recommended for game programming, as can lead to tightly coupled, difficult to extend systems.
Entity Component Systems
ECS aims to create game entities by following the Composition over Inheritance principle. In this paradigm, an entity is little more than a list of components. Components are data that can be added to any entity, for example velocity, position or health. Finally Systems bring it all together and apply logic to these entities that changes dependent upon which components exist.
For example, a Weapon entity may have components for it Sprites, bullet entity factory, bullets container, position, health, and more. Outside of Weapon there would exist systems for movement, drawing, spawning, etc that would iterate over entities and utilize their component values.
Nothing is perfect, ECS has drawbacks too. The more components you have the more special cases exist, making it difficult to communicate between components. Implementing the observable pattern enables removing this special handling and instead keeping a list of dependents and notifying them of state changes via events/signals. Naively implemented, ECS can also cause a performance hit when systems iterate over a global list of all objects. However, enabling separate lists for smarter iteration can actually cause performance boosts (your engine should take care of this for you).
- Understanding ECS
- Role of systems in ECS
- Game Programming Patterns - Components
- Component Based Game Engine Design
- ECS Critique
Data Driven Design
ECS can be used in conjunction with data-driven design. Using the existing ECS paradigm, data is factored out to external files (JSON, XML, DB, etc) and new behaviors are defined through data modification. This utilizes a Data Driven Game Object System that can build "new objects" with quick tweaks to those data files. For example, you can have a ship class that accepts JSON in its initialization. Its image, animations, movement patterns, guns, and abilities are all defined in that JSON. In initialization the JSON is queried and components created on the found elements. The components' behavior is defined by the existing ECS systems, and the ship is ready to go. Just like that you have a extremely extensible, and fast, ship object.
Functional Reactive Programming
FRP combines functional programming (use declarative functions that do not change state) with reactive programming (organize around data streams and change propagation). Here's some more info on FP and RP. Its steadily growing in popularity, and is at least partially endorsed by John Carmack.
For example, the Helm engine follows this approach. Many others do not. At this point in time there aren't many widespread engines, tutorials, and academic teachings that prep developers for this, so learning it fresh presents a challenge and a time sink. Keep your eyes open, and in the meantime try to integrate some of the benefits of FP in your normal routine.