Introduction to 2D Linux Game Programming/Printable version


Introduction to 2D Linux Game Programming

The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Introduction_to_2D_Linux_Game_Programming

Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative Commons Attribution-ShareAlike 3.0 License.

Introduction

What this Book Covers

edit

This book covers writing a 2D game in Linux from start to finish. It starts with setting up the build environment for various distributions, and ends with packaging the game for distribution. It also includes creating tools to make creating the game easier. Sprite and level editors for instance. The game itself will be written in SDL 1.2 and use ALSA for audio. We'll primarily cover SDL 1.2, which is probably the most commonly used flavor of SDL out there right now, and briefly cover its successor SDL 1.3. (If SDL 1.3 becomes more prevalent, we may make the transition wholly to that library.) The level editors and other helper tools will largely be written in Qt4. Sample algorithms may be demonstrated first by simply printing to stdout where applicable for simplicity's sake.

Linux is available on a plethora of environments, so this book will also cover porting code from the original environment, in this case an AMD64 based PC, to other environments. The two primary target environments will be the F-200 and the Pandora. Both are ARM based hand-helds, but with very different capabilities, input schemes, and screen resolutions. We'll cover aspects of porting from one environment to another, including control scheme, screen resolution and aspect ratio, and programming considerations.

It will also briefly cover level design considerations, both when dealing with aspect ratio, and when transitioning from an arcade environment to a PC, and to a console.

What this Book Doesn't Cover

edit

This is a programming book, a how to guide for the creation of the code and levels behind a 2D Linux game. It doesn't cover managing your programming team, or suggest ideas for games other than the one that's created in the text of this book. It doesn't delve into marketing of the game.

Languages and Libraries Used in this Book

edit

The code in this book is written in C++. It uses the libraries SDL, Qt4, and ALSA.

Who This Book is Intended For

edit

This book is intended for those who who want to have fun programming games in Linux! It doesn't assume an extensive background in C++ programming, though a basic grasp of the C++ language is required. Language fundamentals are reviewed as those topics are reached while programming the game, but as a fundamental C++ primer this would present the language in an incomplete and haphazard fashion. There are many good C++ Primers that cover the language in a coherent and comprehensive fashion that would be better suited to teaching the student C++.

Some of the concepts covered in this text are complex. Where that's the case this book will cover the concept in a step by step fashion. It is intended to guide those who've been exposed to C++ but may not have a huge experience with the language, and may not have any experience with game programming at all. Experienced programmers may find utility in this book as a reference to topics that are scarcely covered in other texts, though they may find the detail we go into in this book exhaustive.

The Target Game

edit

The end goal of this book is to walk the student through programming and packaging a fully fledged 2D platforming and side scrolling game. Super Mario Brothers is often thought of as the epitome of this genre. Our game, "A Ragamuffin's Destiny" will follow the concepts laid out in those and other good platforming games, while introducing a few twists of our own. All the sprites in this initial game will be low resolution. Most the concepts should translate well to higher resolution 2D games.


Introduction

What this Book Covers

edit

This book covers writing a 2D game in Linux from start to finish. It starts with setting up the build environment for various distributions, and ends with packaging the game for distribution. It also includes creating tools to make creating the game easier. Sprite and level editors for instance. The game itself will be written in SDL 1.2 and use ALSA for audio. We'll primarily cover SDL 1.2, which is probably the most commonly used flavor of SDL out there right now, and briefly cover its successor SDL 1.3. (If SDL 1.3 becomes more prevalent, we may make the transition wholly to that library.) The level editors and other helper tools will largely be written in Qt4. Sample algorithms may be demonstrated first by simply printing to stdout where applicable for simplicity's sake.

Linux is available on a plethora of environments, so this book will also cover porting code from the original environment, in this case an AMD64 based PC, to other environments. The two primary target environments will be the F-200 and the Pandora. Both are ARM based hand-helds, but with very different capabilities, input schemes, and screen resolutions. We'll cover aspects of porting from one environment to another, including control scheme, screen resolution and aspect ratio, and programming considerations.

