C++ 2D Game Engine (Novice to Novice) - Part 1 - CodeProject

:

Introduction

In this tip series, we will create a small C++ 2D game engine. Along the way, we will create all the necessary tools too. In subsequent tips, we will deal with geometry, drawing, physics, AI, scripting, etc.

Background

I always wanted to create games in C++. So I thought I will learn how to create a basic game engine using C++. In this series, I will explain about it. I am still a novice in this, I am learning as I am doing. I want to share my learning and take any advice, tips and tricks as I learn.

The Tools

For now, the following tools will be used:

Setting Up

Currently all the libraries except the boost libraries are given in the Konark project repo. In the build directory, there is a Visual Studio 12 solution file. In the future, I will migrate to CMake based build system to make it cross platform. To work with it, just download it, open the Visual Studio files.

The folder structure is as follows:

  • include - contains the include files
  • lib - contains the library files (.lib)
  • bin - contains the binaries and the .exe files
  • build - contains the Visual Studio files

Download Boost, and copy the header files into the include directory. Download development libraries of SDL and unzip it. Copy the header files into the include directory, lib files to the lib directory, and the .dll file to the bin directory. Download glew, copy the headers to the include directory, lib files to the lib directory, and the DLL files to the bin folder.

The other libraries are headeronly and supplied with Konark.

Note:  This is just a easy way of setup. If you have your directory structure feel free to include them. In recent future I will be migrating to biicode  for dependency management. In next article I will be using it for the dependency management.

Point2D Class

The basic of the geometry is a point. We will use boost.geometry to define our Point2D.

#include <boost/operators.hpp>
namespace blib {
  namespace geometry {
    namespace bgeom = ::boost::geometry;
    typedef ::blib::math::RowVector3f Vec3f;;

    // 2D Point Type
    class Point2D : public boost::field_operators < Point2D > {
    public:
      typedef CoordinateType ValueType;
      typedef Point2D SelfType;
      typedef boost::mpl::int_ < 2 > Dimensions;

    private:
      std::array<ValueType, Dimensions::value> _coordinates;

    public:
      Point2D( const ValueType aX = 0, const ValueType aY = 0 ) :
        _coordinates({ { aX, aY } }) {}
      void x( const ValueType aX ) {
        _coordinates[ 0 ] = aX;
      }
      void y( const ValueType aY ) {
        _coordinates[ 1 ] = aY;
      }
      ValueType x( ) const {
        return _coordinates[ 0 ];
      }
      ValueType y( ) const {
        return _coordinates[ 1 ];
      }
      ValueType& x( ) {
        return _coordinates[ 0 ];
      }
      ValueType& y( ) {
        return _coordinates[ 1 ];
      }
      // Convert to a Vec3f
      operator Vec3f( ) const {
        const Vec3f ret( _coordinates[ 0 ], _coordinates[ 1 ], 1 );
        return ret;
      }
      ValueType magnitude( ) const {
        const auto ret = std::sqrt( squarMagnitude( ) );
        return ret;
      }
      ValueType squarMagnitude( ) const {
        const auto x = _coordinates[ 0 ];
        const auto y = _coordinates[ 1 ];
        const auto ret = x*x + y*y;
        return ret;
      }
      SelfType& operator+=( SelfType const& aOther ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x + aOther._coordinates[ 0 ];
        y = y + aOther._coordinates[ 1 ];
        return *this;
      }
      SelfType& operator-=( SelfType const& aOther ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x - aOther._coordinates[ 0 ];
        y = y - aOther._coordinates[ 1 ];
        return *this;
      }
      SelfType& operator+=( const ValueType aFactor ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x + aFactor;
        y = y + aFactor;
        return *this;
      }
      SelfType& operator-=( const ValueType aFactor ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x - aFactor;
        y = y - aFactor;
        return *this;
      }
      SelfType& operator*=( const ValueType aFactor ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x * aFactor;
        y = y * aFactor;
        return *this;
      }
      // Factor should be greater than 0
      SelfType& operator/=( const ValueType aFactor ) {
        auto& x = _coordinates[ 0 ];
        auto& y = _coordinates[ 1 ];
        x = x / aFactor;
        y = y / aFactor;
        return *this;
      }
    };
  }
}

