Suggestions for 2D Game Engine (Allegro 5)

I am building a game engine to use for making 2D games with Allegro 5 as my graphics library. So far everything I've included seems to work, but I would like some suggestions on what exactly a good game engine should have? I program as a hobby so I have no idea what "professional" game engines should look like. Ideas I already have that aren't yet implemented include collision detection and path finding algorithms.

Game Engine in action ->Example main() using the game engine to create a window and draw a tile from a tile sheet image and draw "Hello World" on the window. There are no errors in this code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "../../../Libraries/HXPA5v1_0_GameEngine.h"
using namespace obj;

int main()
{
	Engine eng;
	eng.HideConsole();
	
	//Initialize Graphics & Build Window
	Window win;
	win.Initialize(800,800,"PAC MAN REBOOT",(eng.GetScreenWidth()/2) - 400,(eng.GetScreenHeight()/2) - 400);
	win.Clear(Color().Black);

	//Write some text on the window
	Font fPalette;
	fPalette.SetFont("arial.ttf",32,LEFT,Color().White);
	win.Text("Hello World!",fPalette,Point(32*7,0));

	//Load an image
	Bitmap wall;
	wall.ImgLoad("walls.png");

	//Define a grid
	Grid grid;
	grid.BuildGrid(32,32,6,6);
	
	//Draw to the window based on our grid dimensions
	for (int i=0; i<grid.GetCellCount(); ++i)
		wall.DrawRegion(0,0,grid.GetPoint(i).X,grid.GetPoint(i).Y);

	//Update window
	win.Refresh();

	bool gameRunning = true;
	while (gameRunning)
	{
		if (win.DoEvents() == WINDOW_CLOSE) gameRunning = false;
	}

	wall.Destroy();
	win.Destroy();
	return 0;
}


Game Engine header file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/**

	HXPA5v1_0_GameEngine
	(HellfireXP's Allegro 5 version 1.0 Game Engine)
**/
#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <direct.h>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <iomanip>
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>

#define _WIN32_WINNT 0x0500

using namespace std;

ALLEGRO_DISPLAY *display = NULL;
ALLEGRO_FONT *graphicsFont;
ALLEGRO_EVENT_QUEUE *event_queue;
ALLEGRO_EVENT e;
ALLEGRO_TIMER *timer;
ALLEGRO_KEYBOARD_STATE keyState;

enum TEXT_ALIGN {LEFT,CENTER,RIGHT};
enum EVENT_LIST {NONE,BUTTON_LEFT_CLICK,BUTTON_RIGHT_CLICK,WINDOW_CLOSE};

/*****************************************
******************************************
************* MISC OBJECTS ***************
******************************************
*****************************************/
namespace obj{

class Color{
public:
	ALLEGRO_COLOR Black, White, LightGray, DarkGray, Pink, Red, Maroon, Magenta, 
	Brown, Yellow, LightGreen, Green, DarkGreen, LightBlue, Blue, DarkBlue, Purple;
	Color() 
	:Black(al_map_rgb(0,0,0)), White(al_map_rgb(255,255,255)),
	LightGray(al_map_rgb(195,195,195)), DarkGray(al_map_rgb(127,127,127)),
	Pink(al_map_rgb(255,174,201)), Red(al_map_rgb(255,0,0)), Maroon(al_map_rgb(136,0,21)),
	Magenta(al_map_rgb(255,0,255)), Brown(al_map_rgb(185,122,87)), Yellow(al_map_rgb(255,255,0)),
	LightGreen(al_map_rgb(128,255,128)), Green(al_map_rgb(0,255,0)),
	DarkGreen(al_map_rgb(0,128,0)), LightBlue(al_map_rgb(128,128,255)),
	Blue(al_map_rgb(0,0,255)), DarkBlue(al_map_rgb(0,0,128)), Purple(al_map_rgb(163,73,164))
	{}
	ALLEGRO_COLOR RGBA(int r=0, int g=0, int b=0, int a=255) 
	{
		if (r < 0 || r > 255) r = 0;
		if (g < 0 || g > 255) g = 0;
		if (b < 0 || b > 255) b = 0;
		if (a < 0 || a > 255) a = 255;
		ALLEGRO_COLOR classCLR = al_map_rgba(r,g,b,a);
		return classCLR;
	}
};

class Font{
private:
	bool VALID;
	int Size;
	TEXT_ALIGN Alignment;
	ALLEGRO_COLOR Clr;
	string Name;
public:
	Font() :VALID(false), Size(12), Alignment(LEFT), Clr(al_map_rgb(0,0,0)), Name("arial.ttf") {}
	void SetFont(string name, int size = 12, TEXT_ALIGN alignment = LEFT, ALLEGRO_COLOR clr = al_map_rgb(0,0,0))
	{
		VALID = false;
		Size = size;
		Alignment = alignment;
		Clr = clr;
		char * dir = _getcwd(NULL, 0); 
		stringstream ss;
		ss << dir << "\\" << name;
		string fPath = ss.str();
		Name = fPath;
		if (ifstream(fPath.c_str())) VALID = true;
	}
	string GetName() { return Name; }
	ALLEGRO_COLOR GetColor() { return Clr; }
	TEXT_ALIGN GetAlignment() { return Alignment; }
	int GetSize() { return Size; }
	bool IsValid() { return VALID; }
};

struct Point{
	int X,Y;
	Point(int X, int Y) :X(X),Y(Y) {}
	Point() :X(0),Y(0) {}
};

struct Rect{
	int X,Y,Width,Height;
	Rect(int X, int Y, int Width, int Height) :X(X),Y(Y),Width(Width),Height(Height) {}
	Rect() :X(0),Y(0),Width(10),Height(10) {}
};

struct Line{
	int X1, Y1, X2, Y2;
	Line(int X1, int Y1, int X2, int Y2) :X1(X1),Y1(Y1),X2(X2),Y2(Y2) {}
	Line() :X1(0),Y1(0),X2(1),Y2(1) {}
};

struct Circle{
	int centerX, centerY, Radius;
	Circle(int centerX, int centerY, int Radius) :centerX(centerX),centerY(centerY),Radius(Radius) {}
	Circle() :centerX(0),centerY(0),Radius(10) {}
};

class Timer{
public:
	Timer() {}
	void Set(double framesPerSecond) 
	{  
		timer = al_create_timer(1.0/framesPerSecond); 
		al_register_event_source(event_queue, al_get_timer_event_source(timer));
	}
	void Start() { al_start_timer(timer); }
	void Stop() { al_stop_timer(timer); }
};

class Grid{
private: 
	int CellWidth, CellHeight, Columns, Rows;
public:
	bool BuildGrid(int,int,int,int);
	Grid() :CellWidth(32),CellHeight(32),Columns(5),Rows(5) {}
	Grid(int cwidth, int cheight, int cols, int rows) { BuildGrid(cwidth,cheight,cols,rows); }
	Point GetPoint(int);
	int GetCell(Point);
	int GetCellCount() { return (Columns * Rows); }
};

} //End of namespace obj

/*****************************************
******************************************
************ PRIMARY CLASSES *************
******************************************
*****************************************/
using namespace obj;

class Window{
public:
	Point MOUSE;
	Window() {}
	bool Initialize(int,int,string,int,int,int);
	void Refresh() { al_flip_display(); }
	void Pause(double seconds) { al_rest(seconds); }
	void Focus() { al_set_target_bitmap(al_get_backbuffer(display)); }
	void Clear(ALLEGRO_COLOR);
	void Text(string,Font,Point);
	EVENT_LIST DoEvents();
	void DrawPixel(Point, ALLEGRO_COLOR);
	void DrawLine(Line, ALLEGRO_COLOR);
	void DrawCircle(Circle, ALLEGRO_COLOR, bool);
	void DrawRectangle(Rect, ALLEGRO_COLOR, bool);
	void Destroy();
};

class Bitmap{
public:
	ALLEGRO_BITMAP *tBmp;
	void ImgLoad(string);
	void Focus() { al_set_target_bitmap(tBmp); }
	void ImgCreate(int,int);
	void Draw(int,int,int);
	void DrawRegion(int,int,int,int,int,int,int);
	void Destroy();
	void TransparentClr(ALLEGRO_COLOR);
	int GetTileX(int,int);
	int GetTileY(int,int,int);
};

class MusicBox{
private:
	vector<string> mList;
	ALLEGRO_SAMPLE *mSample;
	ALLEGRO_SAMPLE *mbgSample;
	ALLEGRO_SAMPLE_INSTANCE *mInstance;
	int mLoaded;
	int mBGLoaded;
public:
	MusicBox() :mBGLoaded(false), mLoaded(-1) {}
	void AddMusic(string);
	void Play(int,bool);
	void SetBGMusic(string);
	void PlayBGMusic();
	void Destroy();
};

class Engine{
public:
	Engine() {}
	void Randomize() { srand(time(0)); }
	int Random(int,int);
	string CStr(int);
	string CenterText(string,int);
	string Hex(int);
	void HideConsole()
	{
		HWND hWnd = GetConsoleWindow();
		ShowWindow( hWnd, SW_HIDE );
	}
	int GetScreenWidth() { return GetSystemMetrics(0); }
	int GetScreenHeight() { return GetSystemMetrics(1); }
};
Last edited on
Game Engine header file (continued):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

/*****************************************
******************************************
********* WINDOWS FUNCTIONALITY **********
******************************************
*****************************************/

bool Window::Initialize(int windowWidth=800, int windowHeight=600, string Title="", int leftPos=0, int topPos=0, int dflags = ALLEGRO_WINDOWED)
{
		if (display) al_destroy_display(display);
		al_init_font_addon();
		al_init_ttf_addon();
		al_init_primitives_addon();
		if (!al_init()) return false;
		al_set_new_display_flags(dflags);
		display = al_create_display(windowWidth,windowHeight);
		al_set_window_position(display,leftPos,topPos);
		const char* t = Title.c_str();
		al_set_window_title(display,t);
		if (!display) return false;
		al_install_keyboard();
		al_install_mouse();
		al_init_image_addon();
		al_install_audio();
		al_init_acodec_addon();
		event_queue = al_create_event_queue();
		al_register_event_source(event_queue, al_get_keyboard_event_source());
		al_register_event_source(event_queue, al_get_display_event_source(display));
		al_register_event_source(event_queue, al_get_mouse_event_source());
		return true;
}

void Window::Clear(ALLEGRO_COLOR CLR = al_map_rgb(255,255,255)) { al_clear_to_color(CLR); }

void Window::Text(string msg, Font aFont, Point aPoint)
{
	graphicsFont = al_load_font(aFont.GetName().c_str(), aFont.GetSize(), NULL);
	al_draw_text(graphicsFont, aFont.GetColor(), aPoint.X,aPoint.Y, aFont.GetAlignment(), msg.c_str());
}

EVENT_LIST Window::DoEvents() 
{ 
	al_wait_for_event(event_queue, &e);
	al_get_keyboard_state(&keyState);
	if (e.type == ALLEGRO_EVENT_MOUSE_AXES)
		{
			MOUSE.X = e.mouse.x;
			MOUSE.Y = e.mouse.y;
		}
	if (e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) return WINDOW_CLOSE;
	if (e.type == ALLEGRO_EVENT_DISPLAY_SWITCH_IN) Refresh();
	if (e.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN)
	{
		if (e.mouse.button & 1) return BUTTON_LEFT_CLICK;
		if (e.mouse.button & 2) return BUTTON_RIGHT_CLICK;
	}
	//Add more here (don't forget to add them to the enum up top)
	return NONE;
}

void Window::DrawPixel(Point aPoint, ALLEGRO_COLOR clr){ al_draw_pixel(aPoint.X,aPoint.Y,clr); }
void Window::DrawLine(Line aLine, ALLEGRO_COLOR clr){}
void Window::DrawCircle(Circle aCircle, ALLEGRO_COLOR clr, bool filled = false){}
void Window::DrawRectangle(Rect aRectangle, ALLEGRO_COLOR clr, bool filled = false){}

void Window::Destroy()
{
	al_destroy_event_queue(event_queue);
	al_destroy_timer(timer);
	al_destroy_font(graphicsFont);
	al_destroy_display(display); 
}

/*****************************************
******************************************
********* BITMAP FUNCTIONALITY ***********
******************************************
*****************************************/

void Bitmap::ImgLoad(string fName) 
{ 
	tBmp = al_load_bitmap(fName.c_str()); 
}

void Bitmap::Draw(int X, int Y, int flags = NULL) { al_draw_bitmap(tBmp,X,Y,flags); }

void Bitmap::Destroy() { al_destroy_bitmap(tBmp); }

void Bitmap::DrawRegion(int sourceX = 0, int sourceY = 0, int destX = 0, int destY = 0, int sourceWidth = 32, int sourceHeight = 32, int flags = NULL)
{
	al_draw_bitmap_region(tBmp,sourceX,sourceY,sourceWidth,sourceHeight,destX,destY,flags);
}

void Bitmap::ImgCreate(int width, int height)
{
	tBmp = al_create_bitmap(width,height);
}

void Bitmap::TransparentClr(ALLEGRO_COLOR clr)
{
	al_convert_mask_to_alpha(tBmp, clr);
	//al_draw_tinted_bitmap
}

int Bitmap::GetTileX(int Index, int tileWidth = 32)
{
	if (tileWidth <= 0) return 0;
	if (Index <= 0) return 0;
	int bmpWidth = al_get_bitmap_width(tBmp);
	int columns = (bmpWidth / tileWidth);
	int tempX = Index;
	if (tempX >= columns) tempX = tempX % columns;
	tempX = tempX * tileWidth;
	if (tempX >= bmpWidth) return 0;
	return tempX;
}

int Bitmap::GetTileY(int Index, int tileWidth = 32, int tileHeight = 32)
{
	if (tileWidth <= 0) return 0;
	if (tileHeight <= 0) return 0;
	if (Index <= 0) return 0;
	int bmpWidth = al_get_bitmap_width(tBmp);
	int columns = (bmpWidth / tileWidth);
	int tempY = (Index / columns) * tileHeight;
	if (tempY >= al_get_bitmap_height(tBmp)) return 0;
	return tempY;
}

/*****************************************
******************************************
******** MUSIC BOX FUNCTIONALITY *********
******************************************
*****************************************/

void MusicBox::AddMusic(string fName)
{
//Add file name to the playlist
	mList.push_back(fName);

//If this is the first file added, load
//this file as default sample(song)
	if (mList.size() == 1) 
	{
		mSample = al_load_sample(mList[0].c_str());
		mLoaded = 0;
	}

//Increase the number of max simultaenous files that
//can be played overlapping
	al_reserve_samples(mList.size()+1);
}

void MusicBox::SetBGMusic(string fName)
{
	mbgSample = al_load_sample(fName.c_str());
	mInstance = al_create_sample_instance(mbgSample);
	al_set_sample_instance_playmode(mInstance, ALLEGRO_PLAYMODE_LOOP);
	al_attach_sample_instance_to_mixer(mInstance, al_get_default_mixer());
	mBGLoaded = true;
}

void MusicBox::PlayBGMusic()
{
	al_play_sample_instance(mInstance);
}

void MusicBox::Play(int Index = -1, bool loop = false)
{
//If out of bounds, exit function
	if (mList.size() == 0) return;
	if (Index > mList.size()-1 || Index < -1) return;

//If Index is -1 then play the current loaded file
//(This allows x.Play() to just replay the same song without passing arguments)
	if (Index == -1) Index = mLoaded;

//If the Index to be played is different than the last file played,
//load the new file, otherwise don't reload the same file again.
	if (mLoaded != Index) mSample = al_load_sample(mList[Index].c_str());

//Set the currently loaded file = to the file we just loaded
	mLoaded = Index;

//Play the file
	if (loop == false) al_play_sample(mSample,1.0, 0.0, 1.0, ALLEGRO_PLAYMODE_ONCE, 0);
	if (loop == true) al_play_sample(mSample, 1.0, 0.0, 1.0, ALLEGRO_PLAYMODE_LOOP, 0);
}

void MusicBox::Destroy() 
{ 
	if (mList.size() == 0) return;
	if (mLoaded != -1) al_destroy_sample(mSample); 
	if (mBGLoaded) al_destroy_sample(mbgSample);
	if (mBGLoaded) al_destroy_sample_instance(mInstance);
}

/*****************************************
******************************************
********* ENGINE FUNCTIONALITY ***********
******************************************
*****************************************/

int Engine::Random(int minVal, int maxVal)
{
	int r;
	r= rand();
	r = (r % (maxVal-minVal)) + minVal;
	return r;
}

string Engine::CStr(int val)
{
	stringstream ss;
	ss << val;
	return ss.str();
}

string Engine::CenterText(string txt, int width)
{
	stringstream ss;
	if (width < txt.length()) return txt;
	int spc = (width - txt.length())/2;
	ss << setw(spc + txt.length()) << txt << setw(spc) << " ";
	return ss.str();
}
string Engine::Hex(int val)
{
	stringstream ss;
	ss << uppercase << hex << val;
	return ss.str();
}
Last edited on
Well, I guess that it largely depends on what you want the game to be about -- once the main structural framework is in place, like you show here, I think that further functionality for your engine and game will depend on what you want to have happening in it. Think of your desired gameplay, and translate that into C++/Allegro terms and this should give you smaller and specific "goals"/features to start implementing in your engine.

At this point you could implement an "entity" class that can represent animated characters/creatures/etc, store collision rects, animated textures, animation clip information, etc. . With that, and collision detection, you could have a player character in your game, and after adding some AI functions, you could have non-player entities moving around your world (and also using your collision detection functions) etc. .

Also, I note that you should load and store textures in a common area and have the instances of your entities/characters/environments refer to that single texture repository, in order to not have duplicates of a resource take up system memory. This is particularly useful if you have multiple instances of a character loaded and active at the same time (but also for environments, etc.).
Last edited on
Topic archived. No new replies allowed.