It will also briefly cover level design considerations, both when dealing with aspect ratio, and when transitioning from an arcade environment to a PC, and to a console.

What this Book Doesn't Cover

edit

This is a programming book, a how to guide for the creation of the code and levels behind a 2D Linux game. It doesn't cover managing your programming team, or suggest ideas for games other than the one that's created in the text of this book. It doesn't delve into marketing of the game.

Languages and Libraries Used in this Book

edit

The code in this book is written in C++. It uses the libraries SDL, Qt4, and ALSA.

Who This Book is Intended For

edit

This book is intended for those who who want to have fun programming games in Linux! It doesn't assume an extensive background in C++ programming, though a basic grasp of the C++ language is required. Language fundamentals are reviewed as those topics are reached while programming the game, but as a fundamental C++ primer this would present the language in an incomplete and haphazard fashion. There are many good C++ Primers that cover the language in a coherent and comprehensive fashion that would be better suited to teaching the student C++.

Some of the concepts covered in this text are complex. Where that's the case this book will cover the concept in a step by step fashion. It is intended to guide those who've been exposed to C++ but may not have a huge experience with the language, and may not have any experience with game programming at all. Experienced programmers may find utility in this book as a reference to topics that are scarcely covered in other texts, though they may find the detail we go into in this book exhaustive.

The Target Game

edit

The end goal of this book is to walk the student through programming and packaging a fully fledged 2D platforming and side scrolling game. Super Mario Brothers is often thought of as the epitome of this genre. Our game, "A Ragamuffin's Destiny" will follow the concepts laid out in those and other good platforming games, while introducing a few twists of our own. All the sprites in this initial game will be low resolution. Most the concepts should translate well to higher resolution 2D games.


Algorithms/Simple Software Transformations/Non Optimized Integer Scaling

Non-optimized Integer Scaling

edit

Introduction

edit

This algorithm demonstrates scaling an image by one positive integer value in both the x and y axis. A common use for this algorithm would be taking a game made for a GameBoy's low resolution screen and doubling or tripling it before displaying it on a PC's high resolution monitor.

This algorithm keeps the aspect ratio of the original image. Think of watching old TV shows on a widescreen monitor. You can either stretch the image to fit the screen, distorting the original image and making all the actors appear fatter, or you can watch in the original aspect ratio, but then you have black bars on either side of the screen. With this algorithm you'd have the black bars. Princess Peach won't have to worry about why she's gained so much weight!

To understand how this algorithm works, it's important to know how most libraries store image data in memory. Where an image is a two dimensional object, most libraries store the individual pixels of the image in a single dimensional array - a line. The game has to keep track of the height of the image and its width to manipulate the image. Some libraries, like SDL, hide this from you until you need to do something like rotate or scale the image.

Setting up the Problem

edit

To show how this works, we'll scale one 3x3 image. In the following images, each square will represent a single pixel.

012
345
678


Stored in an array, that image looks like:

012345678


If you're trying to come up with an algorithm, it's handy to step through the problem a piece at a time. The first thing we're going to do is copy the first pixel three times:

   


We need to continue copying 3 pixels for every pixel in the source image until we hit the end of the row to properly scale the width of the image:

         


Then we need to repeat this 3 times to properly scale the height of the image:

                           


Which drawn on the screen looks like:

         
         
         

The Source:

edit
// IntegerScaleNonOptimized.cpp

#include  <iostream>
#include "include/S.h"
using namespace std;

/********************************************
 *
 * This is an image scaling algorithm.  It 
 * will scale an image in an array by a
 * positive integer value.
 *
 * It keeps the original image's aspect ratio.
 * If the original aspect ration was 4:3 and
 * you have a wide screen monitor, there
 * will be black vertical bars on either side
 * of the screen.  The image will not be distorted.
 * Princess Peach won't have to worry about why 
 * she's suddenly so much wider.  :b
 *
 * It makes an array that is width*height sized.
 * It initializes every element in the array 
 * with its array position.
 *
 * Then it makes a second array that
 * is width*scale * height*scale sized.
 *
 * It prints the first array, a couple newlines,
 * then it scales and prints the second array.
 *
 ********************************************/

