BlackOmen C++11 game engine part 1: Platform definition

31 03 2013

Hello all and welcome to the first part in a series of articles about my personal game engine I’m currently developing in my spare time. I called it BlackOmen like the last dungeon in Chrono Trigger. It is coded in C++11, the latest standard of C++.

My goal with this series is to teach you what I learned along the way developing my engine. Even if I work professionally in the game industry, I still have much to learn about engine programming so feel free to give me constructive comments.

First of all, I want to talk to you about a fundamental header I’ve seen in many libraries, the platform definition header. This header contains type definition and macro for identify which platform your code is currently being compiled.  It is very useful to make your code more portable.

What’s the big fuzz about types ?

Types are a fundamental part on any programming language. They help you tell the compiler how you want your variable to be stored and interpreted in memory. In a game, you are dealing with lots of data and knowing how your data is stored in memory can be critical in certain situations. Types are defined by the kind of data they represent and their size. They also have different size depending on which compiler and/or platform you are using.

As an example, the signed types in the Microsoft C++ compiler are defined like this:

Type name Size in memory (bytes) Range
int 4 –2,147,483,648 to 2,147,483,647
bool 1 false or true
char 1 –128 to 127 by default
0 to 255 when compiled with /J
short 2 –32,768 to 32,767
long 4 –2,147,483,648 to 2,147,483,647
float 4 3.4E +/- 38 (7 digits)
double 8 1.7E +/- 308 (15 digits)

I want to point you to the char type. It says that depending on a compiler switch, it can be signed or unsigned. If you are using char to store a character, it’s fine. But if you use char naively to store a number of 1 byte size, you can expect different results depending of which platform you are and can cause weird bugs. If you want consistent behavior across the platforms you are supporting, you need to create typedef for each kind and size of type you want to use.

My platform definition header

In my engine, I created a file called platform_types.h. It contains all the required typedef and macro for platform support.

It starts with this:

Platform support

#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
	#include "platform_windows.h"
#else
	#error "Please define a platform"
#endif

To ease maintenance, I decided to split platform support in a separate header so I can handle the various compilers for that platform. To identify the platform, I use macros that I’m 100% sure that are defined for the given platform. On Windows, if _WIN32 or _WIN64 is defined, you are pretty sure you are compiling on Windows. Notice also the
__CYGWIN__ test to make sure I support Cygwin also. I still only support Windows in my engine but I included an #error preprocessor notification to remind me to create the platform support header only I port my engine to another platform.

Windows platform header

Here’s how platform_windows.h looks like

#define BLACKOMEN_PLATFORM_WINDOWS

#if defined(_MSC_VER)
	#if defined(_WIN32)
		#define BLACKOMEN_COMPILER_VISUALCPP_X86
	#elif defined(_WIN64)
		#define BLACKOMEN_COMPILER_VISUALCPP_X64
	#endif
	#define BLACKOMEN_COMPILER_VISUALCPP

	#pragma warning(disable: 4127) // Disable warning C4127: conditional expression is constant
#elif defined(__CYGWIN__)
	#define BLACKOMEN_COMPILER_CYGWIN
#else
	#error "Define the compiler typedef"
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP_X86)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#elif defined(BLACKOMEN_COMPILER_VISUALCPP_X64)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#else
	#error "Please define the types for your compiler
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP)
	#define ForceBreakpoint() __debugbreak()
#else
	#error "Please define the ForceBreakpoint for your compiler"
#endif

First of all I created my own define BLACKOMEN_PLATFORM_WINDOWS to allow me to put Windows specific code inside a define if I want to.

After that, I try to identify which compiler I use to create my typedef of the basic types. I also use this occasion to disable some annoying false positive warnings per compiler. When you are compiling with warning-as-errors, those false positives become quite annoying. As before, I add #error to remind me myself to add support for additional compilers if required.

Then come the meat of the header, The typedef. For each basic type, I create a new name with the size of the type specified for it, expect for byte. I decided also to name double float64 to emphasis the fact it’s a float but with greater size.

