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;;
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 ];
}
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;
}
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:
namespace boost {
namespace geometry {
namespace traits {
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 );
}
};
}
}
}
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.