contents

  1. BitBltでスプライト
  2. ImageListでスプライト
  3. ImageListでDnD

ImageListでスプライト

ImageListは、複数のビットマップを管理するクラスです。 ToolbarやTreeCtrlなどに使われますが、あまり知られていない機能があり、ビットマップを描画させるだけに使っても便利です。

BitBltでスプライト

スプライトとは、透過色を指定できるビットマップ描画のことです。 通常、WindowsGDIを使ってスプライトを行うには、ラスタオペレーションを用いて、以下のような処理を行います。

  1. 描画したい画像と、マスク画像(透過:白、非透過:黒)を用意する
  2. マスク画像をSRCAND(AND演算)でBitBlt()する
  3. 描画したい画像をSRCPAINT(OR演算)でBitBlt()する

また、マスク画像をカラーキーを指定して自動生成することもできます。 そのための簡単な関数を作ってみました(SetColorKey.h, サンプルプロジェクト)。 BitBlt()とMaskBlt()の両方を使ってみました。 マスク生成に関する詳しい情報は、キャバリア研究所さんの画像の透過転送とマスクの自動生成で解説されています。

ImageListでスプライト

上記の方法は、けっこう面倒です。 CreateCompatibleDC()やらSelectObject()やら、ただビットマップを描画するだけでも関数がいくつか必要ですし。 ですが、ImageListを使うと非常に簡単に同様のことが行えます。

いまさら生のHIMAGELISTを使うことも無いので、MFC/WTLにあるCImageListを使います。 以下はWTLを想定していますが、MFCでもほとんど変わらないと思います。

CImageList imagelist;

// 作成する
CBitmap bmp;	// 描画したい画像
COLORREF colorkey;	// カラーキー
CSize size;
bmp.GetSize(size);
imagelist.Create(size.cx, size.cy, ILC_COLOR??? | ILC_MASK, 8, 4);	// ???は4, 8, 16, 24, DDB
imagelist.SetBkColor(colorkey);	// or CLR_NONE
imagelist.Add(bmp, colorkey);

// 描画する
HDC hDC;	// 描画先
int index;	// イメージ番号
CPoint pos;	// 座標
imagelist.Draw(hDC, index, pos, ILD_TRANSPARENT);	// CLR_NONEならばILD_NORMALでもよい

作成時もカラーキーを指定するだけですし、描画時もDraw()一発で済んでしまいます。 欠点は、同じ大きさの画像でないとImageListに保持できないことですが、GDIで作るようなゲーム(2DRPGやパズルゲーム?)ではタイル敷き詰め型だろうので、十分でしょう。

Bitmapリソースを引数に取るタイプのCreate()では自動的に16色(色数は環境依存)のImageListが作成されてしまいます。 空のImageListを作成したあと、Add()したほうが良いでしょう。

描画速度は、やはりILC_COLORDDBがもっとも高速です。 ただし、画面モードが変わった場合はDIBに変換されてしまいます。 (これは、普通のDDBitmapも同様です。) 引き続いてDDBを使いたい場合は、作り直さないといけません。 ゲームなどで、ウィンドウモード⇔フルスクリーンモードの切り替え機能をつけるときは注意が必要です。

ImageListでDnD

アプリケーション間ではなく、自分のアプリケーション内だけのドラッグ&ドロップについてです。 使用目的は、ユニット配置シーンなどを想定しています。

以下は、CWnd もしくは CWindowImpl 内でのコードです。

bool m_isDragging;

void OnLButtonDown(UINT key, CPoint pos)
{
    SetCapture();
    m_images.BeginDrag(0, 32, 32);
    ClientToScreen(&pos);
    m_images.DragShowNolock(TRUE);
    m_images.DragMove(pos);
    m_isDragging = true;
}
void OnMouseMove(UINT key, CPoint pos)
{
    if(m_isDragging)
    {
        // スナッピングなどを行いたい場合は、ここで。
        // pos.x = (pos.x % CellWidth) * CellWidth + CellWidth/2; とか。
        ClientToScreen(&pos);
        m_images.DragMove(pos);
    }
}
void OnLButtonUp(UINT key, CPoint pos)
{
    if(m_isDragging)
    {
        OnDrop(pos); // ドロップされたときの処理をする。
        m_isDragging = false;
        m_images.DragShowNolock(FALSE);
        m_images.EndDrag();
        ReleaseCapture();
    }
}