Wednesday, April 24, 2013

KillingSoftly- A Simple Game Development Using SDL / C++

Hi.. Today i am going to post some thing related to game development using SDL and C++. Simple DirectMedia Layer (SDL) is a cross-platform, free and open source multimedia library written in C that presents a simple interface to various platforms' graphics, sound, and input devices.

In this post I am going to develop, a basic game called killingSoftly using SDL. Let us assume that killingSoftly game has following rules.

1. There are four Dayaans(witch) and they enter into gaming area randomly.
2. There is one Warrior at centre of gaming area, who can be controlled by arrow keys.
3. If Warrior is overlapped with Dayaan then Dayaan will die and your score will increase.
4. To start new game you have to press 'n' key.
5. Whenever you start new game you can see your previous game score.
6. Before quitting game, press 'n' to see the score then quit.

Watch Video Here: http://www.youtube.com/watch?v=TYRft9dRcqs

You can Download KillingSoftly-0.1, A game which is developed by me DOWNLOAD HERE.



 The GUI of this game looks like as follows

Note: The aim this post is not to create a game (you may not like this game!) but to help other developers who are interested in learning game development programming using SDL and C++.

If you observe/ read the comments of the code which i wrote for the development of  KillingSoftly you will come to know the following things.

1. How to initialize SDL components for game development.
2. How to load different objects of game.
3. How to control game objects based on events.
4. How to load TTF's and How to display text in gaming environment.
5. How to play audio files in the background etc.

 // Filename: KillingSoftly.cpp

#include <string>
#include "SDL/SDL.h"
#include "SDL/SDL_ttf.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_mixer.h"

using namespace std;

// Display Properties
const int DISPLAY_HEIGHT = 480;
const int DISPLAY_WIDTH = 480;
const int DISPLAY_BPP = 32;

// The dimensions of the Dayaan/Warrior objects
const int OBJECT_HEIGHT = 30;
const int OBJECT_WIDTH = 30;

// frame rate
const int FRAMES_PER_SEC = 25;

// surfaces for various objects
SDL_Surface *Warrior = NULL;
SDL_Surface *Dayaan1 = NULL,*Dayaan2 = NULL,*Dayaan3 = NULL,*Dayaan4 = NULL;
SDL_Surface *display = NULL;
SDL_Surface *scoreDisplay = NULL;

// font
TTF_Font *font = NULL;

// event variable
SDL_Event event;

//color of the font
SDL_Color text_Color = { 0, 0, 255};

// Varibales to caluculate score
int targets =4;
int score=0;

//sound effects used
Mix_Chunk *KillSound = NULL;

// Objects that will move on the display
class Object
{
    public:
    // x, y offsets of the objects
    int x, y, x1, y1, x2, y2, x3, y3, x4, y4;

    // Speed of the warrior object
    int x_speed, y_speed;

    public:
    // Initializes the variables
    Object();

    // Takes key presses and adjusts warrior speed
    void handle_warrior();

    // Moves the warrior
    void move_warrior();

    // Moves Dayaans and updates scoring
    void move_Dayaans();

    // Shows All the objects on the display
    void show_All_objects();
};

// Clock class
class Clock
{
    private:
    // The clock time when clock started
     int ticking_start;

    // The ticks stored when the clock paused
    int pausedTicks;
    int ticks_paused;

    // Clock status
    bool ispaused;
    bool isstarted;

    public:
    // Initializes variables
    Clock();

    // The various clock actions
    void start();
    void stop();
    void pause();
    void unpause();

   // Checking status of clock
    bool is_started();
    bool is_paused();

    // Gets the Clock's time
    int get_No_of_ticks();
 
};


// fucntion to load bliting surface
void loading_surface( int x, int y, SDL_Surface* source, SDL_Surface* dest, SDL_Rect* area = NULL )
{
    // Holding offsets
    SDL_Rect offset;

    // Geting offsets
    offset.x = x;
    offset.y = y;

    // Bliting using SDL_BlitSurface
    SDL_BlitSurface( source, area, dest, &offset );
}


