Thursday, January 19, 2012

Revisiting SDL and C++, a Log Class

Hello again, while I feel like I'm making progress with Nick Gravelyn's Tile Engine series in C#, I also want to brush up on my SDL/C++ programming, as I feel like that would be the most applicable to professional game studios. Therefore, I have decided to focus on making a simple shmup (shoot 'em up) while also working on the Rpg in C#. This new project will be in C++ using SDL (if you are unfamiliar with SDL, please take the time to read up at lazyfoo.net). It will be in the classic 2D style that you would have seen in an old SNES game.

I am very excited about this new venture and I anticipate this will help me complete one of my goals for this new year, actually finishing a game. By the way, for those of you wondering, I do plan to return to Prospectus in the future. Let's begin our new project with a basic overview. I am going to do some basic game framework coding in the beginning that can be reused in other game projects as well. I am following stayscrisp's tutorials over at dreamincode.net. After getting through the first 4 tutorials, we find that we have a basic game up and running with basic game state management, which will benefit us later on when we don't have to write that code after the game is done. I have followed his tutorials pretty much to the letter until this point and will quickly venture off and write my own code soon.

The first feature that I'm going to add to this basic game engine is the ability to have a global logging system that can be used to give feedback from the game via a log.txt file. This gives us a great debugging tool from the start because we can use this to monitor our game's activity while it is running. To start, we need a Logger.h added to our project. Here is the code I wrote for this class:

#ifndef Logger_H
#define Logger_H

#include <fstream>

class Logger {
    public:
        static Logger* Instance();
        bool open(const char* file);
        void log(int level, const char* message);
        bool close();
        
    private:
        Logger() {};
        Logger(Logger const&) {};
        Logger& operator=(Logger const&) {};
        static Logger* m_Instance;
        std::ofstream logFile;
};

#endif

You can see that we are making this class a singleton by creating an Instance() method that is static and returns a pointer to the Logger class. This will make sure that there is only one instance of this class at any one time in our game. The open() and close() methods are straight forward; they open and close the log file. The log() method is also very basic; it takes an integer to define the warning level of the message, and a string (const char*) for the actual message of the particular log entry.

Next up we have the Logger.cpp file to add to our project:

#include <iostream>
#include "Logger.h"

// Global static pointer used to ensure a single instance of the class
Logger* Logger::m_Instance = NULL;
    
// This function is called to create an instance of the class.
// By calling the private constructor through this method, we
// ensure that there is only one instance of this class at any point.
Logger* Logger::Instance() {
    if (!m_Instance) 
        m_Instance = new Logger;
        
    return m_Instance;
}

bool Logger::open(const char* file) {
    logFile.open(file);
}

void Logger::log(int level, const char* message) {
    switch (level) {
        case 0:
            logFile << "Debug - " << __DATE__ << " " << __TIME__ << " | " << message << std::endl;
            break;
        case 1:
            logFile << "Warning - " << __DATE__ << " " << __TIME__ << " | "  << message << std::endl;
            break;
        case 2:
            logFile << "Critical - " << __DATE__ << " " << __TIME__ << " | "  << message << std::endl;
            break;
        case 3:
            logFile << "Fatal - " << __DATE__ << " " << __TIME__ << " | "  << message << std::endl;
            break;
        default:
            logFile << __DATE__ << " " << __TIME__ << " | " << message << std::endl;
            break;
        }            
}

bool Logger::close() {
    logFile.close();
}

You can see that we create an instance of the class in the Instance() method. We also open and close the file in the open() and close() methods. The log() method uses a switch statement to determine which message to output, based on the level that was passed to the method. The __DATE__ and __TIME__ macros are a great addition as well, because they show us what time stamp the message was created at. And that's it! The Logger class is complete. Now to use it anywhere in the game all we have to do is make sure we include the header file in the .cpp file we want to use the Logger class, then call the log() method like so:

Logger::Instance()->log(-1, "----------------Game Initialized-------------\n");

or...
Logger::Instance()->log(-1, "PlayState has been loaded");

or...
Logger::Instance()->log(3, "Game failed to start, Quitting game now.");

Thanks everyone for reading and make sure to check back often as I will be making progress updates and simple informative posts such as this one in the future.

1 comment:

  1. Ahmm... __TIME__ and __DATE__ will get only a timestamp of the compilation time and date. So, every time you use __TIME__ you will get the same string: the time, where the binary was last build. Same with __DATE__

    Or am I wrong?

    ReplyDelete