Snapshot:
Pending:
▪ Game states
▪ Score, lives calculation
▪ Monster AI
▪ Sound effects & background music
▪ More levels
Source Code:
/**************************************************************
* Pacman C++ *
* Written by Sunit Maharana (c) 2011 - 2012 *
* http://sunitdevdiary.blogspot.com *
**************************************************************
* This source code is released under the The GNU *
* General Public License (GPL). It is free and is *
* distributed in the hope that it will be useful, *
* you can redistribute it and/or modify it under the *
* terms of the GNU General Public License. *
**************************************************************/
// Allegro library
#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
//Screen resolution
#define SCREEN_W 800
#define SCREEN_H 600
#define TILE_SIZE 32
// Game fps
#define FPS 60
// Direction
#define STAND 0
#define LEFT -1
#define RIGHT 1
#define UP -2
#define DOWN 2
// Game input keys
enum Keyboard {KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ESCAPE};
bool keyboard[5] = {false,false,false,false,false};
// Types of objects in the game
enum ObjectType {FOOD, BLOCK};
// Character Object Class
class CharacterObject
{
public:
float posX; // X coordinate of the Character Object
float posY; // Y coordinate of the Character Object
float speed; // Speed of the Character Object
float angle; // Angle of rotation of Character Object
int direction; // Direction [0 -> STAND, -1 -> LEFT, 1 -> RIGHT, -2 -> UP, 2 -> DOWN]
int spritesheetWidth; // Width of the sprite sheet
int spritesheetHeight; // Height of the sprite sheet
int totalFrame; // Total number of frames
int currentFrame; // Current frame counter
int frameWidth; // Width of the selected region from the sprite sheet
int frameHeight; // Height of the selected region from the sprite sheet
int frameDelay; // Frame delay in between animation sequence
int frameDelayCounter; // Frame delay counter
ALLEGRO_BITMAP *image; // ALLEGRO_BITMAP to hold the sprite sheet
// Class constructor
CharacterObject()
{
posX = 0;
posY = 0;
speed = 0;
direction = 0;
angle = 0;
spritesheetWidth = 0;
spritesheetHeight = 0;
totalFrame= 0;
currentFrame = 0;
frameWidth = 0;
frameHeight = 0;
frameDelay = 0;
frameDelayCounter = 0;
image = NULL;
}
// Method To Initialize The CharacterObject
void InitObject()
{
// Returns the width of the sprite sheet
spritesheetWidth = al_get_bitmap_width(image);
// Returns the height of the sprite sheet
spritesheetHeight = al_get_bitmap_height(image);
// Calculation to find out individual frame width
frameWidth = spritesheetWidth / totalFrame;
// Calculation to find out individual frame height
frameHeight = spritesheetHeight;
// Makes the background of the sprite sheet transparent
al_convert_mask_to_alpha(image,al_map_rgb(255, 0, 255));
}
// Method to handle drawing logic of the Character Object
void DrawObject(ALLEGRO_DISPLAY *screen)
{
// X coordinate calculation of selected frame/region
int currentFrameX = currentFrame * frameWidth;
// Y coordinate of selected frame/region
int currentFrameY = 0;
// Empty bitmap to store rotated image
ALLEGRO_BITMAP *rotated = NULL;
// Creating the new bitmap with width and height same as the selected frame/region
rotated = al_create_bitmap(frameWidth, frameHeight);
// New bitmap selected for drawing instead of backbuffer
al_set_target_bitmap(rotated);
// Particular region/frame is selected and drawn on to the new bitmap
al_draw_bitmap_region(image, currentFrameX, currentFrameY, frameWidth, frameHeight, 0, 0, 0);
// Drawing target changed back to backbuffer of the screen
al_set_target_bitmap(al_get_backbuffer(screen));
/* Logic to handle the rotation of the sprite as per direction
0 degree is 0 radian
90 degree is pi / 2
180 degree is pi
270 degree is 1.5 * pi
360 degree is 2 * pi
Check radian and degree conversion chart
*/
switch(direction)
{
case STAND:
angle = angle; // No rotation
break;
case LEFT:
angle = ALLEGRO_PI; // 180 degree rotation
break;
case RIGHT:
angle = 0 ; // 0 degree rotation
break;
case UP:
angle = ALLEGRO_PI * 1.5; // 270 degree rotation
break;
case DOWN:
angle = ALLEGRO_PI / 2; // 90 degree rotation
break;
}
// Rotate the new bitmap 'rotated' holding our selected region of the sprite sheet
// Draw it on to the backbuffer
al_draw_rotated_bitmap(rotated, frameWidth / 2, frameHeight / 2, posX, posY, angle, 0);
// Free resoruce after using ALLEGRO_BITMAP 'rotated'
al_destroy_bitmap(rotated);
// Logic to handle frame delay in animation sequence
if(++frameDelayCounter >= frameDelay)
{
// Frame animation sequence logic
// Increment frame & reset frame counter to 0 after traversing through all frames
if(++currentFrame >= totalFrame)currentFrame = 0;
// If Character Object is standing, only draw the first frame of the sprite sheet
if(direction == STAND){currentFrame = 0;frameDelayCounter = frameDelay;}
// Reset frame delay counter to 0, after it has completed its delay cycle
frameDelayCounter = 0;
}
}
// Method to handle movement, direction and collision logic of Character Object
void UpdateLogic()
{
//Update object direction as per keyPress
if(keyboard[KEY_UP])direction = -2;
if(keyboard[KEY_DOWN])direction = 2;
if(keyboard[KEY_LEFT])direction = -1;
if(keyboard[KEY_RIGHT])direction = 1;
//Update object speed as per direction
switch(direction)
{
case LEFT:
posX = posX - speed; // Move negative to x-axis
break;
case RIGHT:
posX = posX + speed; // Move along the x-axis
break;
case UP:
posY = posY - speed; // Move negative to y-axis
break;
case DOWN:
posY = posY + speed; // Move along the y-axis
break;
case STAND: // No update in position
break;
}
//Check for collision [Object moving out of the screen]
if(posX>=SCREEN_W)posX=10; // If Character Object position outside right of the screen
if(posY>=SCREEN_H)posY=10; // If Character Object position outside bottom of the screen
if(posX<=0)posX=SCREEN_W; // If Character Object position outside the left of screen
if(posY<=0)posY=SCREEN_H; // If Character Object position outsided the top of the
}
//Method to handle collision
template <class T>
void CollisionLogic(T &object)
{
if(this->posX + 10 > (object.posX - object.boundedBoxWidth/2) &&
this->posX - 10 < (object.posX + object.boundedBoxWidth + object.boundedBoxWidth/2) &&
this->posY + 10 > (object.posY - object.boundedBoxHeight/2) &&
this->posY - 10 < (object.posY + object.boundedBoxHeight + object.boundedBoxHeight/2 ))
{
switch(object.type)
{
case BLOCK:
{
switch(direction)
{
case LEFT:
posX = posX + 2;
break;
case RIGHT:
posX = posX - 2;
break;
case UP:
posY = posY + 2;
break;
case DOWN:
posY= posY - 2;
break;
}
direction = STAND; // Collision occurs, change direction to stand
break;
}
case FOOD:
{
object.alive = false;
break;
}
}
}
}
};
class GameObject
{
public:
float posX; // X coordinate of the Game Object
float posY; // Y coordinate of the Game Object
int type; // Type of Game Object
bool alive; // Boolean flag to check if Game Object is alive
int boundedBoxWidth; // Bounded box width for collision
int boundedBoxHeight; // Bounded box height for collision
ALLEGRO_BITMAP *image; // ALLEGRO_BITMAP to hold the image
GameObject()
{
posX = 0;
posY = 0;
alive = true;
boundedBoxWidth = 0;
boundedBoxHeight = 0;
image = NULL;
}
// Method To Initialize The GameObject
void InitObject(ALLEGRO_BITMAP *image, float posX, float posY, int type)
{
this->image = image;
this->posX = posX;
this->posY = posY;
this->type = type;
al_convert_mask_to_alpha(this->image, al_map_rgb(255, 0, 255));
boundedBoxWidth = al_get_bitmap_width(image);
boundedBoxHeight = al_get_bitmap_height(image);
}
// Method to draw the GameObject
void DrawObject()
{
if(alive)
al_draw_bitmap(image, posX, posY, 0);
}
};
// GameManager class
class GameManager
{
public:
ALLEGRO_FILE *levelData; // ALLEGRO_FILE to operate external file
ALLEGRO_BITMAP *foodSprite, *blockSprite; // ALLEGRO_BITMAP to hold food sprite, level block sprite
CharacterObject Pacman; // Pacman object creation of class CharacteObject
GameObject *gameObject; // Pointer to GameObject
int gameObjectCount; // Variable to hold total count of Game Object's in a level
int levelGrid[SCREEN_H / TILE_SIZE][SCREEN_W / TILE_SIZE]; // 2 dimensional array to hold level data from a external file
// Method to Initialize Level
void InitLevel()
{
Pacman.image = al_load_bitmap("Pacman_Sprite_Sheet.PNG"); // Loading pacman sprite sheet
Pacman.posX = SCREEN_W / 2; // Starting x-coordinate of pacman on the screen
Pacman.posY = SCREEN_H / 2; // Starting y-coordinate of pacman on the screen
Pacman.speed = 2.0; // Movement speed of pacman
Pacman.totalFrame = 2; // Total number of pacman animation frames in the sprite sheet
Pacman.frameDelay = 7; // Speed of pacman animation
Pacman.InitObject(); // Initialize pacman
gameObject = new GameObject[(SCREEN_W / TILE_SIZE) * (SCREEN_H / TILE_SIZE)];
gameObjectCount = 0;
foodSprite = al_load_bitmap("food.png");
blockSprite = al_load_bitmap("block.png");
levelData = al_fopen("level_1.data","r");
// Loading and initializing Level & Game Objects
for(int row=0;row < SCREEN_H / TILE_SIZE;row++)
{
for(int column=0;column < SCREEN_W / TILE_SIZE;column++)
{
levelGrid[row][column] = al_fgetc(levelData);
switch(levelGrid[row][column])
{
case 10:
column--;
break;
case 49:
gameObject[gameObjectCount].InitObject(blockSprite, column * TILE_SIZE, row * TILE_SIZE, BLOCK);
gameObjectCount++;
break;
case 48:
gameObject[gameObjectCount].InitObject(foodSprite, column * TILE_SIZE, row * TILE_SIZE, FOOD);
gameObjectCount++;
break;
}
}
}
}
void DrawLevel(ALLEGRO_DISPLAY *screen)
{
// Draw level objects
for(int loopCounter = 0; loopCounter < gameObjectCount; loopCounter++)
{
gameObject[loopCounter].DrawObject();
}
// Draw pacman Character Object
Pacman.DrawObject(screen);
}
void UpdateLogic()
{
// Update pacman Character Object logic
Pacman.UpdateLogic();
// Update pacman collision check
for(int loopCounter = 0; loopCounter < gameObjectCount; loopCounter++)
{
Pacman.CollisionLogic(gameObject[loopCounter]);
}
}
// Method to update keypress events
void KeyboardEventUpdate(ALLEGRO_EVENT &event, bool flag)
{
switch(event.keyboard.keycode)
{
case ALLEGRO_KEY_UP:
keyboard[KEY_UP] = flag;
break;
case ALLEGRO_KEY_DOWN:
keyboard[KEY_DOWN] = flag;
break;
case ALLEGRO_KEY_LEFT:
keyboard[KEY_LEFT] = flag;
break;
case ALLEGRO_KEY_RIGHT:
keyboard[KEY_RIGHT] = flag;
break;
case ALLEGRO_KEY_ESCAPE:
keyboard[KEY_ESCAPE] = flag;
break;
}
}
};
int main()
{
al_init(); // Start Allegro
ALLEGRO_DISPLAY *screen = NULL; // Main display
ALLEGRO_EVENT_QUEUE *event_queue = NULL; // Event queue to maintain events
ALLEGRO_TIMER *timer = NULL; // Timer to control game fps
// Initializing screen, event queue and timer
screen = al_create_display(SCREEN_W, SCREEN_H);
event_queue = al_create_event_queue();
timer = al_create_timer(1.0/FPS); // Timer set to tick at 60 frames per second
al_init_image_addon(); // Initialize Allegro image addon
al_install_keyboard(); // Initialize Allegro keyboard
al_start_timer(timer); // Start timer
// Register event sources to be handled in the event queue
al_register_event_source(event_queue,al_get_display_event_source(screen));
al_register_event_source(event_queue,al_get_timer_event_source(timer));
al_register_event_source(event_queue, al_get_keyboard_event_source());
GameManager gameManager; // Object creation
gameManager.InitLevel(); // Function call
//Main game loop
while(!keyboard[KEY_ESCAPE]) // Run while escape key is not pressed
{
ALLEGRO_EVENT event; // ALLEGRO_EVENT to hold events
al_wait_for_event(event_queue,&event); // Wait for events to occur
// Display handler
// Draw if timer counter has incremented and there are no events to handle
// and event queue is empty
if(event.type == ALLEGRO_EVENT_TIMER && al_is_event_queue_empty(event_queue))
{
gameManager.DrawLevel(screen);
al_flip_display(); // Display backbuffer on to the main screen
al_clear_to_color(al_map_rgb(0, 0, 0)); // Clear display to black
}
//Event handlers
if (event.type == ALLEGRO_EVENT_TIMER)
{
gameManager.UpdateLogic(); // Function call
}
else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)
{
break; // End game loop on closing display window
}
else if(event.type == ALLEGRO_EVENT_KEY_DOWN)
{
gameManager.KeyboardEventUpdate(event, true); // Update routine on key press
}
else if(event.type == ALLEGRO_EVENT_KEY_UP)
{
gameManager.KeyboardEventUpdate(event, false); // Update routine on key release
}
} // End of Game Loop
al_destroy_display(screen); // Destroy display
return 0;
}
Required data to run the game:
▪ Pacman_Sprite_Sheet.png
▪ block.png
▪ food.png
▪ level_1.data
1111111111111111111111111
1 1
1 00000000000000000000001
1 011111111 0011111111 01
1 01 00 1 01
1 01 00000000000000001 01
1 01 011111 0011111 01 01
1 01 01 00 1 01 01
1 0 0 0000000000 0 01
1 00000000000000000000001
1 01 01 00000000001 01 01
1 01 011111 0011111 01 01
1 01 0 00 01 01
1 01 00000000000000001 01
1 011111111 0011111111 01
1 0 00 01
1 00000000000000000000001
1111111111111111111111111