// Function to load optimised images/objects
SDL_Surface *load_optimized_image( std::string filename )
{
    // optimised surface
    SDL_Surface* optiImage = NULL;

    // loaded image
    SDL_Surface* loadImage = NULL;

    // loading image
    loadImage = IMG_Load( filename.c_str() );

    if( loadImage != NULL )
    {
        // creating optimised surface
        optiImage = SDL_DisplayFormat( loadImage );

        // Free old surface
        SDL_FreeSurface( loadImage );

        // Checking optimised surface
        if( optiImage != NULL )
        {
            //Colouring key surface area
            SDL_SetColorKey( optiImage, SDL_SRCCOLORKEY, SDL_MapRGB( optiImage->format, 0, 0xFF, 0xFF ) );
        }
    }

    // Returning optimized surface
    return optiImage;
}

// Function to load required files
bool loading()
{
    // Loading the Warrior/Dayaans images
    Warrior = load_optimized_image( "Warrior.bmp" );
    Dayaan1 = load_optimized_image( "Dayaan.bmp" );
    Dayaan2 = load_optimized_image( "Dayaan.bmp" );
    Dayaan3 = load_optimized_image( "Dayaan.bmp" );
    Dayaan4 = load_optimized_image( "Dayaan.bmp" );

    // Checking for problems in loading Warrior/Dayaans images
    if( Warrior == NULL ||  Dayaan1 == NULL || Dayaan2 == NULL || Dayaan3 == NULL || Dayaan4 == NULL)
        return false;
   
    // Loading killing sound effect
    KillSound = Mix_LoadWAV( "Kill.wav" );
   
    // Checking for problem in loading the sound effect
    if( KillSound == NULL )
        return false;

    // Opening font called akshar
    font = TTF_OpenFont( "akshar.ttf", 20 );

    // Checking for error in loading the font
    if( font == NULL)
        return false;

    // If everything is fine returing true
    return true;
}

// Function to initialize SDL sub functions
bool initialize()
{
    // Initialising SDL
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
        return false;

    // Setting up display
    display = SDL_SetVideoMode( DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_BPP, SDL_SWSURFACE );

    // Checking for problems in setting up display
    if( display == NULL )
        return false;

    // Initializing SDL_mixer
    if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 )
        return false;
   
    // Intializing TTF and checking for problems
    if( TTF_Init() == -1 )
        return false;

    // Setting up window Name
    SDL_WM_SetCaption( "Killing Softly", NULL );

    // If everything is fine returning true
    return true;
}

// Function to clean everything
void Do_cleaning()
{
   
    // Removing all surfaces
    SDL_FreeSurface( Warrior );
    SDL_FreeSurface( Dayaan1 );
    SDL_FreeSurface( Dayaan2 );
    SDL_FreeSurface( Dayaan3 );
    SDL_FreeSurface( Dayaan4 );
   
    // Removing font
    TTF_CloseFont( font );

    // Removing sound effect
    Mix_FreeChunk( KillSound );

    // Exiting from SDL_mixer
    Mix_CloseAudio();

    //Exiting from SDL
    SDL_Quit();
}

// Initialising object variables
Object::Object()
{
    // Setting up object positions
    x = DISPLAY_WIDTH/2;
    y = DISPLAY_HEIGHT/2;
    x1 = 0;
    y1 = 0;
    x2 = 0;
    y2 = DISPLAY_HEIGHT-OBJECT_HEIGHT;
    x3 = DISPLAY_WIDTH-OBJECT_WIDTH;
    y3 = 0;
    x4 = DISPLAY_WIDTH-OBJECT_WIDTH;
    y4 = DISPLAY_HEIGHT-OBJECT_HEIGHT;

    // Setting speed of warrior
    x_speed = 0;
    y_speed = 0;
}

// Speeding up warrior with Keys
void Object::handle_warrior()
{
    // If Key pressed
    if( event.type == SDL_KEYDOWN )
    {
        // Changing speed
        switch( event.key.keysym.sym )
        {
            case SDLK_UP: y_speed -= OBJECT_HEIGHT / 5;
                          break;
            case SDLK_DOWN: y_speed += OBJECT_HEIGHT / 5;
                            break;
            case SDLK_LEFT: x_speed -= OBJECT_WIDTH / 5;
                            break;
            case SDLK_RIGHT: x_speed += OBJECT_WIDTH / 5;
                             break;
        }
    }
    // If Key released
    else if( event.type == SDL_KEYUP )
    {
        // Changing speed
        switch( event.key.keysym.sym )
        {
            case SDLK_UP: y_speed += OBJECT_HEIGHT / 5;
                          break;
            case SDLK_DOWN: y_speed -= OBJECT_HEIGHT / 5;
                            break;
            case SDLK_LEFT: x_speed += OBJECT_WIDTH / 5;
                            break;
            case SDLK_RIGHT: x_speed -= OBJECT_WIDTH / 5;
                             break;
        }
    }
}