And at last, I add a nifty macro which can be handy while debugging called ForceBreakpoint(). It forces the debugger to break at that exact line. Useful when you want to debug a specific object and the conditional debugging is too slow. I might move this macro outside of platform specific headers in the future thought.

Target macros

Next in my header is the target macro definition.

#if defined(DEBUG) || defined(_DEBUG) || defined(_BLACKOMEN_DEBUG)
#define BLACKOMEN_DEBUG
#elif defined(_BLACKOMEN_FINAL)
#define BLACKOMEN_FINAL
#elif defined(NDEBUG) || defined(_BLACKOMEN_RELEASE)
#define BLACKOMEN_RELEASE
#endif

It is used to create specific code depending of which target I’m compiling. For example, my debug info code is not compiled for the Final target. The target I’m using are Debug, Release and Final. Debug is the version with no optimization. Release is the version with full optimization but with debug information on. Final is the final release of the game with full optimization and no debug information.

I use the C++ preprocessor definitions in the project settings to set _BLACKOMEN_DEBUG, _BLACKOMEN_FINAL and _BLACKOMEN_RELEASE depending of the target.

DLL visibility macros

Next up is the DLL visibility macros. In a shared dynamic library, you can set the visibility of any function or class (symbols) to be public or private.   This is done using various compiler specific attribute you can’t afford to use directly if you want portable code.

#if defined BLACKOMEN_PLATFORM_WINDOWS
#define BLACKOMEN_DLL_EXPORT __declspec(dllexport)
#define BLACKOMEN_DLL_IMPORT __declspec(dllimport)
#define BLACKOMEN_DLL_LOCAL
#elif __GNUC__ >= 4 || defined __clang__
#define BLACKOMEN_DLL_EXPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_IMPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_LOCAL __attribute__ ((visiblity("hidden")))
#else
	#define BLACKOMEN_DLL_EXPORT
	#define BLACKOMEN_DLL_IMPORT
	#define BLACKOMEN_DLL_LOCAL
#endif

#ifdef BLACKOMEN_DLL
    #ifdef BLACKOMEN_BUILDING_DLL
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_EXPORT
    #else
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_IMPORT
    #endif
    #define BLACKOMEN_LOCAL BLACKOMEN_DLL_LOCAL
#else
    #define BLACKOMEN_PUBLIC
    #define BLACKOMEN_LOCAL
#endif

On a Windows system, you need two attribute to make DLL visibility works. The first one is BLACKOMEN_DLL_EXPORT when you are buidling the DLL itself which tells the linker to make the symbols (functions or class) public. And when you are using code from the DLL, you need to specific BLACKOMEN_DLL_IMPORT for the linker to correctly import the symbols. This is why I use BLACKOMEN_BUILDING_DLL to switch between the two. On Windows BLACKOMEN_DLL_LOCAL does nothing.

On an UNIX/Linux system, either you set the visibility to default or to hidden.  BLACKOMEN_DLL_EXPORT and BLACKOMEN_DLL_IMPORT are the same here. BLACOMEN_DLL_LOCAL tells the linker to hide the symbol.

In any case, if I want my symbols to be public, I use the macro BLACKOMEN_PUBLIC.

The last one is the BLACKOMEN_DLL define, it allows me to support static or dynamic library with the same code.

I don’t compile my engine in a DLL yet but I just put the macros there just in case I need them one day.

Engine specific types defitions

The last section on my platform definiton file is where I defined useful typedef to use for my game engine.

typedef const char* cstring;
typedef const float32 timestep_t;
typedef int64 system_tick_t;
typedef float64 gametime_t;

cstring is just an alias for a C-style string. timestep_t is the delta time between frames, I wanted this to be explicit in the code so that’s why I created a typedef. Same for system_tick_t and gametime_t. system_tick_t is the system time from a high-precision timer and gametime_t is the elapsed time since the start of the game.

How I include this header