int main()
{
	int width  = S::sw();    // The original Array's width
	int height = S::sh(width);   // The original Array's height
	int scale = S::ss(width);
	int source[height*width]; // Original Array
	int dest[height*scale * width*scale]; // Dest Array

	cout << "\n\n";

/*********************************************
 * The source array
 *
 * This loop constructs an "image" with a
 * unique color in each position of the array,
 *
 * width * height is the integral statement
 * here.  If width is set to 3 and height is
 * set to 3 it creates an array[9] that has
 * 9 elements, starting with element 0 and
 * continuing to element 8.
 *********************************************/
	for(int x = 0; x < width*height; x++)
		source[x]=x;

	S::print_rect(width,height,&source[0]);
	cout << "\n\n";



/*********************************************
 * The destination, scaled array
 *
 * In games most 2 dimmensional images are
 * stored in a one dimmensional array.
 *
 * A four color image
 * 0,1
 * 2,3

 * that that's been scaled up
 * by two looks like:
 *      0,0,1,1
 *      0,0,1,1
 *      2,2,3,3
 *      2,2,3,3
 * when print_rected
 *
 * and is stored in the array as:
 *      [0,0,1,1,0,0,1,1,2,2,3,3,2,2,3,3]
 *
 *********************************************/

	for(int h = 0; h < height; h++) // Source Height Position
	{
		for(int hrepeat = 0; hrepeat < scale; ++hrepeat) // Repeat a full row scale times
		{
			for(int w = 0; w < width; w++) // Source width Offset
			{
				// Repeat individual pixel scale times
				// See the tutorial for an explanation of this line.
				for(int wrepeat = 0; wrepeat < scale; wrepeat++)
					dest[ ((h*scale * width*scale) + (hrepeat * width*scale)) + (w*scale + wrepeat)] = source[(h*width) + w];
			}
		}
	}

	S::print_rect(width*scale,height*scale,&dest[0]);

	cout << "\n";
	return(0);
}


Algorithms/Simple Software Transformations/Optimized Integer Scaling

Optimized Image Scaling

edit

This takes the previous, non-optimzed version of the integer scaling program and changes a few things to make it much quicker.

The Source:

edit
// IntegerScale.cpp

#include <iostream>
#include "include/S.h"

using namespace std;

/************************************************************
 * This is the speed optimized version of our IntegerScale()
 * function.  Readability is a distant second to speed.
 * We pre-calculate everything we can rather than calculating
 * over and over in the inner for loops.  Where we can't
 * pre-calculate, we move the calculation as "high" up in the
 * for loop chain as we can.  We move definitions of variables
 * out of the loops so they're defined once rather than every 
 * iteration.  Instead of rebuilding a row scale times we
 * calculate it once, then copy it scale-1 times.  It is a
 * much quicker algorithm, but it's also much more difficult
 * to read or explain.  After seeing the original, unoptomized
 * scale function, it should be easier to see what this one
 * is doing.
 ************************************************************/