// Moving warrior
void Object::move_warrior()
{
    // Moving warrior left or right
    x += x_speed;

    // checking Boundaries
    if( ( x < 0 ) || ( x + OBJECT_WIDTH > DISPLAY_WIDTH ) )
    {
        //  Moving warrior back
        x -= x_speed;
    }

    // Moving warrior up or down
    y += y_speed;

    // Checking Boundaries
    if( ( y < 0 ) || ( y + OBJECT_HEIGHT > DISPLAY_HEIGHT ) )
    {
        // Moving warrior back
        y -= y_speed;
    }
}

// Moving Dayaans and updating scoring
void Object::move_Dayaans()
{
    // Moving Dayaan1
    x1 = x1+3;
    if((x1 + OBJECT_WIDTH) > DISPLAY_WIDTH)
       {
        x1 = 0;
        targets++;
       }
    // Dayaan1 killed Score updated
    if(x1==x && y1==y)
      {
        // Playing killing Music
        Mix_PlayChannel( -1, KillSound, 0 );
        x1=0;
        score++;
      }
    // Moving Dayaan2
    y2 = y2-3;
    if((y2 + OBJECT_WIDTH) < 0)
       {
        y2 = DISPLAY_HEIGHT-OBJECT_HEIGHT;
         targets++;
       }
    // Dayaan2 killed Score updated
    if(x2==x && y2==y)
    {
        // Playing killing Music
        Mix_PlayChannel( -1, KillSound, 0 );
        y2 = DISPLAY_HEIGHT-OBJECT_HEIGHT;
        score++;
      }
    // Moving Dayaan3
    y3 = y3+3;
    if((y3 + OBJECT_HEIGHT) > DISPLAY_HEIGHT)
      {
        y3 = 0;
        targets++;
      }
    // Dayaan3 killed Score updated
    if(x3==x && y3==y)
     {
        // Playing killing Music
        Mix_PlayChannel( -1, KillSound, 0 );
        y3=0;
        score++;
     }
    // Moving Dayaan4
    x4 = x4-3;
    if((x4 + OBJECT_WIDTH) < 0)
        {
         x4 = DISPLAY_WIDTH - OBJECT_WIDTH;
         targets++;
         }
    // Dayaan4 killed Score updated
    if(x4==x && y4==y)
      {
         // Playing killing Music
         Mix_PlayChannel( -1, KillSound, 0 );
         x4 = DISPLAY_WIDTH - OBJECT_WIDTH;
         score++;
      }
}

// Showing Dayaans/warrior on display
void Object::show_All_objects()
{
    // Show warrior/dayaans
    loading_surface( x, y, Warrior, display );
    loading_surface( x1, y1, Dayaan1, display );
    loading_surface( x2, y2, Dayaan2, display );
    loading_surface( x3, y3, Dayaan3, display );
    loading_surface( x4, y4, Dayaan4, display );
}

// Intializing clock
Clock::Clock()
{
    // Initialize the variables
    ticking_start = 0;
    ticks_paused = 0;
    ispaused = false;
    isstarted = false;
}

// Starting Clock
void Clock::start()
{
    // Start clock
    isstarted = true;

    // Unpause clock
    ispaused = false;

    // Get the current clock time
    ticking_start = SDL_GetTicks();
}

// Stopping Clock
void Clock::stop()
{
    // Stop clock
    isstarted = false;

    // Unpause clock
    ispaused = false;
}

// Pausing Clock
void Clock::pause()
{
    // If the clock running and not already paused
    if( ( isstarted == true ) && ( ispaused == false ) )
    {
        // Pause clock
        ispaused = true;

        // counting paused ticks
        ticks_paused = SDL_GetTicks() - ticking_start;
    }
}

