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

An Introduction to interfaces


One of the main features of using design patterns is the idea of always programming to an interface and not to an implementation. In other words, the top of any class hierarchy should have an abstract class or an interface.

Polymorphism refresher

In Hollywood, lots of actors and actresses take on many different roles when filming movies. They can be the hero of a story, a villain, or anything else as they inhabit a role. No matter what role they've gotten, when they are being filmed they are acting even if what they do specifically can be quite different. This kind of behavior acts similarly to the idea of polymorphism.

Polymorphism is one of the three pillars of an object-oriented language (along with encapsulation and inheritance). It comes from the words poly meaning many and morph meaning change.

Polymorphism is a way to call different specific class functions in an inheritance hierarchy, even though our code only uses a single type. That single type, the base class reference, will be changed many ways depending on the derived type. Continuing with the Hollywood example, we can tell an actor to act out a role and, based on what they've been cast in, they will do something different.

By using the virtual keyword on a base class function and overriding that function in a derived class, we can gain the ability to call that derived class function from a base class reference. While it may seem a bit complex at first, this will seem clearer with an example. For instance, if we have the following class:

class Animal 
{ 
  public: 
    virtual void Speak(void) const //virtual in the base class 
    { 
      //Using the Mach 5 console print 
      M5DEBUG_PRINT("...\n"); 
    } 
};

I could create a derived class with its own method, without modifying the base class in any way. In addition, we have the ability to replace or override a method within a derived class without affecting the base class. Let's say I wanted to change this function:

class Cat: public Animal 
  { 
  public: 
    void Speak(void) const //overridden in the derived class 
  { 
  M5DEBUG_PRINT("Meow\n"); 
  } 

void Purr(void) const //unrelated function 
  { 
    M5DEBUG_PRINT("*purr*\n"); 
  } 
}; 
class Dog: public Animal 
  { 
    public: 
    void Speak(void) const //overridden in the derived class 
    { 
      M5DEBUG_PRINT("Woof\n"); 
    } 
};

Since a derived class can be used anywhere a base class is needed, we can refer to derived classes using a base class pointer or an array of pointers and call the correct function at runtime. Let's have a look at the following code:

void SomeFunction(void) 
{ 
  const int SIZE = 2; 
  Cat cat; 
  Dog dog; 
  Animal* animals[SIZE] = {&cat, &dog}; 

  for(int i = 0; i < SIZE; ++i) 
  { 
    animals[i]->Speak(); 
  } 
}

The following is the output of the preceding code:

Meow 
Woof

As you can see, even though we have an array of base class pointers, the correct derived class function is called. If the functions weren't marked as virtual, or if the derived classes didn't override the correct functions, polymorphism wouldn't work.

Understanding interfaces

An interface implements no functions, but simply declares the methods that the class will support. Then, all of the derived classes will do the implementation. In this way, the developer will have more freedom to implement the functions to fit each instance, while having things work correctly due to the nature of using an object-oriented language.

Interfaces may contain only static final variables, and they may contain only abstract methods, which means that they cannot be implemented within the class. However, we can have interfaces that inherit from other interfaces. When creating theses classes, we can implement whatever number of interfaces we want to. This allows us to make classes become even more polymorphic but, by doing so, we are agreeing that we will implement each of the functions defined in the interface. Because a class that implements an interface extends from that base class, we would say that it has an IS-A relationship with that type.

Now, interfaces have one disadvantage, and that's the fact that they tend to require a lot of coding to implement each of the different versions as needed, but we will talk about ways to adjust and/or fix this issue over the course of this book.

In C++, there isn't an official concept of interfaces, but you can simulate the behavior of interfaces by creating an abstract class.

Here's a simple example of an interface, and an implementation of it:

class Enemy 
{ 
  public: 
    virtual ~Enemy(void) {/*Empty virtual destructor*/} 
    virtual void DisplayInfo(void) = 0; 
    virtual void Attack(void)      = 0; 
    virtual void Move(void)        = 0; 
}; 

class FakeEnemy: public Enemy 
{ 
  public: 
  virtual void DisplayInfo(void) 
  { 
    M5DEBUG_PRINT("I am a FAKE enemy"); 
  } 

  virtual void Attack(void) 
    { 
      M5DEBUG_PRINT("I cannot attack"); 
    } 

  virtual void Move(void) 
  { 
    M5DEBUG_PRINT("I cannot move"); 
  } 
};

And here's how it looks in UML: