Book Image

Game Development Patterns and Best Practices

By : John P. Doran, Matt Casanova
Book Image

Game Development Patterns and Best Practices

By: John P. Doran, Matt Casanova

Overview of this book

You’ve learned how to program, and you’ve probably created some simple games at some point, but now you want to build larger projects and find out how to resolve your problems. So instead of a coder, you might now want to think like a game developer or software engineer. To organize your code well, you need certain tools to do so, and that’s what this book is all about. You will learn techniques to code quickly and correctly, while ensuring your code is modular and easily understandable. To begin, we will start with the core game programming patterns, but not the usual way. We will take the use case strategy with this book. We will take an AAA standard game and show you the hurdles at multiple stages of development. Similarly, various use cases are used to showcase other patterns such as the adapter pattern, prototype pattern, flyweight pattern, and observer pattern. Lastly, we’ll go over some tips and tricks on how to refactor your code to remove common code smells and make it easier for others to work with you. By the end of the book you will be proficient in using the most popular and frequently used patterns with the best practices.
Table of Contents (19 chapters)
Title Page
Credits
About the Authors
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface
4
Artificial Intelligence Using the State Pattern

Why you should plan for change


In my many years doing game development, one thing that has always been constant is that a project never ends up 100% the same as it was imagined in the pre-production phase. Features are added and removed at a moment's notice and things that you think are pivotal to the game experience will get replaced with something completely different. Many people can be involved in game development, such as producers, game directors, designers, quality assurance, or even marketing, so we can never tell who, where, or when those changes will be made to the project.

Since we never can tell what will be changed, it's a good practice to always write your code so it can be easily modified. This will involve planning ahead much more than you might be used to, and typically involves either a drawn flowchart, some form of pseudocode, or possibly both. However, this planning will get you much further much faster than jumping straight to coding.

Sometimes you may be starting a project from scratch; other times you may be joining a game team and using an existing framework. Either way, it is important to start coding with a plan. Writing code is called software engineering for a reason. The structure of code is often likened to building or architecting. However, let's think in smaller terms for now. Let's say you want to build some furniture from IKEA.

When you buy your furniture, you receive it unassembled with an instruction manual. If you were to start building it without following the instructions, it is possible you wouldn't finish it at all. Even if you did eventually finish, you may have assembled things out of order, causing much more work. It's much better to have a blueprint that shows you every step along the way.

Unfortunately, building a game is not exactly like following an instruction manual for furniture. In the case of games and software of any kind, the requirements from the client might constantly change. Our client might be the producer that has an updated timeline for us to follow. It might be our designer that just thought of a new feature we must have. It might even be our play testers. If they don't think the game is fun, we shouldn't just keep moving along making a bad game. We need to stop, think about our design, and try something new.

Having a plan and knowing a project will change seem to be in opposition to each other. How can we have a plan if we don't know what our end product will be like? The answer is to plan for that change. That means writing code in such a way that making changes to the design is fast and easy. We want to write code so that changing the starting location on the second level doesn't force us to edit code and rebuild all the configurations of the project. Instead, it should be as simple as changing a text file, or better yet, letting a designer control everything from a tool.

Writing code like this takes work and planning. We need to think about the design of the code and make sure it can handle change. Oftentimes this planning will involve other programmers. If you are working on a team, it helps if everyone can understand the goal of each class and how it connects with every other class. It is important to have some standards in place so others can start on or continue with the project without you there.

Understanding UML class diagrams

Software developers have their own form of blueprints as well, but they look different from what you may be used to. In order to create them, developers use a format called Unified Markup Language, or UML for short. This simple diagramming style was primarily created by Jim Rumbaugh, Grady Booch, and Ivar Jacobson and has become a standard in software development due to the fact that it works with any programming language. We will be using them when we need to display details or concepts to you via diagrams.

Design patterns are usually best explained through the use of class diagrams, as you're able to give a demonstration of the idea while remaining abstracted. Let's consider the following class:

class Enemy  
{ 
  public: 
    void GetHealth(void) const; 
    void SetHealth(int); 
  private: 
    int currentHealth; 
    int maxHealth; 
};

Converted to UML, it would look something like this:

Basic UML diagrams consist of three boxes that represent classes and the data that they contain. The top box is the name of the class. Going down, you'll see the properties or variables the class will have (also referred to as the data members) and then in the bottom box you'll see the functions that it will have. A plus symbol (+) to the left of the property means that it is going to be public, while a minus symbol (-) means it'll be private. For functions, you'll see that whatever is to the right of the colon symbol (:) is the return type of the function. It can also include parentheses, which will show the input parameters for the functions. Some functions don't need them, so we don't need to place them. Also, note in this case I did add void as the return type for both functions, but that is optional.

Relationships between classes

Of course, that class was fairly simple. In most programs, we also have multiple classes and they can relate to each other in different ways. Here's a more in-depth example, showing the relationships between classes.

Inheritance

First of all, we have inheritance, which shows the IS-A relationship between classes.

class FlyingEnemy: public Enemy 
{ 
  public: 
    void Fly(void); 
  private: 
    int flySpeed; 
};

When an object inherits from another object, it has all of the methods and fields that are contained in the parent class, while also adding their own content and features. In this instance, we have a special FlyingEnemy, which has the ability to fly in addition to all of the functionality of the Enemy class.

In UML, this is normally shown by a solid line with a hollow arrow and looks like the following:

Aggregation

The next idea is aggregation, which is designated by the HAS-A relationship. This is when a single class contains a collection of instances of other classes that are obtained from somewhere else in your program. These are considered to have a weak HAS-A relationship as they can exist outside of the confines of the class.

In this case, I created a new class called CombatEncounter which can have an unlimited number of enemies that can be added to it. However when using aggregation, those enemies will exist before the CombatEncounter starts; and when it finishes, they will also still exist. Through code it would look something like this:

class CombatEncounter 
{ 
  public: 
    void AddEnemy(Enemy* pEnemy); 
  private: 
    std::list<Enemy*> enemies; 

};

Inside of UML, it would look like this:

Composition

When using composition, this is a strong HAS-A relationship, and this is when a class contains one or more instances of another class. Unlike aggregation, these instances are not created on their own but, instead, are created in the constructor of the class and then destroyed by its destructor. Put into layman's terms, they can't exist separately from the whole.

In this case, we have created some new properties for the Enemy class, adding in combat skills that it can use, as in the Pokémon series. In this case, for every one enemy, there are four skills that the enemy will be able to have:

class AttackSkill 
{ 
  public: 
    void UseAttack(void);  
  private: 
    int damage; 
    float cooldown; 
}; 

class Enemy  
{ 
  public: 
    void GetHealth(void) const; 
    void SetHealth(int); 
  private: 
    int         currentHealth; 
    int         maxHealth; 
    AttackSkill skill1; 
    AttackSkill skill2; 
    AttackSkill skill3; 
    AttackSkill skill4; 
};

The line in the diagram looks similar to aggregation, aside from the fact that the diamond is filled in:

Implements

Finally, we have implements, which we will talk about in the Introduction to interfaces section.

The advantage to this form of communication is that the ideas presented will work the same way no matter what programming language you're using and without a specific implementation. That's not to say that a specific implementation isn't valuable, which is why we will also include the implementation for problems in code as well.

Note

There's a lot more information out there about UML and there are various different formats that different people like to use. A nice guide that I found that may be of interest can be found at https://cppcodetips.wordpress.com/2013/12/23/uml-class-diagram-explained-with-c-samples/.