Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Game Development Patterns and Best Practices

You're reading from   Game Development Patterns and Best Practices Better games, less hassle

Arrow left icon
Product type Paperback
Published in Apr 2017
Publisher Packt
ISBN-13 9781787127838
Length 394 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
John P. Doran John P. Doran
Author Profile Icon John P. Doran
John P. Doran
Matt Casanova Matt Casanova
Author Profile Icon Matt Casanova
Matt Casanova
Arrow right icon
View More author details
Toc

Table of Contents (13) Chapters Close

Preface 1. Introduction to Design Patterns 2. One Instance to Rule Them All - Singletons FREE CHAPTER 3. Creating Flexibility with the Component Object Model 4. Artificial Intelligence Using the State Pattern 5. Decoupling Code via the Factory Method Pattern 6. Creating Objects with the Prototype Pattern 7. Improving Performance with Object Pools 8. Controlling the UI via the Command Pattern 9. Decoupling Gameplay via the Observer Pattern 10. Sharing Objects with the Flyweight Pattern 11. Understanding Graphics and Animation 12. Best Practices

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.

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/.
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime