ゲームフレームワーク的なものを作る。(5)〜Point、Size、Rect、Color〜

今回はグラフィックス系プログラムでよく使われる点、サイズ、矩形、色クラスを作ります。
とりあえず、基本的な機能のみ。

Pointクラス

スクリーン上の点を表す。

class Point
{
public:
    Point( int x = 0, int y = 0 );

public:
    int x, y;
};

Point::Point( int x, int y )
    : x( x ), y( y )
{
}
Sizeクラス

スクリーン上の矩形の縦横サイズを表す。

class Size
{
public:
    Size( int width = 0, int height = 0 );

public:
    int width, height;
};

Size::Size( int width, int height )
    : width( width ), height( height )
{
}
Rectクラス

スクリーン上の矩形を表す。

class Rect
{
public:
    /**
     *  コンストラクタ。左上と右下の点を指定
     */
    Rect( int left = 0, int top = 0, int right = 0, int bottom = 0 );
    /**
     *  コンストラクタ。左上と右下の点を指定
     */
    Rect( const Point& left_top, const Point& right_bottom );
    /**
     *  コンストラクタ。左上の点とサイズを指定
     */
    Rect( const Point& left_top, const Size& size );


public:
    //矩形の幅を取得
    int Width()const{ return right - left; }
    //矩形の高さを取得
    int Height()const{ return bottom - top; }
    //矩形のSizeを取得
    Size GetSize()const{ return Size( Width(), Height() ); }
    //矩形の左上の点と右下の点にoffsetを加算
    void Offset( const Point& offset );


public:
    int left, top, right, bottom;       ///<    左上と右下の点
};

Rect::Rect( int left, int top, int right, int bottom )
    : left( left ), top( top )
    , right( right ), bottom( bottom )
{
}

Rect::Rect( const Point& left_top, const Point& right_bottom )
    : left( left_top.x ), top( left_top.y )
    , right( right_bottom.x ), bottom( right_bottom.y )
{
}

Rect::Rect( const Point& left_top, const Size& size )
    : left( left_top.x ), top( left_top.y )
    , right( left_top.x + size.width ), bottom( left_top.y + size.height )
{
}

void Rect::Offset( const Point& offset )
{
    left += offset.x;
    top += offset.y;
    right += offset.x;
    bottom += offset.y;
}
Colorクラス

0〜1の成分(r,g,b,a)の色を表す。

class Color
{
public:
    Color( float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f );

public:
    DWORD to_A8R8G8B8()const
    {
        DWORD a_ = static_cast< DWORD >( a * 255 ) << 24;
        DWORD r_ = static_cast< DWORD >( r * 255 ) << 16;
        DWORD g_ = static_cast< DWORD >( g * 255 ) <<  8;
        DWORD b_ = static_cast< DWORD >( b * 255 ) <<  0;
        return a_ | r_ | g_ | b_;
    }

public:
    float r, g, b, a;
};

Color::Color( float r, float g, float b, float a )
    : r( r ), g( g ), b( b ), a( a )
{
}
文字列化インターフェイス

ゲームのデバッグでは、リアルタイムに変化するパラメータを常に画面上に表示することが多いと思いますが、例えばRectのメンバを表示するためには、以下のように非常に面倒な記述が必要になります。

Rect rect( 1, 2, 3, 4 );

std::stringstream ss;
ss  << "rect:" << "( " <<rect.left << ", " << rect.top << ", " << rect.right << ", " << rect.bottom << " )" << std::endl;
graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );

以下のように、ストリームに直接渡せるようになると便利ですよね。

Rect rect( 1, 2, 3, 4 );

std::stringstream ss;
ss  << "rect:" << rect << std::endl;   //"rect:( 1, 2, 3, 4 )"
graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );

ということで、文字列化インターフェイスの作成と、operator<<のオーバーロードを行いましょう。

class IStringizable
{
    /**
     *  文字列への変換
     */
    virtual std::string to_string()const = 0;

    /**
     *  ostreamへの挿入
     */
    friend inline std::ostream& operator<<( std::ostream& os, const IStringizable& s )
    {
        os << s.to_string();
        return os;
    }
};
使い方

デバッグ時にストリームに対して出力したいクラスにIStringizableインターフェイスを実装します。

class Rect
    : public IStringizable
{
/*略*/

private:
    /**
     *  文字列への変換
     */
    std::string to_string()const;

};

/**
 *  文字列への変換
 */
std::string Rect::to_string()const
{
    std::stringstream ss;
    ss << "( " << left << ", " << top << ", " << right << ", " << bottom << " )";
    return ss.str();
}
Point point( 1, 2 );
Size size( 1, 2 );
Rect rect( 1, 2, 3, 4 );
Color color( 1.0f, 1.0f, 1.0f, 1.0f );

std::stringstream ss;
ss  << "point:" << point << std::endl
    << "size:" << size << std::endl
    << "rect:" << rect << std::endl
    << "color:" << color << std::endl
    ;
graphics.DrawString( Point( 0, 0 ), ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f ) );


ダウンロード


stringizableの発音がわかりません><

explicit(06/21追記)

コメントでご指摘頂いた通り、コンストラクタにexplicitを付けなければ、暗黙の変換によって意図しない変換が行われてしまいます。

graphics.DrawString(
    1,    //Point( 1, 0 )が渡される。
    ss.str(), Color( 1.0f, 1.0f, 1.0f, 1.0f )
    );

あるいは、

Point point( 1, 2 );

point = 1;    //explicitがなければ、これもOK。意味がわからないですね。

非常にまずいですね。ということで、コンストラクタに引数が1つ、または2つ以上ある時で2つ目以降にデフォルト引数が設定されている場合、原則としてexplicitを付けましょう。たとえば文字列クラスのように、暗黙の変換がなければ不便な場合だけexplicitを外すとよいです。

class Point
    : public IStringizable
{
public:
    /**
     *  コンストラクタ
     */
    explicit Point( int x = 0, int y = 0 );
/*略*/
};
class Size
    : public IStringizable
{
public:
    /**
     *  コンストラクタ
     */
    explicit Size( int width = 0, int height = 0 );
/*略*/
};
class Rect
    : public IStringizable
{
public:
    /**
     *  コンストラクタ。左上と右下の点を指定
     */
    explicit Rect( int left = 0, int top = 0, int right = 0, int bottom = 0 );
/*略*/
};
class Color
    : public IStringizable
{
public:
    /**
     *  コンストラクタ
     */
    explicit Color( float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 0.0f );
/*略*/
};