Thursday, February 27, 2014

Squared Game Engine High-level Overview



Developing a game engine can be a lot of work. Over the years, I’ve developed quite a few game engines, and I’ve learned a lot of lessons doing it. I want to share some of those lessons by giving a very high level overview of my current game engine.

When developing my current game engine, I wanted it to be modular and expandable. Because of this, I decided to make the engine into separate libraries and I tried to limit dependencies between libraries to the most minimal extent possible. This methodology also carried over to the way I designed my individual classes. Limiting dependencies is one of the most important things that I feel is needed to write clean maintainable code. Below is a diagram of the main libraries that I created for the Squared Game Engine. Note that these are libraries and not classes and that the graph shows the dependencies.



I keep to this strictly. The code in DSquaredCore knows nothing about SquaredDPublic and should never directly or indirectly try to access anything inside of it. DSquaredNet “knows” about DSquaredLogic, but DSquaredLogic should never try to access DSquaredNet. Keeping these strict dependencies has helped my quickly spot design issues in other parts of the code. Sometimes code needs to be made quickly to get things done, but fooling around with the dependencies is unacceptable.

But what is in all of those libraries? Here’s a quick overview.

SquaredDPublic
The purpose of classes and functions under this namespace and library are to provide a multiplatform framework from which the game can be built from. For platform support and graphics, I have separate libraries for each platform that link with the rest of the application. For example, I currently have two WindowsDirectX9 and a WindowsDirectX11. When one of those libraries is linked with the application, the other classes in this namespace will use them to provide graphics and input support for the application. Initially, this namespace was just a bunch of utility functions, but it’s been morphing into the basic game framework. For the 3D engine, the code in SquaredDPublic has been locked. The next version is being built alongside an experimental 2D engine. In the future, I want SquaredDPublic to be able to be the base for any type of game. Currently this code is closed source, but I in the future I want to release the source code of it with demos. That’s why I named it SquaredDPublic.

DSquaredCore
I developed DSquaredCore to be the core components of a game and able to exist with or without graphics, sound, and user input. DSquaredCore provides functionality but it was meant as a hub to which other modules should link to. I did this because I wanted to be able to build a graphical game, and at the same time be able to build a console-based server only version. The code in this library includes the game state manager, the game loop, and the component-based entity system. The code in this library was developed to work with the code in SquaredDPublic, but it can work with any graphics engine as long as the proper “bridge” code is written for it.

DSquaredLogic
DSquaredLogic as its name implies has the code that includes most of the simulation logic. It provides classes for physics and collision detection (via NVidia PhysX), AI, and the base character controllers. The classes in this library are focused on the game logic needed once gameplay starts. It also has some basic code for initializing levels. The code was written with expansion in mind so there are many places for linking in extra features. The game uses component-based entities so AI and physics components are defined in this library.

DSquaredNet
DSquaredNet uses RakNet4 to provide networking capabilities to the game. It has classes that plug into DSquaredLogic to handle network connections and synchronization. One major design decision that I made early on was I wanted to be able to make a server only console app without graphics using only DSquaredCore, DSquaredLogic, and DSquaredNet.

DSquaredGame
DSquaredGame provides functionality to link the engines core systems with SquaredDPublic to make a game with graphics, sound, and user input. DSquaredGame provides all of the graphics entity components. DSquaredGame was designed for 3D games and doesn't provide any 2D support apart from what's provided in SquaredDPublic. The GUI system is totally separate and interfaces with SquaredDPublic directly to to access the 2D functions. 

SquaredDGame
The code in this library started out as game specific code. Over time I saw that it could be reused so I extracted the code into it's own library. The code in this library helps to bind everything together. This code is not needed when making a game using the engine, but it greatly simplifies the task of making 3D multiplayer games. Game specific code under normal circumstances will only need to access the code in this library. Game Specific Code

There are some other things that in the code that I didn't mention, but these are the main parts. From this, you can get a basic understand of the engine’s modular design. 

Thursday, February 13, 2014

Fun with Modern C++ and Smart Pointers

C++ has changed over the years. With the new C++11 standard, we now have smart pointers, but what are they and how are they different from normal pointers.

Raw Pointer Primer
There are two basic ways to create a variable in C++: stack variables and heap allocations. Stack variables are defined inside a certain scope and as long as you're in that scope, the variable will exist. Heap allocations (dynamically allocated memory), variables typically declared with new or malloc, aren't not defined within a scope and will exist until the memory is freed.

Here's an example:

