Many inexperienced software and game developers take a linear thinking approach to their first projects. As their project grows, they quickly discover the code becomes complicated and hard to change or maintain. For example, in the popular 2D platformer game “Celeste,” the player code includes all logic that applies to the player. At first, this makes sense. But when you look deeper, this approach has many issues down the road. A system-driven design is a much better methodology for developing a game.
In this article, I will explain what a system-driven approach is and how it applies to game development. Next, we will analyze the systems in some popular video games. Then, I will list the benefits and challenges of using this approach. Finally, I will guide you on how you can implement this approach in your development process. This will be a long article, so get a coffee, sit comfortably, and let’s dive in.
System-driven Design in Games
A system-driven design is a code development methodology that encourages the usage of independent code modules that handle specific behaviors. This methodology replaces the old paradigm, where the code is interconnected and has many internal dependencies. It is a superior way of thinking and is an acquired skill. Before we dive deep into the details, let’s define what a system is.
What Is a System?
A system is a group of components that work together to achieve a common goal or purpose. Usually, a system will have an input and an output. It can be made up of physical objects, processes, people, or software. The components of a system are connected and interact with each other in a complex and dynamic way. Any change in one component can impact the entire system.
A system can be simple or complex and have a wide range of properties and behaviors. For example, a simple system might be a light switch that turns a light on or off. The wires, power source, and light bulb interact with each other to produce the desired output – light. On the other hand, a more complex system might be an ecosystem, where different species of plants and animals interact in a complex and dynamic way. The relative amounts of species and their interactions keep the system balanced and functioning properly.
Systems can be found in many fields, including engineering, science, business, and games. Understanding how systems work and their components interactions is essential for designing and improving them. This is where systems thinking comes in. It provides a framework for analyzing and designing systems in an effective way.
A Deeper Understanding of System-driven Design in Games
A game system is a large and independent module that handles a specific behavior in your game. System will interact with each other using a well-defined interface. Usually, a well-organized video game will consist of multiple large systems. Each large system will contain smaller systems that can interact with each other. This structure has many benefits in game design, development, and user experience.
There are many different types of game systems that you can incorporate into your games. Here are some examples of game systems commonly used in video games:
- Combat: describes how players fight enemies in a game. It may include mechanics for attacking, blocking and dodging. It may also include damage calculations, special abilities and many other aspects of combat.
- Crafting: allows players to create new items by combining different materials or resources. It often includes a recipe system and can be a core gameplay mechanic, similar to Minecraft.
- Leveling: rewards players for completing tasks and challenges in a game. It often involves gaining experience points (XP) and leveling up, which allows players to unlock new abilities or items.
- Economy: simulates an in-game economy where players can earn and spend in-game currency. It can include buying and selling items, inflation, and supply and demand.
- Dialogue: governs how players interact with non-player characters (NPCs) in a game. It includes things like conversation trees and player choices that affect the game’s narrative.
- Movement: governs how players move around the game world. It includes things like player movement speed, jumping mechanics, and others.
- AI: This system governs the behavior of NPCs in a game. It includes things like pathfinding, decision-making, and combat behavior.
- Puzzle: includes puzzles or challenges players must solve to progress in a game. It often involves logic puzzles, environmental puzzles, or platforming challenges.
- Audio: controls music and sound playback, mix, effects, storage and organization of audio assets.
Comparing Linear and Systems Thinking Approach
Let’s take a simple system and compare the difference in thinking between linear and systems approach. The Movement system is a simple example we can use. Instead of writing code to move players in different ways, you should write a system that lets players have various movement capabilities. This is a subtle difference, but an important one. Linear thinking in the Movement system will result in several problems.
- Would you implement similar behaviors to multiple player classes?
- How do you experiment when each player or character governs its own movement? Do you manually change parameters for each type of character?
- If the code for character movement is encapsulated within the character, how do you test it?
- How much time do you spend copy/pasting logic between players and other characters?
- How do you explain your code to a teammate when files contain multiple behaviors spread over thousands of lines of code?
This is where systems approach shines. With this approach, you create an entity that governs the abstract concept of “Movement.” It may contain generic capabilities like running, jumping, and hanging. Additionally, it must contain all the logic needed to move a character in the game. Once you have this system in place, each player or character will inherit some properties provided by this system and expand them whenever needed.
This approach solves many issues we had with linear thinking. Now, you can easily add more players without copying entire blocks of code for movement handling. You can change a base parameter to see how the game feels. Bugs are easier to find since the logic is pretty much the same for all characters. Finally, your code is much more balanced in terms of files and lines of code, and much simpler to explain to other teammates.
How Game Systems Interact With Each Other
Game systems interact with each other in complex ways to create the overall gameplay experience of a video game. When designing a game, you must consider how different game systems will work together and how they will impact each other.
Combat and leveling system: Combat and leveling systems are closely intertwined. You might earn experience points for defeating enemies in combat, and those experience points can be used to level up and unlock new combat abilities. As you gain more powerful combat abilities, you become better equipped to tackle tougher enemies and progress further in the game.
Economy and crafting system: You might use in-game currency to purchase materials or resources for crafting. As you create new items, you might be able to sell them back to NPC vendors for a profit, or you might use those items to progress further in the game.
Movement and puzzle system: In puzzle-platformer games, the movement and puzzle systems are often closely intertwined. You must use your movement abilities, such as jumping or climbing, to navigate a level and solve puzzles. As the level progresses, you might unlock new abilities, allowing you to reach previously inaccessible areas.
AI and combat system: In games that include an AI system for enemy NPCs, the combat system must be designed to interact with the AI system. For example, enemy AI might be programmed to dodge attacks or use specific attack patterns. The combat system must be designed to take these behaviors into account and provide you with a way to counteract them.
Imagine what would happen if you built your code in a linear fashion. This would probably result in a single file that includes a lot of systems. This file would be huge and unmanageable. This is why a system-driven design is so beneficial.
Case Studies of System-driven Design in Game Development
Let’s look at some popular game examples and figure out what systems are they using. I chose to focus on these games just because they have unique and clear systems we can study.
Case Study 1: Civilization
The Civilization series is an excellent example of how systems thinking can be applied to game development. It is a turn-based strategy game where you must build and lead a civilization from ancient times to the modern era. The goal of each game session is different; you must either eliminate all your enemies, develop technologies, build specific structures (or several structures), and more.
We can infer the systems used in the game just by understanding the genre. In this game, you must manage resources, research new technologies, build cities, make diplomacy, and fight your enemies to conquer or destroy their cities. The large systems in this game are resources, trading, technology, diplomacy, combat, movement, turn system, city management, population, terrain, AI, economy, and many more. Much of these systems interact with each other, creating a complex web of gameplay mechanics that you must navigate in order to win. This creates a dynamic and ever-changing gameplay experience that requires you to constantly adapt and adjust your strategy.
Case Study 2: Minecraft
Minecraft is an open-world sandbox game widely regarded as one of the most popular and successful video games of all time. The game allows players to collect resources such as wood, stone, and ore, which can be used to craft tools, weapons, and building materials. Players can then use these materials to build structures of their own design, ranging from simple houses to elaborate castles and cities.
The block-based building system is designed as a set of interlocking game systems that interact with each other in complex ways. For example, players can gather resources generated by the environment system, which can then be used to craft tools and weapons thanks to a sophisticated crafting system implemented by the game developers.
The game also includes a day and night cycle, which affects gameplay in several ways. During the day, players can safely gather resources and build structures without fear of attack from monsters. At night, however, monsters such as zombies and skeletons spawn, making it harder to explore and gather resources. This effect is achieved by time and calendar systems which control the day and night cycles.
Another key feature of Minecraft is its procedural generation system. The game world is generated procedurally, meaning that each game world is unique and randomly generated. This creates an infinite gameplay experience as players can explore and discover new areas of the world indefinitely.
Minecraft also includes a robust multiplayer system that allows players to collaborate on building projects and explore the game world together. This system is supported by dedicated servers and player-created mods, which have added new gameplay mechanics and features to the game over time.
Case Study 3: Spelunky
Spelunky is an indie roguelike platformer game where you must traverse different terrains such as caves, jungles, icy caverns, and temple levels. The goal is to finish the game without dying while collecting gold on your way. The game’s levels are procedurally generated, and the player must navigate a complex system of traps, enemies, and environmental hazards to progress.
Spelunky’s procedural level generation system creates new levels automatically. Each time you start a new game, you will encounter a different layout of obstacles, enemies, and traps. The movement system governs how you move and interact with the environment, while the enemy AI system controls the behavior of the game’s many creatures.
You can also collect items and weapons to help you survive while figuring out ways to complete the level. The inventory system manages all collected items, and the weapons system enables you to use different weapons. These systems interact with each other in complex ways, creating emergent gameplay scenarios that require careful planning and quick reflexes to overcome.
Benefits of System-driven Design in Game Development
1. Efficient Code Development Process
Software development has an annoying property that applies to both games and software in general. The first block of code is the easiest to write, and with each additional block, it becomes exponentially more complex. This happens because of code dependency. Each time you add code to your game, you must know how it integrates into your codebase. The larger the codebase, the more complex it becomes.
Inexperienced software developers will encounter this problem when working on large-scale software. Unfortunately, games are included in this category. So when a developer ignores design patterns and a system-driven approach, they will eventually create Spaghetti Code.
Spaghetti Code is a nickname for code that is messy, interconnected, and unreasonably hard to change. It is not scalable, maintainable, or understandable for other people. As a software engineer, I am frustrated every time I have to work on Spaghetti code someone else wrote. Systems thinking helps avoid this problem in the first place. Each system should be independent with a well-defined interface. This way, each time you add code, the only affected code is the one you reference. All other systems are completely independent and should not be changed.
By using this approach, you make code development much more efficient. You avoid Spaghetti code, which lets you add code very quickly. It doesn’t create tiny bugs in your code, which are impossible to find. It also organizes your code in a logical manner, so other people can understand how the systems interact with each other and how the whole thing works.
2. Better Game Scalability
In addition to the previous benefit, a system-driven approach makes your game more scalable. Imagine you have a complete game with interconnected code. You release the game to your friends and family for testing and discover the game is good, but lacks diversity. So, you decide to add more assets to make it more interesting. Unfortunately, the new assets are more complex and your existing code is unable to parse them correctly. You have to find a way to generalize your asset pipeline to include the new type of assets. Since your code is not organized properly, you now have to break large parts of the code and rebuild it with the new asset handling code.
I have experienced this many times. Sometimes you have to redesign the entire code and rebuild it from scratch, just to accommodate the new feature. This is not fun, seeing all your hard work go down the toilet. Organizing your code into systems will minimize this problem by reducing the dependencies within your code. This, in turn, will increase your ability to scale your code and easily add new features and assets.
3. Bug Avoidance
Avoiding bugs is super important when developing large software. Solving a bug in a small game may be easy, but finding a bug in a large-scale, full-featured game might be very hard or even impossible with your tools. Bugs can take anywhere from minutes to days to solve. Many game developers abandon their games because they are stuck on a critical bug and don’t know how to solve it. Don’t be that person.
The nice thing about systems thinking is that it generalizes your code. And you know what’s so great about generalized code? It either works for all cases, or it doesn’t work at all. With Spaghetti code, you have specific code for each use case, whereas with generalized code, you have a central system that handles all use cases. If you have several code sections that use a system, and it works, adding another code section must work as well.
So don’t write specific behavior handling, group everything into systems. This way, you leverage your bug solving. Once you solve a single bug, you eliminate it from all code sections that use this system.
4. Transferrable Knowledge
What happens if you have a new teammate and need to get them up to speed on all your Spaghetti code? Sorry, that’s impossible. Since there is no structure to the code, the new person will never understand the meaning of each code block. Use systems to encapsulate the major ideas of your game. This big picture is much easier to explain, even without diving into the code.
The learning curve is much faster when seeing the big picture, and then focusing on each system separately. For example, explaining that the game is mainly about movement and combat is easy. The relationship between those two systems should be clear. If your code is organized into those systems, and they have a well-defined interface, there should be no problem understanding the code. If not, your developer can take days or weeks to understand all relationships, calls, patches, and existing bugs in your code.
5. Consistent Game Experience
A game with defined systems will tend to be more coherent to the player. Since systems are generalized, you will have a video game with consistent behaviors. Coming back to our movement system example, you can be sure that the movement of all players and NPCs behaves the same way. Or, you might want your player to consistently move at different speeds across various surfaces.
A player can experience inconsistent behavior if your game code is not organized. A systems approach eliminates this problem and gives the player a consistent and smooth game experience. This will probably increase their engagement and willingness to keep playing your game.
Challenges of System-driven Design in Game Development
While there are many benefits to using a systems thinking approach in game development, there are also several challenges you must overcome.
1. Hard To Maintain a Balance Between General and Specific Behavior
As I mentioned before, systems tend to generalize and group similar behaviors. And although generic and clean code is a good practice, some objects in your game will require specific behavior. This behavior must be somehow overlaid on top of the general system governing that behavior. The difficulty is to develop systems that are general enough to be used by characters and objects in your video game, but not too generic to the point where you can’t differentiate the behavior for various object types.
This is a balance that requires some experience in software development. Unfortunately, there is no magic formula to solve this issue. Each game is different and will force you to think about how to design the systems specifically for your game.
2. Requires Software Knowledge and Expertise
Designing generic systems and APIs on a small-scale software is not that difficult. Once you have a large enough codebase, designing the system’s structure and interfaces will be a massive challenge. As I mentioned before, this is a skill that requires practice. It will also require some trial and error in adjusting your game. If you are a beginner, it will be unreasonable to expect to have perfect software on the first try.
Keep refining your systems. Eventually, you will find solutions to all the game requirements. It might take several code rewriting attempts (sometimes from scratch), so don’t be discouraged. Every time you rewrite the game, you develop a more stable framework. This framework will allow your game to be more flexible and scalable.
3. Requires Time and Resources To Develop
Systems in software generally take more time to develop compared to sticking a piece of code in your character class. The reason is that the systems must be generalized to handle all edge cases of specific behaviors. Systems are usually abstract, and much harder to account for all their edge cases. So, designing your systems and managing the time to implement them is very important.
4. Difficult To Test All Corner Cases
Testing is an important aspect of software development. Indie game developers tend to forget they must test their code to make sure it works as expected. Since systems are abstract, it is harder to test them. In order to test a system, you need to use it in specific use cases. But how do you choose a good use case that will validate all features and functions of the system? What about all the edge cases that can occur? How do you test multiple systems working together?
As your game evolves, you will use the systems more and more, which will validate the strength of your code. You might need to expand systems or sometimes completely redesign them from scratch. This is the nature of development; there is no way around it.
Test-driven design gained popularity in the last decade, and for a good reason. It allows you to automate your code. Every change you make in your code must be tested to ensure you don’t break anything. As the saying goes, “Test early, test often.” Testing your systems will make the game development process much smoother.
Guidelines for Designing Systems in a Video Game
Assuming you know the basic mechanics of your game, here are some guidelines for designing well-structured code:
- Map all systems: Make a list of all the systems your game should have and write a short description for each.
- Find relationships: Think of all the interactions needed between the systems you listed and write them down.
- Define interfaces: Define small and clear functions for internal and external calls of each system.
- Visualize: Copy all your systems to a visualization tool like PowerPoint or Milanote to understand the big picture.
- Refine: Go through the first steps again and refine your systems until you are satisfied with the result.
Once these systems are implemented in code, you can test and refine them. As the game evolves, these systems will change and improve.
- Test common cases: Test all common use cases for your implemented systems.
- Test corner cases: When you feel you have tested all common use cases of these systems, try and think of corner cases. The difficult bugs usually come from unmanaged corner cases, so don’t skip on these tests.
- Add/remove systems: As you go along, feel free to remove or add new systems if you think the game requires them. The list is not set in stone; it will change dramatically throughout the development process.
System-driven design is an important and valuable approach to game development. By designing your games using this approach, you can make your life much easier, and your game much better. But to do that, you must invest the time and resources necessary to design your game properly. If you are interested in game development and other related topics, visit my Night Quest Games Blog for more great articles like this one. By the way, this article was inspired by the “How Thinking in Systems Can Improve Your Code” video on YouTube. Go check it out.
The background of the featured image was created thanks to kjpargeter and upklyak on Freepik. Celeste screenshots were provided by TrueAchievements website. Civilization IV game screenshots were taken from GOG Civilization IV web page. Screenshots of Minecraft are thanks to Minecraft Wiki and IGN Minecraft Wiki Guide web pages. The screenshots of Spelunky were provided by Spelunky Wiki web page. Thank you for all these wonderful resources.