ゲームを作ろうとしてみる（DIBセクションで軽くハマる）

[前回の日記](https://qiita.com/NyancoRitter/items/28ba00eb4c951a7af0e8)の続きです．

ゲームを作るとは？　実際何を作るのか？　というのは大きな問題だ．

「コンソールでじゃんけんを作りました！」みたいなのをやりたいわけではないのだが，本格的な(?)何かを作れる技量は無いのである．（あと，「本格的な何か」というのは Win32 + GDI とかで作るべきものではないような気もする．）

とにかく素人なのだから，ここはミニゲーム的なやつが良かろう．

…と，いろいろ考えた結果，「砦の攻防」を題材に決めた．
30年くらい前にベーマガ掲載のコードを意味もわからず打ち込んで遊んだアレだ．
基地からミサイルを発射して相手の基地にぶつける対戦ゲーム．

そんなわけで，この題材に必要そうな以下の部品を用意した．（…というのがこの日記の内容だ）

* 乱数：ステージ生成に使う
* ２次元のベクトル：加減乗除くらいは楽に書きたい
* リングバッファ：ミサイルの軌跡をある程度の長さで描画する用
* 画像バッファ：当たり判定を「画素値」で見ることを考えているので

くらいが必要だ．

## 乱数

`<random>` を使うが，呪文みたいな記述を何回も書きたくないので適当にラップする，というだけ．
今回欲しいのは「指定範囲の整数」だけなので，多分こんなのでOK.

```C++
#include <random>

//std::mt19937を用いた乱数生成
class StlRnd
{
public:
    StlRnd() : m_MT( std::random_device().operator()() ){}
    StlRnd( std::mt19937::result_type Seed ) : m_MT( Seed ){}
public:
    template< class T_INT >
    T_INT GetInt( T_INT Min, T_INT Max ){   return std::uniform_int_distribution<T_INT>( Min,Max )( m_MT ); }
private:
    std::mt19937 m_MT;
};
```

## ２次元ベクトル

よくある operator オーバーロードをひたすら書くやつだ．とにかくひたすら書くだけの話．

```C++
//2D Vector
template< class VAL_T >
class Vec2D
{
public:
    VAL_T v[2]; //data is public.

public:  //こういうのを必要なだけ書く．無心で．
    //Unary operators
    Vec2D operator -() const {  return Vec2D( -v[0], -v[1] );   }
    Vec2D &operator +=( const Vec2D &rhs ){ v[0]+=rhs.v[0]; v[1]+=rhs.v[1]; return *this;   }
    Vec2D &operator -=( const Vec2D &rhs ){ v[0]-=rhs.v[0]; v[1]-=rhs.v[1]; return *this;   }
    Vec2D &operator *=( VAL_T s ){  v[0]*=s;    v[1]*=s;    return *this;   }
    Vec2D &operator /=( VAL_T s ){  v[0]/=s;    v[1]/=s;    return *this;   }
    //（※以下略）
};

//alias
using Vec2i = Vec2D<int>;
using Vec2f = Vec2D<float>;
using Vec2d = Vec2D<double>;
```

## リングバッファ

`std::array` のindexをぐるぐる使うだけのやつを用意した．
ガチの汎用みたいなのを実装するわけじゃなくて（←自分の力量的にやれないだろうし），あくまでも自分が今回必要とするやつを書くだけなので，こんな．

* iterator すら無いやさしい世界．
* 各メソッドも簡単に地獄が見れそうな実装になっている．

```C++
template< class T, size_t N >
class RingBuff
{
private:
    std::array<T,N> m_Buff;
    size_t m_iOldest=0;
    size_t m_Size = 0;

public:
    void clear(){   m_iOldest=0;    m_Size=0;   }

    void Push( const T &t )
    {
        if( m_Size < N )
        {   m_Buff[m_Size] = t; ++m_Size;   return; }

        m_Buff[m_iOldest] = t;
        m_iOldest = (m_iOldest + 1) % N;
    }

    T &operator[]( size_t i ){  return m_Buff[ (m_iOldest+i)%N ];   }
    const T &operator[]( size_t i ) const { return m_Buff[ (m_iOldest+i)%N ];   }

    size_t size() const {   return m_Size;  }

    T &Last(){  return this->operator[]( size()-1 );    }
    const T &Last() const { return this->operator[]( size()-1 );    }
};
```

## 画像バッファ

お勉強が必要な箇所であった．
「DDB と DIB が DIBSection でどうのこうの…」いう深遠な世界が広がっており，大変に苦労した．ウボァー．
今でもイマイチ把握できていないが，使う側の視点からすると以下のような感じかと思われ：

* **DDB** :
  GDIの描画関数が使えるけど，ダイレクトに画素データには触れない感じ．
* **DIB** :
  画素データがダイレクトだが，今度は GDI の描画関数を使えないので， 線とか丸とか描きたいなら自前で実装すれば？みたいな世界．

という感じで一長一短な感じなんだけど，DIBセクションなる物は

* **DIBセクション** :
  「俺ならどっちもいけるし！」という感じで，画素データを直接触れるしGDIで線とか丸とか描ける．すごい．便利．

みたいな感じであった．
DIBだけどDDBのように振る舞える(?)とか何とか，ちょっと何言ってるかわかんない感じだけども，何だか最強に思える．
`CreateDIBSection()` で作ると画素データ用のメモリまで勝手に作ってくれて `DeleteObject()` 時に勝手に捨ててくれるとかいう至れり尽くせり感もあり，「とりあえずこれでいいじゃん」とか思うのでこれを使うことにした．

ハンドルとかを保持しなきゃならないので class として実装．（DIB なのか DIBセクション なのか…　みたいな試行錯誤の跡が見られる実装になっているぞ）

```C++
class DIB
{
public:
    DIB(){}
    virtual ~DIB(){}
private:
    DIB( const DIB & ) = delete;
    DIB &operator=( const DIB & ) = delete;

public:
    bool empty() const{ return !BitmapBytes();  }
    int Width() const { return ( empty()  ?  0  :  BMI().bmiHeader.biWidth );   }
    int Height() const {    return ( empty()  ?  0  :  BMI().bmiHeader.biHeight );  }

public:
    //部分描画．
    //拡縮可．DstW, DstH が負の値の場合にはその方向に反転．
    bool Blt(
        HDC hdc, int DstLeft, int DstTop, int DstW, int DstH,
        int SrcLeft, int SrcTop, int SrcW, int SrcH,
        DWORD OP=SRCCOPY
    ) const
    {
        if( empty() )return false;
        if( DstW<0 ){   DstLeft = DstLeft-DstW-1;   }
        if( DstH<0 ){   DstTop = DstTop-DstH-1; }
        return ::StretchDIBits(
            hdc, DstLeft, DstTop, DstW, DstH,
            SrcLeft, Height()-(SrcTop+SrcH), SrcW, SrcH,
            BitmapBytes(), &BMI(), DIB_RGB_COLORS, OP
        );
    }

    //画像全体を描画．
    //拡縮可．DstW, DstH が負の値の場合にはその方向に反転．
    bool Blt( HDC hdc, int DstLeft, int DstTop, int DstW, int DstH, DWORD OP=SRCCOPY ) const
    {   Blt( hdc,DstLeft,DstTop,DstW,DstH, 0,0,Width(),Height(), OP );  }

    //画像全体を等倍 & 無反転 描画
    bool Blt( HDC hdc, int DstLeft, int DstTop, DWORD OP=SRCCOPY ) const
    {   return Blt( hdc,DstLeft,DstTop,Width(),Height(), 0,0,Width(),Height(), OP );    }

protected:
    //(派生側の実装用ヘルパとして提供) パディングを考慮した１行のbyte数
    static size_t LineBytes( size_t Width, int BitCount ){  return (  ((Width*BitCount/8)+3)/4  )*4;    }

protected:  //virtual
    //画素データ先頭．
    //データが無い時には nullptr を返す．
    virtual const BYTE* const BitmapBytes() const = 0;

    //描画に用いるための BITMAPINFO．
    //データが無い場合は内容不定で良い．
    virtual const BITMAPINFO &BMI() const = 0;
};

//24bit DIBセクション
class DIBSection24 : public DIB
{
public:
    DIBSection24() : m_hBMP(NULL), m_pBits(nullptr), m_bmi{0} {}
    virtual ~DIBSection24(){    Release();  }

public:
    //DIBSectionの作成
    bool Create( int W, int H )
    {
        if( W<=0 || H<=0 )return false;
        Release();  //既に生成されていたら捨てる

        m_bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
        m_bmi.bmiHeader.biBitCount = 24;
        m_bmi.bmiHeader.biPlanes = 1;
        m_bmi.bmiHeader.biWidth = W;
        m_bmi.bmiHeader.biHeight = H;
        m_bmi.bmiHeader.biCompression = BI_RGB;

        m_hBMP = CreateDIBSection( NULL, &m_bmi, DIB_RGB_COLORS, (VOID**)(&m_pBits), NULL, 0 );
        return ( m_hBMP != NULL );
    }

    //DIBSectionの破棄
    void Release()
    {
        if( m_hBMP )
        {
            ::DeleteObject( m_hBMP );
            m_hBMP = NULL;
            m_pBits = NULL;
        }
    }

    //HBITMAPを得る
    inline HBITMAP GetHBITMAP(){    return m_hBMP;  }

    //指定座標の画素値を得る
    COLORREF At(int x, int y) const
    {
        const BYTE *P = m_pBits + ( LineBytes( Width(), 24 ) * y ) + x*3;
        return RGB( P[2],P[1],P[0] );
    }

protected:  //virtual
    virtual const BYTE* const BitmapBytes() const override {    return m_pBits; }
    virtual const BITMAPINFO &BMI() const override {    return m_bmi;   }
private:
    BITMAPINFO m_bmi;
    HBITMAP m_hBMP;
    BYTE *m_pBits;
};
```

<details><summary>※1bit 版も作ったけど今回は没</summary>

※本当は 1bit 版のDIBセクションも作る予定だったのだけど，途中で振る舞いが「？」「？？？」となったので「だったらもう 24bit で富豪的にやるし！」と方針を切り替えて没にした．
（最終的には「パレットデータが２つ分必要」ということだと分かったのだが，大昔に触ったとき 1bitだと `SetTextColor` とかの設定が描画に効いていた気がするんだよなぁ……　イミワカンネー）

</details>

……で，実際に使おうとすると今度は「メモリデバイスコンテキストが互換性どうこう…」いう話が出てきて，私は再度「ウボァー」と叫ぶのであった．

DIBセクションなる物にGDI関数で描画したい場合にはDDBと同様に「デバイスコンテキストに選択」せねばならぬ．
画像バッファをいじくる場合には `CreateCompatibleDC()` で「メモリデバイスコンテキスト」なる物を作って使えばいいっぽいのだが，この場合，この関数の引数に渡す HDC は何なのか？
NULLを指定すると「アプリケーションの現在の画面と互換性のあるメモリDC」が作成されるとのことだが，これでいいのかダメなのか？
そもそもDIBは「デバイス非依存」なハズだが「何かと互換性のあるDC」とかいうのを使うべきなのか何なのか，これがさっぱりわからない．
とりあえず NULL 指定で動いてるみたいだけど，いいのかなぁ…？

あと，描画する際に `StretchDIBits()` を使うのが良いのか，それとも「メモリデバイスコンテキストに選択して `BitBlt()` とか使う」みたいな形とすべきなのか？　というのもよくわかんない．（とりあえず前者で実装したけれども）

---

**つづき** :

* [ゲーム部分を実装](https://qiita.com/NyancoRitter/items/b1f70251272dba6fc7db)