Instead of including this header is every file that I use, I found a compiler switch that does it for me.

In Visual Studio, it is here:
VS_ForcedIncludes

In GCC and clang, it’s the flag –include that does this trick.

I agree it’s not the most portable solution but it’s works for me for now.

Conclusion

Having a platform defintion header is a key part in making your software portable across different compilers and systems. It is also a handy place to define DLL visibility macros and common types you use in your library/engine.

Advertisements




Mayanezz: it’s a start !

26 09 2009

Our game project for our final year of university has finally begun ! Even if I’m not part of the Game Programming degree, I was still able to join the team as part of a project course.

What is Mayanezz ?

Mayaznezz is 2d game top-view action adventure game (think Zelda) set in a fictional Maya universe. We play the role of a young Mayan warrior pursuing the power of 6 Mayans Gods. The story outline is still to be determined. Although we know we’ll mix some elements from the Mysterious Cities of Gold in it.

Each main dungeon will be available from the beginning of the game. Dungeons will not require special items to be completed, although the items will make the dungeons much easier.

The items will be scattered across the land, either given by key people or found in secret places.

Note that Mayanezz is code name, it is not the final name of the game

The Team

Meet our team ! Each member is involved in every part of the process, except for the graphic side.

  • Fred Imbeault: Programmer, ScrumMaster, Game Design, Level Design
  • Jimmy Provencher: Programmer, Game Design, Level Design, Story Planner
  • Bryan Bergeron: Integration programmer, Artist, Game Design, Level Design
  • and me: Programmer, Level Design, Story Planner

First meetings

Each game development team (3 for this year) is assigned to a local where only the member of the teams has access. It took about 3 weeks to get our local. Thus, our first meeting was delayed a lot. During our first meeting, we got a crash course of SCRUM from Fred. For those who doesn’t know SCRUM, it’s a agile project management methodology used by many game and software company. SCRUM is focused on user stories, which are requirement based on the point of view of the user. For example: “As a player, I want to attack enemies”.

After the crash course on SCRUM, we started the product backlog. A Product Backlog is a collection of user stories that we want to get into the game. It is saved during the whole project and serve as basis for the sprint.

A Sprint is a iteration in the process. It’s usually last from 2 to 4 weeks (team choice). We chose user stories from the backlog based on our velocity. A velocity is the amount of user points we can do in a sprint.

In our cases, the Sprint 1 looks like this:

  • Allow the character to navigate on a map (screen size)
  • Listen to background music
  • Showing a enemy with basic movements

Our sprint time is set to 4 weeks. Our velocity is quite low because we don’t work full time on the project.

Setuping the environment

A great deal of our time was to setup our environment. Each PC is installed with Windows 7 Professional, yes the final version (gotta love MSDN-AA) with Visual Studio 2008 SP1. We’ve setup our own server, which is running on Windows Server 2008. The server is running VisualSVN server for source code management, Trac+Agilo for SCRUM, ticket management and wiki, CruiseControl.NET for continous integration. As for myself, I am using DevExpress Refactor for C++ and AnkhSVN Visual Studio plugin.

Technologies used

We are coding our own engine from scratch in C++.  Most of the backend will use SDL and related libraries. We are using UnitTest++ and Google Mock for unit testing. We are planning to use Eigen for linear algebra related code. For map editing, we will modify Tiled to suit our needs. For scripting, we are going to use Lua.

As for today

The code is started ! I started working on the user story “Listen to background music”. It should be done soon enough and move to another task.

See you next time !





Refactoring game legacy code Part 2 – Cleaning up the terrain

29 07 2009

In last episode, I introduced my project about refactoring my Tetris clone code.

In order to get a feel of the code, I’m going to do some small mechanical refactorings. I will do only safe renames and extract method in the class related to Block and Piece. I will not try to alter behavior before I write my characterization tests (will explain in a later article). I am using Visual Studio 2005 and Code Rush(which include Refactor! C++) free version. Visual Assist X will be fine too but it costs money.