// unPausing Clock
void Clock::unpause()
{
    // If clock is paused
    if( ispaused == true )
    {
        // Unpause clock
        ispaused = false;

        //Reset clock
        ticking_start = SDL_GetTicks() - ticks_paused;

        // Reset clock
        ticks_paused = 0;
    }
}

// Getting Number of ticks
int Clock::get_No_of_ticks()
{
    // If clock is running
    if( isstarted == true )
    {
        // If clock is paused
        if( ispaused == true )
            return ticks_paused;
        else
            return SDL_GetTicks() - ticking_start;
    }
    // If clock not running
    return 0;
}

// Checking clock started or not
bool Clock::is_started()
{
    return isstarted;
}

// Checking clock paused or not
bool Clock::is_paused()
{
    return ispaused;
}

// Main Function
int main( int argc, char* args[] )
{
    // quit flag to check quitting
    bool quit = false;

    // Object to be used
    Object obj;

    // The frame rate checker
    Clock clk;

    // checking for Initialisation
    if( initialize() == false )
        return 1;

    //Loading necessary files
    if( loading() == false )
        return 1;

    // When user Not quit
    while( quit == false )
    {
        //Start frame clock
        clk.start();

        // Events to handle
        while( SDL_PollEvent( &event ) )
        {
            // Handle events for Display
            obj.handle_warrior();
            if( event.key.keysym.sym == SDLK_n )
                {
                        // Diplaying score and starting new game on pressing 'n' key
                         char line[50];
                         sprintf(line,"You Killed: %d/%d Dayaans::Starting New game ;-)",score, targets);
                         scoreDisplay = TTF_RenderText_Solid( font, line, text_Color );
                         // Sendign score to Display
                         SDL_BlitSurface(scoreDisplay, NULL, display, NULL);
                         // Setting deplay to see score
                         SDL_Delay(5000);
                         // Free the score
                         SDL_FreeSurface( scoreDisplay );
                         SDL_Flip( display );
                         // Setting object positions to orizinal
                         obj.x = DISPLAY_WIDTH/2;
                         obj.y = DISPLAY_HEIGHT/2;
                         obj.x1 = 0;
                         obj.y1 = 0;
                         obj.x2 = 0;
                         obj.y2 = DISPLAY_HEIGHT-OBJECT_HEIGHT;
                         obj.x3 = DISPLAY_WIDTH-OBJECT_WIDTH;
                         obj.y3 = 0;
                         obj.x4 = DISPLAY_WIDTH-OBJECT_WIDTH;
                         obj.y4 = DISPLAY_HEIGHT-OBJECT_HEIGHT;
                         targets =0;
                         score =0;
                }

            // If the user close window
            if( event.type == SDL_QUIT )
                quit = true;
        }

        // Moving warrior
        obj.move_warrior();
        // Moving Dayaans
        obj.move_Dayaans();
        // Filling display with white
        SDL_FillRect( display, &display->clip_rect, SDL_MapRGB( display->format, 0x00, 0x00, 0x00 ) );

        // Showing all objects on display
        obj.show_All_objects();

        // Updating the Display
        if( SDL_Flip( display ) == -1 )
            return 1;

        // Checking frame rate
        if( clk.get_No_of_ticks() < 1000 / FRAMES_PER_SEC )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SEC ) - clk.get_No_of_ticks() );
        }
    }

    // Doing cleaning
    Do_cleaning();

    return 0;
}

To Run KillingSoftly Game:

step-1.  Install required libraries using following command
    >sudo apt-get install libsdl1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev g++
step-2. Download KillingSoftly-0.1.
step-3. Extract using following command
    >tar -xzvf KillingSoftly-0.1.tar.gz
step-4. Move to extracted killingSoftly folder
    >cd KillingSoftly
step-5. To compile execute following command
    >g++ -lSDL -lSDL_image -lSDL_mixer -lSDL_ttf KillingSoftly.cpp
step-6. To run execute following command
   >./a.out


2 comments:

  1. Hey buddy, that's a nice post you got up there. I haven't really tried it but I like the reason you posted it. I'm a C/C++ Programmer but don't often practice it like most people do. Thanx for the heads up.

    ReplyDelete
    Replies
    1. You are Welcome buddy. Sorry for late reply

      Delete