void foo()
{
    // Object A of class CSomeClass has been declared inside the scope of foo
    CSomeClass A;

    // do some stuff
    ....

    // you can even call other functions and use A as a parameter
    Func1(A); // This could be pass by value or pass by reference depending on Func1's declaration

    Func2(&A); // Passes a pointer to A

    // at the end of this  function, the scope will end and A will automatically be destroyed
}
Now with this function, every time another function calls foo, A will be created and then destroyed when the function exits. Not bad right. What about this?

void foo()
{
    // Object A of class CSomeClass has been declared inside the scope of foo
    CSomeClass *A = new CSomeClass; 

    // do some stuff
    ....

    // you can even call other functions and use A as a parameter
    Func1(*A); // This could be pass by value or pass by reference depending on Func1's declaration

    Func2(A); // Passes pointer A of CSomeClass (Edited)

    // MEMORY LEAK
    // at the end of this  function, the scope will end, but A was created on the heap
    // delete should be called here
}
So with dynamic memory allocations, you must free the memory. So why you might ask do we even need dynamic memory allocations? Well one, to declare variables on the stack, you need to know exactly what you'll need at compile time. If you want to be able to create arrays of various sizes depending on user input, or if you're making a game and want to load variable amount of resources, you'll need to use dynamic memory allocations.
Take this example:

int num_students;

// First get the number of students in the class
std::cout << "How many students are in the class?"
std::cin >> num_students;

// Create a dynamic students array
CStudent *student_array = new CStudent[num_students];

// Do some stuff with the data
....

// call the array version of delete to free memory
delete [] student_array;

In the previous situation, you must use dynamic memory because the size of the array is determined by the user. How can smart pointers help?

Smart Pointers
(chief reference: Smart Pointers (Modern C++) on MSDN)

Smart pointers allow you to create dynamic memory allocations but defined them inside a scope or an owner. This way, when the owner goes out of focus, the data will be automatically deleted. Smart pointers have been implemented using templates and to use them, you must include the header . Smart pointers are in the std namespace. I will only discuss unique_ptr's here. So how do you create a unique_ptr?


std::unique_ptr apples(new Base(L"apples")); // Where Base is the class type

You create a unique_ptr like a typical template and then pass the raw pointer to initialize the variable. After that, you can use the unique_ptr, just as you would any other pointer.

class Base
{
    public:
        Base(const std::wstring &string)
        :m_string(string)
        {
        }

        virtual void Display() const
        {
            std::wcout << L"Base:" << m_string << std::endl;
        }

    private:
        std::wstring m_string;
};

int main()
{
    // declare some unique_ptrs. This pointers can have only one owner and cannot be copied. Only moved.
    std::unique_ptr<Base> apples(new Base(L"apples"));

    apples->Display();
}

unique_ptr's can also be passed to other functions, but you must pass by reference. Passing by value will result in a compiler error. With unique_ptr's, only one can own the pointer, but if you pass by value you will make a copy of the unique_ptr which in essence makes two unique_ptr's that own the same block of memory. You can also use unique_ptr's with derived class and virtual functions and get the typical C++ behavior.
class Base
{
    public:
        Base(const std::wstring &string)
        :m_string(string)
        {
        }

        virtual void Display() const
        {
            std::wcout << L"Base:" << m_string << std::endl;
        }

    private:
        std::wstring m_string;
};

class Derived : public Base
{
    public:
        Derived(const std::wstring &string)
        :Base(string)
        {
        }

        virtual void Display() const
        {
            std::wcout << L"Derived:::";
            __super::Display(); // __super is MS specific. Others should use Base::Display();
        }
};

int main()
{
    // declare some unique_ptrs. This pointers can have only one owner and cannot be copied. Only moved.
    std::unique_ptr<Base> apples(new Base(L"apples"));
    std::unique_ptr<Base> oranges(new Derived(L"oranges"));

    apples->Display();
    oranges->Display();
}

This is very useful when dealing with vectors as the next example show.
std::vector <std::unique_ptr<Base>> test_vector;

test_vector.push_back(std::unique_ptr<Base>(new Base(L"apples")));
test_vector.push_back(std::unique_ptr<Base>(new Derived(L"oranges")));

In the above example, you can use the vector of unique_ptr's in the same way you would if it was a vector of raw Base pointers, but there is one nice benefit. If this vector had raw pointers, you'd have to make sure you manual free the pointers before you clear the vector or erase an element, but with unique_ptr's, all of that is handled for you.

Conclusion
I hope this information was helpful. I am in no way an expert so if you anyone sees something glaring that I missed, feel free to leave a "kind" comment. If you would like to know more about smart pointers, I suggest checking out the like on msdn.


Friday, February 7, 2014

Update

It looks like I'll be moving back to Maryland in the middle of April. I'm serious about going back into the technology industry either doing information security again or going into programming. I'd like to find a permanent position because I'm married and have family responsibilities, but I'm so serious about programming that I'll also do consulting and contract work.