Let’s go deep into the code !

The first thing I noticed when reading the code related to Piece and Block was this:

void Piece::moveBlocks(int deltaX, int deltaY)
{
 for(int i=0; i<TetraSize; i++)
 {
  m_tetra&#91;i&#93;.move( m_tetra&#91;i&#93;.x() + deltaX, m_tetra&#91;i&#93;.y() + deltaY );
 }
}
&#91;/sourcecode&#93;

Why do I need to query the X and Y coordinates of each block forming the tetra in order to move them ? This is a violation of the principle "<a title="Tell, don't ask" href="http://www.pragprog.com/articles/tell-dont-ask">Tell, don't ask</a>". I'm going to refactor in small steps (like strongly suggested in Martin Folwer's Refactoring).

First I renamed the <code>move</code> method of Block class to <code>moveAbsolute</code> since it's clearly what this method is doing. After, I created a new method called <code>Block::move</code> which really moved according to deltaX and deltaY. Then, I looked up all the calls of moveAbsolute and make then use the new <code>move()</code> if they required this new behavior.

I found a interesting thing. All calls to <code>moveAbsolute</code> are in my poor handling of the Piece. Thus in the future, we will be able to remove this method after several refactorings. This single rename make my bad code stood out of the rest.

After, my random look into the code, I saw this big function:


SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( d_ptr->surfaceCache.count(color) == 0 )
 {
  SDL_Surface *newSurface = SDL_CreateRGBSurface(SDL_HWSURFACE, BlockWidth, BlockHeight, ColorDepth, 0, 0, 0, 0);

  SDL_Rect blockRect;
  blockRect.x=0;
  blockRect.y=0;
  blockRect.w=BlockWidth-1;
  blockRect.h=BlockHeight-1;

  Uint8 r=0,g=0,b=0;
  switch(color)
  {
   case BlockCyan:
    r=0; g=240; b=240;
    break;
   case BlockBlue:
    r=0; g=0; b=240;
    break;
   case BlockOrange:
    r=240; g=160; b=0;
    break;
   case BlockYellow:
    r=240; g=240; b=0;
    break;
   case BlockGreen:
    r=0; g=240; b=0;
    break;
   case BlockPurple:
    r=160; g=0; b=240;
    break;
   case BlockRed:
    r=240; g=0; b=0;
    break;
  }

  SDL_FillRect(newSurface, &blockRect, SDL_MapRGB(newSurface->format, r,g,b));

  d_ptr->surfaceCache[color] = newSurface;
 }

 return d_ptr->surfaceCache[color];
}

As you can see, the if block is creating a new surface if the color has not been asked yet. The problem is this block is in the way of the main logic of my method, which is to retrieve a cached surface for the given color. Using “Extract Method”, I can make my intent more clear.

SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( d_ptr->surfaceCache.count(color) == 0 )
 {
  createCachedSurfaceFor(color);
 }

 return d_ptr->surfaceCache[color];
}

I could go very zealot while refactoring this class. For the intend of this article, I’m going to ‘give my life for Aiur’ errrr make my intention even more clear 🙂

I will extract the if condition into its own method:

SDL_Surface *BlockSurfaceManager::get(BlockColor color)
{
 if( hasNoCachedSurfaceFor(color) )
 {
  createCachedSurfaceFor(color);
 }

 return d_ptr->surfaceCache[color];
}

bool BlockSurfaceManager::hasNoCachedSurfaceFor(BlockColor &color)
{
 return d_ptr->surfaceCache.count(color) == 0;
}

As you can see, it makes my intent more clear. Also, when you extract method with CodeRush, please double check the header if your method has the correct visibility.

For the beverity of the article, I will not describe in details the mechanics of the refactoring of createCachedSurfaceFor() but here’s for your viewing pleasure:

void BlockSurfaceManager::createCachedSurfaceFor(BlockColor &color)
{
 d_ptr->surfaceCache[color] = createBlockSurface(color);
}

