Game development: Creative order

Introduction 

Probably every established programmer has been involved in developing a computer game. Whether it was a homework assignment at university or a small personal project; if the efforts resulted in an application designed to entertain the user without the use of additional media content, it was a game. 

Usually, people understand how to keep themselves from getting bored pretty well, but providing entertainment to a wide audience is a path full of surprises, disappointments, and even contradictions.

Commercial game development is a process that generates impressive volumes of technical debt. A problem that is quite game design specific is that the energetic search for the best gameplay experience leads to developing various experimental mechanics which may or may not survive until the release date; however, the tryouts might stay in the project for a long period which contributes significantly to the emergence of harmful code in addition to more common software development issues. 

Whereas the development of more traditional apps and web pages requires significant adjustments to meet user experience requirements, game project code appears to have tighter coupling, making it difficult to detect due to a false sense of its appropriateness within an object-oriented model of a game world. Fixing this situation can challenge even experienced developers and may lead them to go round in circles or become utterly confused.

Loosening coupling is a common and solvable task, but finding the resources to do so may pose a challenge within the context of commercial game development. Developers from major game studios acknowledge that technical debt issues, and their subsequent less noticeable consequences, are traditionally of extremely low priority for the management that may prefer to either require a QA team to overlook the bugs resulting from code complexity or utilise PR strategies to mitigate customer negativity and make promises to address issues in future updates. 

This article is intended to demonstrate how technical problems naturally arise in games and to provide a traditional way of getting the code coupling problem under control.

Game development: Creative order

Start of the development

It is worth noting that the balance of technical and creative in game development has a cardinal preponderance in favour of creativity. Therefore, for the sake of completeness of the picture, let's digress for a brief introduction to the creative process, and look into the area of responsibility of producers and game designers. The path of production begins with the formation of ideas about the future game, defining its identity. No details, just the concept.

1. Selecting key categories (platform, genre, budget, setting, style, etc.).

This step is important, simple, and usually irreversible. A text-based adventure game will not turn into a "Tetris" game, and a farm simulator will not grow into "Civilization". Timely definition of project categories is the key to a rational choice of developer's tools and, in general, the viability of the project at the initial scene of production.

2. Outlining the core gameplay loop

Just as a new business needs a business plan, a game needs a clear idea of what it will do to entertain the player. The choice of game genre sets the framework of the core gameplay loop, and the details, different from other games of the genre, determine how the gameplay should hook the audience of users.

3. Initial game world design 

A game designer thinks through the mechanics behind the dynamics and aesthetics of a future game. The creative description of game mechanics is the source of most tasks performed by a developer on a project. Often, a developer's involvement in game development begins when the task documentation consists only of minimalistic game space design:

  • a general description of what the player sees on the device screen;
  • properties of the player's avatar in the game world and actions available to them;
  • properties and ways of interacting with game objects.

The developer starts working at the level of high-level abstractions and service components fully realizing the lack of specificity of future tasks. Hence the simplification of the beginning of the development in most popular development tools; the aim is to make the start of a game project creative and uncomplicated even for beginners.

Game objects 

Special game development tools or game engines offer convenient high-level abstractions for assembling the game world components. 

When creating a 3D multi-object scene, manual code that accesses the graphics API (DirectX, OpenGL, etc.) is not always necessary; a visual scene editor allows for the creation of a  dummy object that contains spatial coordinates and components pointing to the 3D geometry file and the file texture to be applied when rendering the model. A dummy object called “camera” will contain information on scene rendering parameters that it will send to the game engine; alongside information about its position and rotation, the engine will be getting all the necessary input data to project a 3D scene onto a 2D screen.

Such a dummy object with coordinates and components is called an "actor" or "scene object". It is possible to add programmable components to the main scenario of use which is the rendering of graphical elements of the game.

This makes perfect sense as game objects display behaviors that are supposed to somehow be connected to the visualization. Why create classes and objects to aggregate logic and representation when you can apply composition and build a more complete and also autonomous object on a dummy object? Also, the life cycle of a logical component is not to be worried about as it will be created and destroyed together with the view.

Logic to representation connection 

Game engines take care of the implementation of the game object representation, offering a ready-made building block tool that gives a developer creative freedom in realizing a logical model of the object, including its life cycle management. As a rule, game objects can be easily divided into two categories: 

  • simple and numerous (e.g. the resources of the game world); 
  • complex and rare (e.g. special characters operated by a player). 