I've done extensive work in C++ using Direct X. If you check out my projects page, you can see videos of two of my latest 3D engines. Yes Genesis Seed and Auxnet use different 3D engines. I've also started learning Android programming. I won't become an expert, but I'm giving myself 3 weeks to learn the basics, and then I'll move on to the NDK and OpenGL ES. I'll going to use the NDK instead of Java because I want to use my current C++ code to make a multiplatform 2D game engine. It looks like my partners need more time to get things rolling with Auxnet so I'll use the downtime to richly increase my skills in other areas.

I've also decided to purchase the squaredprogramming.com domain. I'll post my progress often to my blog there. You can follow me on Google+ or YouTube for updates.

Wednesday, February 5, 2014

Latest Updates - Starting Android Development

Today I've started looking into Java and Android development. I want to focus on learning the basics of Android programming through Java first, but I also want to improve my overall Java programming skills. So today I downloaded the Android SDK and I hope to try out some online tutorials. If necessary, later I may get a book on the subject as well. Once I get basic Android programming using Java down, I'll move on to the NDK and Android programming using C++. I've already built a simple tile based 2D graphics system for Windows, I'd like to port it over to Android. That's my long term goal. Here's a screen shot of the Windows version of the 2D graphics engine.


Don't criticize my art skills too much. I'm a programmer, not an artist. I'll provide updates on my progress. This is just a small side project that I'll be working on until things pick up again with Auxnet: Battlegrounds so I can't make any promises about how fast I'll be able to get it done.

Sunday, February 2, 2014

Very Simple Base64 Reader

I've written this very simple Base64 reader. I wrote this as part of my loader for Tiled Map Editor files (see mapeditor.org). The code is very simple, but it works. The out_binary_data parameter is unsigned char * for simplicity as the code works in 8 bit chunks. Tiled actually uses 32 bit int variables so the pointer will need to be cast to int *. If you have anymore questions about the code, just leave a comment.


// converts a 6 bit value into the Base64 ASCII equivalent
unsigned char ASCIITo_6BitVal(char ch)
{
 if((ch >= 'A') && (ch <= 'Z')) return ch - 'A';
 else if((ch >= 'a') && (ch <= 'z')) return (ch - 'a') + 26;
 else if((ch >= '0') && (ch <= '9')) return (ch - '0') + 52;
 else if(ch == '+') return 62;
 else if(ch == '/') return 63;
 else if(ch == '=') return 64;
 else return 65;
}

// converts binary data to Base64, If the data will be used on a platform with a different endianness, the
// data 32 bit integers or the decoder will not be able to switch the endianness automatically
int Base64Binary(const char *in_base64_data, unsigned int in_base64_data_length,
                 unsigned char *out_binary_data, unsigned int in_binary_buffer_length, unsigned int &out_binary_data_length)
{
 out_binary_data_length = 0;

 for(unsigned int i = 0; i < in_base64_data_length; i += 4)
 {
  bool end_found = false;
  unsigned char b64_byte1 = ASCIITo_6BitVal(in_base64_data[i]);
  if(b64_byte1 > 63) return 1; // stop processing
  unsigned char b64_byte2 = ASCIITo_6BitVal(in_base64_data[i+1]);
  if(b64_byte2 > 63) return -1;
  unsigned char b64_byte3 = ASCIITo_6BitVal(in_base64_data[i+2]);
  if(b64_byte3 > 64) return -1; // this could be the end of the stream so it could also be '='
  unsigned char b64_byte4 = ASCIITo_6BitVal(in_base64_data[i+3]);
  if(b64_byte4 > 64) return -1; // this could be the end of the stream so it could also be '='

  int size = 3;
  // check for the end
  if(b64_byte4 == 64)
  {
   size = 2;
   end_found = true;
   b64_byte4 = 0;
  }
  if(b64_byte3 == 64)
  {
   size = 1;
   end_found = true;
   b64_byte3 = 0;
  }

  if(in_binary_buffer_length < (out_binary_data_length + size))
  {
   // the buffer is too small
   return -2;
  }

  out_binary_data[out_binary_data_length + 0] = (b64_byte1 << 2) | ((0x30 & b64_byte2) >> 4);
  if(size > 1)out_binary_data[out_binary_data_length + 1] = ((0xF & b64_byte2) << 4) | ((0x3C & b64_byte3) >> 2);
  if(size > 2)out_binary_data[out_binary_data_length + 2] = ((0x3 & b64_byte3) << 6) | b64_byte4;

  out_binary_data_length += size;

  if(end_found) break;
 }

 return 0;
}