SDL_Surface* BlockSurfaceManager::createBlockSurface(BlockColor &color)
{
 SDL_Surface *newSurface = SDL_CreateRGBSurface(SDL_HWSURFACE, BlockWidth, BlockHeight, ColorDepth, 0, 0, 0, 0);

 Uint8 r=0,g=0,b=0;

 getBlockColorRgb(color, r, g, b);

 drawBlockSurface(newSurface, r, g, b);

 return newSurface;
}

void BlockSurfaceManager::drawBlockSurface(SDL_Surface *newSurface, Uint8 &r, Uint8 &g, Uint8 &b)
{
 SDL_Rect blockRect = createBlockRect();
 SDL_FillRect(newSurface, &blockRect, SDL_MapRGB(newSurface->format, r,g,b));
}

SDL_Rect BlockSurfaceManager::createBlockRect()
{
 SDL_Rect blockRect;
 blockRect.x=0;
 blockRect.y=0;
 blockRect.w=BlockWidth-1;
 blockRect.h=BlockHeight-1;
 return blockRect;
}

void BlockSurfaceManager::getBlockColorRgb(BlockColor &color, Uint8 &r, Uint8 &g, Uint8 &b)
{
 switch(color)
 {
 case BlockCyan:
  r=0; g=240; b=240;
  break;
 case BlockBlue:
  r=0; g=0; b=240;
  break;
 case BlockOrange:
  r=240; g=160; b=0;
  break;
 case BlockYellow:
  r=240; g=240; b=0;
  break;
 case BlockGreen:
  r=0; g=240; b=0;
  break;
 case BlockPurple:
  r=160; g=0; b=240;
  break;
 case BlockRed:
  r=240; g=0; b=0;
  break;
 }
}

The code has more lines, but the intend of the code is more clear. I stop here for the moment but I should really factor the surface creator into its own class. BlockSurfaceManager has two responsibilities: Managing the surface cache AND creating the surface. This does not follow the Single Responsibility Principle where each class should have one reason to change.

I will commit the changes I’ve made. Let’s return back to Piece. In Piece::rotate() I have this snipped of code

if( type() == Piece_Square )
 {
  m_direction = newDirection;
  return;
 }

 Block *rotatedBlocks = getBlocks(newDirection);
 if( tryMove(0, 0, rotatedBlocks) )
 {
  delete[] m_tetra;
  m_tetra = rotatedBlocks;
  m_direction = newDirection;
 }
 else
 {
  delete[] rotatedBlocks;
 }

I smell something bad in here. The base class make assumption about its derived classes. I will leave it here for now because I haven’t written my test and I could alter behavior by removing the faulty block.

Here’s another obscure method:

bool Piece::tryMove(int deltaX, int deltaY, Block *block)
{
 bool canMove=true;

 for(int i=0; i<TetraSize; i++)
 {
  if( (block&#91;i&#93;.x() + deltaX) >= BoardEndX ||
   (block[i].x() + deltaX) < BoardX ||
   m_board->blockAt(block[i].x() + deltaX, block[i].y() + deltaY) ||
   (block[i].y() + deltaY) < BoardY ||
   (block&#91;i&#93;.y() + deltaY) >= BoardEndY
   )
  {
   canMove=false;
   break;
  }
 }

 return canMove;
}

The if condition is confusing as hell. With simple reordering and extract method, it becomes this:

if( isOutsideBoard(block[i], deltaX, deltaY) ||
   hasBlockingBlock(block[i], deltaX, deltaY) )
  {
   canMove=false;
   break;
  }
bool Piece::isOutsideBoard(const Block &block, int deltaX, int deltaY) const
{
 return (block.x() + deltaX) >= BoardEndX ||
   (block.x() + deltaX) < BoardX ||
   (block.y() + deltaY) < BoardY ||
   (block.y() + deltaY) >= BoardEndY;
}

bool Piece::hasBlockingBlock(const Block &block, int deltaX, int deltaY)
{
 return m_board->blockAt(block.x() + deltaX, block.y() + deltaY);
}

Make much more sense. Let’s commit this. As for the derived classes of Piece, I won’t ever dare to touch them before I write some tests.

Let’s call done for the day. In the next episode, I will show you how to make the code into test harness.





Refactoring game legacy code: Part 1 – Introduction

26 07 2009

Introduction

Hello, welcome to my series of article about refactoring game legacy code. Game legacy code is no different than other types of code. What’s makes game different than others type of codes ? With a game, you are dealing with a event driven, simulated real-time architecture. They have different needs and requirements than a business, Web or rich client application. Games need to react fast to events such as ‘gun was triggered’, you have parts of your code that are called 30 or 60 times per second, loading times need to be short, etc.

There is a lot of things going on in a game, where in a typical business application you just need to collect some input, process it and persist into a database (good ‘ol CRUD).

The subject

The subject is my first complete game ever written. If you look at the projects sections of this site, you’ll see some failed attempts. The game is a Tetris clone called “That Game From Russia (you know it)”. It is written in C++ using SDL library. It was done in two weeks back in January 2009. If you have read my article Paradigm Shift, you know this was before I read many books and articles that changed my way of thinking about code, design and architecture.

The code can be found at http://code.google.com/p/gamefromrussia/

We start with SVN revision 5.

Current state of the architecture

The architecture is based on a basic state machine. There is a bit of duplicated code scattered around the code, like the input handling. The code is pretty tightly coupled. They are little to no abstraction in the code. SDL types are found in public API. When I created the code, I intended to do my Tetris in one day. Yeah silly I know. I didn’t intend to create a flexible architecture. I wanted to create a Tetris for fun thus I didn’t try to care that much about the craft of my code.

Anyway so you have the states (DropPiece, InitGame, Setup, GameOver, RemoveLines, SetNextPiece, PauseState, LinesRemoveAnimation) that define the core behavior of the game. Each state has a enter(), execute() and exit() methods. enter() initialize the state, execute() is called on every iteration of the game loop and exit() is called before changing to another state.

The “domain” or the core of the game are in the classes Block, BlockLine, Board, Piece. Block is a single block. BlockLine is a line of blocks in the Board. Board contains a list of block lines. Piece is a collection of blocks that represent the shapes in a Tetris game.

The other classes are support classes. SoundPlayer manages sound effects and music. TextPrinter manage drawing the current level, the score and the number of lines.

What we are trying to refactor ?

What I want to refactor is all the code related to the Piece handling. I want to make the rotation code easier to understand. Currently, for each type of piece, you have obscure code that move the blocs depending of the direction. It assume that the x, y is for the center block and the other blocks are moving according to this block. While developing this code, I had sketches on paper to help me map the blocks for each piece. I shouldn’t needed a piece of paper to manage the complexity of this code.

The right way to rotate a piece is to use a rotation matrix and apply it to each of the blocks coordinate. I found this when looking at other Tetris clone code. You don’t need to use a matrix explicitly thought. I will explain the solution a little deeper in a following article.

When you refactor code, you should keep in mind your main goal. When dealing with legacy code, you can be tempted to do large scale refactoring of your ugly, stupid code. Having a goal help you focus of refactoring code that matters for what you are trying to accomplish.

Why I am doing this ?

In this case, I want to train my refactoring, TDD, SOLID skills in a C++ code base in preparation for a much larger game project this fall. Also, I am to experiment some techniques illustrated in Michael C. Feathers book Working Effectively with Legacy Code.

Refactoring legacy code is hard

I don’t claim to be an expert in refactoring legacy code. In fact, it’s quite the opposite. I am still a beginner in refactoring legacy code. Up to this point, I only refactored one legacy code base I written in C# back in 2007. It is quite a hard journey depending of the quality of the code base. My current code base has a lot of inner, subtle dependencies that we will need to break over time.

Next episode

In next episode, we’ll start doing some code cleanups to get a feel of the code base before setting up the tests in preparation for larger refactorings.