int main()
{
	// All the variables that can be pre-calculated
	// and do not change in the for loops
	const int width = S::sw();         // The original Array's width
	const int height = S::sh(width);   // The original Array's height
	const int scale = S::ss(width);    // How many times to scale the image
	const int s_size = width*height;   // Size of source image
	const int widthscale = width*scale;
	const int scalewidthscale = scale*widthscale;
	const int heightscale = height*scale;

	// Our source and destination arrays
	int source[s_size];     // Original Array
	int dest[scalewidthscale * height]; // Scaled Array

	// Pointers for the line we're going to copy
	int *repeat; // Address of first element of row we're copying
	int *traverserepeat; // Address of the element we're copying
	int *traversedest; // Address we're copying to in dest array

	// Setup our source "image"
	for(int x = 0; x < s_size; ++x)
		source[x]=x;

	cout << "\n\n";
	S::print_rect(width, height, &source[0]);
	cout << "\n\n";

	// The following for loop iterators 
	// defined out of the for loop to 
	// avoid recreating them every time 
	// we drop out of the inner loops
	int hrepeat = 0; // height repeat
	int w = 0;       // width counter
	int wrepeat = 0; // width repeat

	// Integers we calculate as high up in the for loops
	// as possible
	int hscalewidthscale; // h * scale * width * scale
	int wscale;           // w * scale
	int hwidth;           // h * width

	for(int h = 0; h < height; ++h) // Source Height Position
	{
		hscalewidthscale = h*scalewidthscale;

		// Set the address of the first element of the array
		// We're going to copy
		repeat=&dest[hscalewidthscale];

		for(; hrepeat < scale; ++hrepeat) // Repeat a full row scale times
		{
			if(hrepeat==0) // This is a new line in the source image
			{
				for(; w < width; ++w) // Source width Offset
				{
					wscale = w*scale;
					hwidth = h*width;
					// Repeat individual pixel scale times
					for(; wrepeat < scale; ++wrepeat)
						dest[hscalewidthscale + wscale + wrepeat] = source[ hwidth + w];

					wrepeat = 0;
				}
				w = 0;
			}
			else
			{
				// The initial element we're going to copy
				traverserepeat=repeat;

				// The initial element to copy to dest[]
				traversedest=&repeat[hrepeat*widthscale];
				for(; w < width; ++w)
				{
					wscale = w*scale;
					for(; wrepeat < scale; ++wrepeat)
					{
						// Copy a value into dest[]
						*traversedest=*traverserepeat;

						// Traverse the array we're
						// copying from and into
						++traversedest;
						++traverserepeat;
					}
					wrepeat=0;
				}
				w = 0;
			}
		}
		hrepeat = 0;
	}

	S::print_rect(widthscale,heightscale,&dest[0]);
	cout << "\n" << endl;

	return(0);
}


Algorithms/Simple Software Transformations/90° Image Rotation

90° Image Rotation

edit

In 2D games it's often necessary to rotate an image by 90 degrees. This algorithm does so quickly and easily.

The Source:

edit
// Rotate90.cpp
#include <iostream>
#include "include/S.h"
#include <string>
#include <sstream>
using namespace std;

int main()
{
	// All rotations are clockwise
	// rotate = 0 - do nothing
	// rotate = 1 - rotate 90
	// rotate = 2 - rotate 180
	// rotate = 3 - rotate 270
	// const int rotate;
        const int width = S::sw();         // The original Array's width
	cout << "\n";
        const int height = S::sh(width);   // The original Array's height
	const int widthheight = width * height;
	int source[widthheight];
	int dest[widthheight];
	int rotate;

        for(int x = 0; x < widthheight; x++)
                source[x]=x;

	cout << "\n\n";
	S::print_rect(width, height, &source[0]);

	cout << "\nTo rotate clockwise by 0   degrees enter 0."
	     << "\nTo rotate clockwise by 90  degrees enter 1."
	     << "\nTo rotate clockwise by 180 degrees enter 2."
	     << "\nTo rotate clockwise by 270 degrees enter 3: ";
	cin >> rotate;	
	cout << "\n\n";

	switch(rotate)
	{
		// Do nothing
		case 0:
			S::print_rect(width, height, &source[0]);
			break;

		// Rotate 90
		case 1:
			for(int h = 0, dest_col = height - 1; h < height; ++h, --dest_col)
			{
				for(int w = 0; w < width; w++)
				{
					dest[ ( w * height ) + dest_col] = source[h*width + w];
				}
			}

			S::print_rect(height, width, &dest[0]);
			break;

		// Rotate 180
		case 2:
			for( int h=0, dest_row=(height-1); h < height; --dest_row, ++h )
			{
				for ( int w=0, dest_col=(width-1); w < width; ++w, --dest_col )
				{
					dest[ dest_row * width + dest_col] = source[h*width + w];
				}
			}

			S::print_rect(width, height, &dest[0]);
			break;

		// Rotate 270
		case 3:
			for(int h = 0, dest_col=0; h < height; ++dest_col, ++h)
			{
				for(int w=0, dest_row=width-1; w < width; --dest_row, ++w)
				{
					dest[(dest_row * height) + dest_col] = source[ h * width + w];
				}
			}

			S::print_rect(height, width, &dest[0]);
			break;

		// Out of range, do nothing
		default:
			break;
	}

	cout << endl;
	return 0;
}


Algorithms/Simple Software Transformations/Flip Image

Flip Image

edit

Probably the most common reason to flip an image in gaming is to reverse the direction a character is running. For instance, if our Ragamuffin is running left, we could use this algorithm to flip his image and have him run to the right. You'd typically do this before you actually needed to have him run in the other direction. This algorithm may actually be more beneficial in a sprite creation program than in the game itself, but either way it neatly flips the image horizontally, or vertically.

The Source:

edit
// Flip.cpp
#include <iostream>
#include "include/S.h"

using namespace std;

int main()
{
	static int width=S::sw();
	static int height=S::sh(width);
	int horizontal;
	int *position;
	int source[width*height];
	int dest[width*height];

	cout << "\n\n";

        for(int x = 0; x < width*height; x++)
                source[x]=x;

	S::print_rect(width, height, &source[0]);

	cout << "\nFlip horizontally?"
	     << "\nEnter 1 for true, 0 for false: ";
	cin >> horizontal;
	cout << endl;

	if(horizontal)
	{
		for(int h = 0; h < height; ++h)
		{
			position=&source[h*width];
			for(int w = width - 1, i = 0; w > -1; --w, ++i)
				dest[h*width + i] = position[w];
		}
	}
	else
	{
		for(int h = height - 1, i = 0; h > -1; --h, ++i)
		{
			position=&source[h*width];
			for(int w = 0; w < width; ++w)
				dest[i*width+w] = position[w];
		}
	}

	S::print_rect(width, height, &dest[0]);
	cout << endl;
}


Algorithms/Simple Software Transformations/The Helper Functions

The Helper Functions

edit

Rather than writing a function to print the 2D array of integers for every one of these simple examples, or manually ask for and validate input, these helper functions do the work for us. It also keeps the code cleaner, keeping the printing away from the algorithm we're showing.

The Source:

edit
// S.cpp
// Static Functions
#include "include/S.h"
#include <iostream>
using namespace std;

void S::print_rect(int width, int height, int *image)
{
        for(int x = 0; x < width*height; x++)
        {
                cout << image[x];

                if((x+1) % width != 0)
                        cout << ",";
                else
                        cout << "\n";
        }
}

int S::sw()
{
        int width;

        cout << "\nEnter a value between 1 and 10 for the width: ";
        cin >> width;
        if(width > 10 || width < 1)
        {
                cout << "\nOut of Range!\n\n";
                return 0;
        }

        return width;
}

int S::sh(int width)
{
	int height;

	// keeps the image from having 2 characters
	if(width > 6)
	{
		cout << "\nWidth > 6, height set to 1";
		height = 1;
	}
	else if(width == 5 || width == 4)
	{
		cout << "\nEnter a height between 1 and 2 for height: ";
		cin >> height;
		if(height < 1 || height > 2)
		{
			cout << "\nOut of Range!\n";
			return 0;
		}
	}
	else if(width == 3)
	{
		cout << "\nEnter a height between 1 and 3 for height: ";
		cin >> height;
		if(height > 3 || height < 1)
		{
			cout << "\nOut of Range!\n";
			return 0;
		}
	}
	else if(width == 2)
	{
		cout << "\nEnter a height between 1 and 5 for height: ";
		cin >> height;
		if(height > 5 || height < 1)
		{
			cout << "\nOut of Range!\n";
			return 0;
		}
	}
	else
	{
		cout << "\nEnter a height between 1 and 10 for height: ";
		cin >> height;
		if(height > 10 || height < 1)
		{
			cout << "\nOut of Range!\n";
			return 0;
		}
	}

	return height;
}

