LoginSignup
0
0

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

Last updated at Posted at 2024-04-08

前回の日記の続きです.

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

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

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

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

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

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

くらいが必要だ.

乱数

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

#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;
};

2次元ベクトル

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

//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 すら無いやさしい世界.
  • 各メソッドも簡単に地獄が見れそうな実装になっている.
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セクション なのか… みたいな試行錯誤の跡が見られる実装になっているぞ)

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:
    //(派生側の実装用ヘルパとして提供) パディングを考慮した1行の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;
};
※1bit 版も作ったけど今回は没

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

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

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

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


つづき :

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0