To be less abstract with further examples, let’s assume that this article is describing the development of a high-budget role-playing action game similar to "The Witcher 3: Wild Hunt", s popular 2015 game.

The Witcher 3 Logic to representation connection

"Simple" game objects do not increase the complexity of the code, because they have minimal logical load; they only need to be placed on the scene, and the logic of interaction will be taken over by "complex" objects.

The role of an independent component (which we will call "witcher's brain") would be as unique and important to the game world as the role of the "camera" component is unique to the logic of rendering the game scene. Add the programmable "brain" to the 3D model of the protagonist, and you get an extensible system that is easy to describe.

The close 1-1 relationship between a unique representation and its logical model is intuitive. It is hard to condemn a developer who follows the path of the simplest abstractions and the least labor-consuming at the beginning of development when creating ad hoc components for all single objects in the game. However, an approach that’s more than appropriate for a low-budget mini-game or a prototype game for a video blog about game dev may not be the best for the development of a "The Witcher"-like game as it scales extremely poorly without a well-thought-out logical organization of the project.

Game logic decomposition

The representation of any game object is subject to change throughout game development; models and textures are constantly being fixed and improved, number of variations of the same object grows (e.g. a unique melee weapon on a merchant's counter should look slightly different than when the hero holds it). This process is consistent and transparent, labor costs are easy to assess with high precision, and the whole operation is not usually problematic.

During the development of the project, the game logic undergoes changes in complexity that strikingly outnumber changes in the visual part. Initially, simple mechanics can turn into a creation of game design genius; like a linear sequence of collecting ingredients from "approach a flower >> press a button >> flower disappears from the game world >> flower appears in the inventory" can grow into a tree of options, preconditions, additional actions and alternative collection results, and all this will be unique for each type of collected ingredients. It was originally thought that gameplay could do without this, but a competitor game came out a month ago with this exact mechanic, and players were delighted!

For a game mechanic that is gaining in complexity, the "brain" component of an object in the game world is no longer a good metaphor. What if we spin it off into a separate class component, e.g. "the witch's specialization in ingredient gathering"? It's not that simple a name anymore, but it seems to solve the problem: any future changes to ingredient gathering mechanics will now take place in this module, and not complicate the code of the rest of the "brain". The main thing is to remember that the "brain" class is no longer responsible for this particular mechanic.

It is not hard to guess that the multiplication of components to address the growing complexity of game mechanics leads to the emergence of "witcher’s inventory", "witcher’s health", "witcher’s combat skills", "witcher’s role characteristics", etc. The set of systems becomes specialized, along with how the contours of the features are refined.

In development following the path of component separation, questions arise over time:

  • Should we have started with a "brain" component at all, if visual representations and game mechanics evolve independently?
  • Is the composition between logic and representation as useful as we imagine?

Let's leave aside the choice of a better code architecture at the start of the project, and follow a typical development scenario, where everything has already gone too far to get away from object-oriented budding of independent feature components.

Dependency chains and control flow

Let’s imagine that a new werewolf movie has recently been released and it’s a blast; so next sprint the development team will be tasked with adding an ability to increase health when outside the city walls during a full moon to the “witcher’s abilities” module.

Games Dependency chains and control flow

The code already has unique components of all the aspects of the game logic, so the task may be decomposed into the following bits: 

  • The "moon phase" should request  the status of learning a new skill of the "witcher's abilities";
  • The "witcher's abilities" will have to confirm that the "witcher's location" corresponds to the game zone "city neighborhood" before returning the result;
  • If the skill is active, the "moon phase" will increase the "witcher's health" by 15% of the current value.

Not to say that the description is logically incorrect, but this is the way a creative specialist would reason. If the task is implemented in the code literally without considering nuances or looking ahead, a network of non-obvious dependencies will grow, and bugs buried in the system will inevitably come up.

Technically correct re-description of each task could be handled by the most experienced developers in the team, but rarely will a workflow be built in a way that they have an opportunity every time help is needed. So, when a less experienced developer fails to make timely corrections to a task and proposes a straightforward implementation, it is not always the best solution to send them to redo everything as it will take time and possibly push the deadline. Moreover, at the scene of a quick code review, the reviewer may not even catch the problem.

So in the absence of a well-defined dependency control framework and specialized control abstractions, it is almost inevitable that the project will slip into chaos when more complex logical relationships are added.

Components on "scene objects" are created by the engine, without custom constructors, and instead of the habit of keeping a list of external dependencies in the constructor, game developers get into the habit of static access methods (the "singleton" pattern, searching for an object by name, etc.). In such circumstances, when "AnotherComponent.Instance" is called in the next new method, it is not clear whether a dependency already exists between the current object and the requested object.

Not to mention relationships between objects that control and the ones that are being controlled. Developers may come across a situation where in one game mechanic, the witcher's health is controlled by the moon phase, in another mechanic it will be controlled by the damage from attacks, and in a third situation, the calendar may control the moon phase. Keeping all that in mind, the whole network of components should be able to work synchronously allowing for cyclic dependencies and should be expandable, changeable, and as stable as possible.

Fortunately, not only game developers face challenges in the dependency chain and control flow but also professionals from other corners of the industry. This means that numerous time-tested approaches make the pattern of object connections transparent, reduce coupling, and systemize the control flow.

Gameplay mechanics as a system

The degree of supportability of a game's code correlates with the level of modification difficulty of a game mechanics set, that is to introduce a new mechanic organically, simplify or complicate an existing one, or eliminate the unnecessary.

Having framed the problem this way, it is clear that the game mechanics have to be separated into abstraction, and the components can be made healthier by removing a layer of responsibility from them. Let us call such an abstraction a system.

What this would look like in the example from the previous section:

  • Call the system class "health increase during the full moon";
  • Fix the list of dependencies: "moon phase", "witcher's abilities", "witcher's location", "witcher's health";
  • Subscribe the system for changes in the "moon phase", "witcher's abilities" and "witcher's location", because it should work concurrently: when the correct set of states of these components to acquire the effect of "amplification active", or "amplification inactive" otherwise ;
  • Changing the status of reinforcement should be reflected in the "witcher's health"; with active amplification the maximum number of health points increases, and with inactive amplification it decreases.

In addition to the fact that the logic of these game mechanics no longer clutters the code of the components involved, the following benefits have been realized:

  • non-mediated access to the control of the active state of the system itself (whether or not this game mechanics is necessary for external conditions dictated by business logic), e.g. through the control over the lifetime of the system object, rather than through manipulations in the code of components;
  • system dependencies on components can be explicitly marked at the scene of system object creation;
  • system dependencies on components can be divided into "read-only" and "read/write".

Introducing a "system" approach into an existing composition of logical components is possible at any scene of game development, and leads to taking control over the situation with chaotic coupling of components. This is the traditional approach to improving the maintainability of game code that suffers from excessive coupling. Even though the approach is quite simple, the importance of implementing it in the game development process cannot be overemphasized.

Each overloaded logical component holds parts of the game logic, which can be removed from it one by one, assembling new, transparent systems from them. And the totality of all systems in the game sets the full space of game mechanics - the components have gone to the background, and become simple controllable elements that store essentially only the state of game objects and their fully autonomous functions, usually closely related to the display.

P.S. Dependency injections 

When implementing a “system” into a game code, a development team has to consider the life cycle of the objects and the ways of connecting them to scene objects if not through a “singleton” pattern or object search. 

One of the approaches suggests the realization of the “system” as an invisible scene object which is a straightforward solution favored by newcomers that still traps the developer within the framework of the scene object implementation imposed by the game engine developers. If the developer wants to take full control over the system life cycle into their own hands, the game logic and the organization of scene objects must be clearly distinguished.

A more advanced approach is to implement an object composition plan, a context for creating and deleting objects, and dependency injection, or DI for short. These are well-known techniques in object-oriented programming that rarely have to be implemented from scratch, because many DI frameworks have already been created for common development environments and that have been tested by the community.

DI is an excellent solution for implementing loose coupling, as the communication between objects is no longer the responsibility of the objects themselves. If the game system needs to read the state from several components, not having to worry about searching for these components could be very helpful.

DI solutions for game engines allow user customization of the hierarchy and lifetime of containers, but quite often programmers link one-to-one dependency containers to the game scene. In this way game mechanics, or systems, live with the game scene objects, but not on the objects themselves. A container created with the scene can take care of addressing all the objects in the scene for later injection of component dependencies when creating systems.

When using generating mechanisms for creating scene objects implemented in game engines, a DI-container can not only embody a binding mechanism but also become a scene builder, literally a “composition root”.

The scope and ways of using DI framework in a game project are limited only by the developer's imagination, and this topic in itself deserves to be dealt with in several articles. In the context of the reorganization of game mechanics through the transition from logical scene objects to system objects, the DI framework provides a ready-made solution for managing the lifetime and dependencies of systems and saves time for creating a suitable infrastructure in the project code.