int S::ss(int width)
{
	int scale;

	switch(width)
	{
		case 10 :
		case 9 :
	                cout << "\nEnter a value to scale the image between 1 and 4: ";
			cin >> scale;
			if (scale < 1 || scale > 4)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 8 :
		case 7 :
			cout << "\nEnter a value to scale the image between 1 and 5: ";
			cin >> scale;
			if (scale < 1 || scale > 5)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 6 :
			cout << "\nEnter a value to scale the image between 1 and 6: ";
			cin >> scale;
			if (scale < 1 || scale > 6)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;
		case 5 :
			cout << "\nEnter a value to scale the image between 1 and 8: ";
			cin >> scale;
			if (scale < 1 || scale > 8)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 4 :
			cout << "\nEnter a value to scale the image between 1 and 10: ";
			cin >> scale;
			if (scale < 1 || scale > 10)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 3 :
			cout << "\nEnter a value to scale the image between 1 and 13: ";
			cin >> scale;
			if (scale < 1 || scale > 13)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 2 :
			cout << "\nEnter a value to scale the image between 1 and 20: ";
			cin >> scale;
			if (scale < 1 || scale > 20)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		case 1 :
			cout << "\nEnter a value to scale the image between 1 and 80: ";
			cin >> scale;
			if (scale < 1 || scale > 80)
			{
				cout << "\nOut of Range!\n";
				return 0;
			}
			break;

		default : break;
	}

	return scale;
}


Algorithms/Simple Software Transformations/The Makefile

The Makefile

edit

The Source:

edit
#Makefile
CXX = g++ `sdl-config --cflags`
CCFLAGS = -g -Wall -lSDL_image

all: IntegerScaleNonOptimized IntegerScale Rotate90 Flip

IntegerScaleNonOptimized : S.o IntegerScaleNonOptimized.o
	${CXX} ${CCFLAGS} -o IntegerScaleNonOptimized IntegerScaleNonOptimized.o S.o

IntegerScaleNonOptimized.o : IntegerScaleNonOptimized.cpp S.o
	${CXX} ${CCFLAGS} -c IntegerScaleNonOptimized.cpp

IntegerScale : IntegerScale.o S.o
	${CXX} ${CCFLAGS} -o IntegerScale IntegerScale.o S.o

IntegerScale.o : IntegerScale.cpp S.o
	${CXX} ${CCFLAGS} -c IntegerScale.cpp

Rotate90 : Rotate90.o S.o
	${CXX} ${CCFLAGS} -o Rotate90 Rotate90.o S.o

Rotate90.o : Rotate90.cpp S.o
	${CXX} ${CCFLAGS} -c Rotate90.cpp

Flip : Flip.o S.o
	${CXX} ${CCFLAGS} -o Flip Flip.o S.o

Flip.o : Flip.cpp S.o
	${CXX} ${CCFLAGS} -c Flip.cpp

S.o : S.cpp include/S.h
	${CXX} ${CCFLAGS} -c S.cpp
clean : 
	rm -f *.o IntegerScale Rotate90 IntegerScaleNonOptimized Flip

allclean :
	make clean
	make IntegerScaleNonOptimized
	make IntegerScale
	make Rotate90
	make Flip


Algorithms/Simple Software Transformations/The Zipped Code

The Zipped Code

edit

Zipped Code

edit
Description

The Complete, Zipped Source Code

Source

own work

Date

2009-08-22

Author

Wardred

Permission
(Reusing this file)

See below.