Here to make the operator overloading easy we are inheriting it from the boost::field_operator class.

To use our Point<font color="#990000" face="Consolas, Courier New, Courier, mono"><span style="font-size: 14.6666669845581px;">2D </span></font>class with different other algorithms of boost.geometry we need to implement the requirements needed for boost geometry. The class looks as follows:

// Adapting the custom Point2D Class
namespace boost {
  namespace geometry {
    namespace traits {
      // Adapt Point2D to Boost.Geometry
      template<>
      struct tag < ::blib::geometry::Point2D > {
        typedef point_tag type;
      };

      template<>
      struct coordinate_type < ::blib::geometry::Point2D > {
        typedef ::blib::geometry::Point2D::ValueType type;
      };

      template<>
      struct coordinate_system < ::blib::geometry::Point2D > {
        typedef cs::cartesian type;
      };

      template<>
      struct dimension<::blib::geometry::Point2D >
        : boost::mpl::int_ < 2 >
      {};

      template<>
      struct access < ::blib::geometry::Point2D, 0 > {

        static inline ::blib::geometry::CoordinateType get( ::blib::geometry::Point2D const& p ) {
          return p.x( );
        }

        static inline void set( ::blib::geometry::Point2D & p,
                                ::blib::geometry::CoordinateType const& value ) {
          p.x( value );
        }
      };

      template<>
      struct access < ::blib::geometry::Point2D, 1 > {

        static inline ::blib::geometry::CoordinateType get( ::blib::geometry::Point2D const& p ) {
          return p.y( );
        }

        static inline void set( ::blib::geometry::Point2D & p,
                                ::blib::geometry::CoordinateType const& value ) {
          p.y( value );
        }
      };
    }
  }
} // namespace boost::geometry::traits

Now we can use our custom point class with boost algorithms. Here, we have a conversion operator implemented in the point class. This will convert the point into a vector. This will be useful in different transformations. We will deal with them later.

After we have the point class, we can use it to define an ellipse class. An ellipse will have a height, width and a center point. It can also be described as a ring. So, we inherit it from the boost ring.

namespace blib {
  namespace geometry {
    class Ellipse : public bgeom::model::ring < Point2D > {
    public:
      typedef ::boost::geometry::traits::coordinate_type< ::blib::geometry::Point2D >::type ValueType;
      Ellipse( ) :
        _heigth( 0 ),
        _width( 0 ),
        _center( ) {}
      void height( const ValueType  aHeight ) {
        _heigth = aHeight;
      }
      void width( const ValueType  aWidth ) {
        _width = aWidth;
      }
      ValueType height( ) const {
        return _heigth;
      }
      ValueType width( ) const {
        return _width;
      }
      Point2D& center( ) {
        return _center;
      }
    private:
      ValueType _heigth, _width;
      Point2D _center;
    };
  }
}

Now we can specialize the other boost geometries with our 2d Point.

namespace blib {
  namespace geometry {
    typedef bgeom::model::linestring<Point2D> LineString;
    typedef bgeom::model::polygon<Point2D> Polygon;
    typedef bgeom::model::multi_point<Point2D> MultiPoint;
    typedef bgeom::model::multi_linestring<LineString> MultiLineString;
    typedef bgeom::model::multi_polygon<Polygon> MultiPolygon;
    typedef bgeom::model::box<Point2D> Box;
    typedef bgeom::model::ring<Point2D> Ring;
    typedef bgeom::model::segment<Point2D> Segment;
    typedef bgeom::model::referring_segment<Point2D> ReferringSegment;
  }
}

In the next section, we will see how we can render geometries and also we will make the dependencies easier to get.