第8章 クリッピング

8-3 クリッピングをプログラムで行う

枠で隠すのではなく、画面内に出ている部分だけを切り取って表示するプログラムを考える。

(a) 考え方

BltFast関数は、描画予定の左上の座標(x, y)と、描画する画像の矩形(left, top, right, bottom)を与え、バックバッファに転送する。このとき、バックバッファの領域をはみ出して描画した場合、エラーを返す。

キャラクタ画像の矩形がゲーム領域をはみ出してしまうため、描画が失敗する。

であれば、BltFast関数を実行する前に、領域をはみ出しているかどうかを調べ、はみ出している部分があれば画像の矩形を調整し、はみ出さないような矩形にしてからBltFast関数を実行すればよい。

ゲーム領域が640×480で、100×100の画像を(600, 300)の位置に描画すると仮定する。この場合、描画される画像の左上の座標は(600, 300)だが、右下の座標が(600+100, 300+100)で(700, 400)となり、右に700 - 640 = 60ピクセルはみ出してしまう
そこで、画像の矩形(left, top, right, bottom)のうち、rightを60ピクセル縮めれば、ゲーム領域をはみ出さずに描画できる。

(b) プログラミング例

描画処理を行う直前に、表示する画像の矩形がゲーム領域をはみ出しているかを調べ、はみ出していない部分の矩形を求めてから、その矩形を利用して描画を行う。

RECT GameRect = {0, 0, 640, 480};  // ゲーム領域(キャラクタ表示領域の矩形)
RECT CharRect = {0, 0, 100, 100};  // キャラクタ画像の矩形
int x = 600, y = 300;  // キャラクタ表示座標

RECT wRect;  // 描画に使う矩形
int wx, wy;  // 描画に使う座標
int width, height;  // 計算用
  ・
  ・
  ・

/* データの準備 */
wRect = CharRect;
wx = x;
wy = y;
width = CharRect.right - CharRect.left;
height = CharRect.bottom - CharRect.top;

/* 指定範囲を超えた画像を切り取る */
if (x < GameRect.left)  // 左側
{
    wRect.left = CharRect.left + (GameRect.left - x);
    wx = GameRect.left;
}
else if (GameRect.right < x + width) // 右側
{
    wRect.right = CharRect.right - ((x + width) - GameRect.right);
}
if (y < GameRect.top)  // 上側
{
    wRect.top = CharRect.top + (GameRect.top - y);
    wy = GameRect.top;
}
else if (GameRect.bottom < y + height)  // 下側
{
    wRect.bottom = CharRect.bottom - ((y + height) - GameRect.bottom);
}

/* 描画処理(切り取った矩形と座標を使う) */
hRet = g_pDDSBack->BltFast(wx, wy, g_pDDSChar, &wRect, DDBLTFAST_SRCCOLORKEY);
if (hRet != DD_OK)
    return;

※キャラクタ表示座標と画像の矩形は繰り返し使うため、描画処理用の変数を用意している

(c) 実際のゲームプログラムへの実装を考える

クリッピング処理は、シューティングゲームやアクションゲームなどでは頻繁に使う処理である。描画のたびにこれだけの処理を行うようプログラムするのは現実的ではない。頻繁に使うことを考え、クリッピング処理の関数化を考える。

クリッピング処理は、通常の描画処理に使う「表示座標(x、y)」「画像の矩形」「使用するオフスクリーンサーフェイス」「転送パラメータ」の他に、「表示領域」を必要とする。であれば、通常の描画処理に必要な変数と、表示領域を引数にして、クリッピングを行った描画を行う関数を作成すればよい。
描画処理はいろんな場面で使うので、myDraw.cppに作成するのが望ましい。なお、プロトタイプ宣言はmyDraw.hで行う。

//-----------------------------------------------------------------------------
// 関数名 : myBltFastClip()
// 機能概要: クリッピングして描画する
//-----------------------------------------------------------------------------
HRESULT myBltFastClip(
    int x, int y,  // 表示座標
    LPDIRECTDRAWSURFACE7 g_pDDSOne,  // 転送元サーフェイス
    RECT rcRect,  // 画像の矩形
    DWORD dwTrand,  // 転送パラメータ
    RECT ClipRect  // クリッピングする矩形
)
{
    int     width, height;

    /* データの準備 */
    width = rcRect.right - rcRect.left;
    height = rcRect.bottom - rcRect.top;

    /* 指定範囲を超えた画像を切り取る */
    if (x < ClipRect.left)  // 左側
    {
        rcRect.left += (ClipRect.left - x);
        x = ClipRect.left;
    }
    else if (ClipRect.right < x + width) // 右側
    {
        rcRect.right -= ((x + width) - ClipRect.right);
    }
    if (y < ClipRect.top)  // 上側
    {
        rcRect.top += (ClipRect.top - y);
        y = ClipRect.top;
    }
    else if (ClipRect.bottom < y + height)  // 下側
    {
        rcRect.bottom -= ((y + height) - ClipRect.bottom);
    }

    /* 描画処理(切り取った矩形と座標を使う) */
    return g_pDDSBack->BltFast(x, y, g_pDDSOne, &rcRect, dwTrand);

}

※座標や矩形情報を「値渡し(Call by Value)」で貰っているため、直接計算に使っている
※BltFast関数の実行結果(戻り値)を直接returnで返している

(d) myBltFastClip関数の使いかた

この描画処理を、myBltFastClip関数を使って行うには、次のように記述する。

//-----------------------------------------------------------------------------
// 関数名 : ScreenOut()
// 機能概要: ゲーム画像描画処理
//-----------------------------------------------------------------------------
static void ScreenOut(void)
{
        HRESULT hRet;
        int i;

    /* ゲーム画面用背景 */
    hRet = g_pDDSBack->BltFast(0, 0, g_pDDSGame, &ScreenRect, DDBLTFAST_NOCOLORKEY);
    if (hRet != DD_OK)
        return;

    /* 自キャラ */
    hRet = myBltFastClip(MyChar.x, MyChar.y, g_pDDSChara,
                    MyChar.rect[MyChar.direction][MyChar.AnimeNo], DDBLTFAST_SRCCOLORKEY, ScreenRect);
    if (hRet != DD_OK)
        return;

    /* 自ショット */
    for (i=0 ; i<SHOT_MAX ; i++)
    {
      if (MyShot[i].life > 0)
        {
           hRet = myBltFastClip(MyShot[i].x, MyShot[i].y, g_pDDSChara,
                  MyShot[i].rect[MyShot[i].direction][MyShot[i].AnimeNo], DDBLTFAST_SRCCOLORKEY, ScreenRect);
           if (hRet != DD_OK)
              return;
        }
    }
}

※ScreenRectからはみ出した部分を切り取って描画を行う
※RECT構造体に&をつけていないことに注意

(a)〜(d) 確認(必須問題)

第7章で作成したプログラムにmyBltFastClip関数を組み込み、キャラクタがクリッピングされるように修正する。
動作を確かめるには、ゲーム領域とマイキャラの当たり判定を外さなければならないことに注意!!

[ 実行結果サンプル ]

(e) myBltFastClip関数の問題点

この関数は、クリッピング領域を完全にはみ出しているキャラクタについてのチェックを行っていない。そのため、キャラクタが完全にはみ出してる状態で関数を実行すると、BltFast関数がエラーになり、描画に失敗する。
((a)〜(d)確認プログラムでは、弾を発射した瞬間にマイキャラを画面外に移動させると、弾が描画されずに消えてしまう)

ゲーム領域から完全にはみ出してる場合、表示座標と転送元矩形の計算が狂い、描画に失敗する

描画に失敗する(DD_OK以外の戻り値が帰ってくる)と、if文によりreturnするようにプログラムしているため、これ以降の描画が行われずに描画処理が終わってしまう。
だからといって、描画後のif文を消してしまうと、表示領域を超えたから失敗したのか、別の原因で失敗したのかが分からなくなってしまう

そこで、表示するキャラクタが表示領域を完全に超えていたら描画を行わずにDD_OKを返すよう、myBltFastClip関数を修正する。

//-----------------------------------------------------------------------------
// 関数名 : myBltFastClip()
// 機能概要: クリッピングして描画する
//-----------------------------------------------------------------------------
HRESULT myBltFastClip(
    int x, int y,  // 表示座標
    LPDIRECTDRAWSURFACE7 g_pDDSOne,  // 転送元サーフェイス
    RECT rcRect,  // 画像の矩形
    DWORD dwTrand,  // 転送パラメータ
    RECT ClipRect  // クリッピングする矩形
)
{
    int     width, height;

    /* データの準備 */
    width = rcRect.right - rcRect.left;
    height = rcRect.bottom - rcRect.top;

    /* キャラクタがクリッピング領域を超えているかをチェックする */
    if ((x + width) < ClipRect.left || (y + height) < ClipRect.top || ClipRect.right < x || ClipRect.bottom < y)
        return DD_OK;

    /* クリッピング領域を超えた画像を切り取る */
    if (x < ClipRect.left)  // 左側
    {
        x = ClipRect.left;
        rcRect.left += (ClipRect.left - x);
    }
    else if (ClipRect.right < x + width) // 右側
    {
        rcRect.right -= ((x + width) - ClipRect.right);
    }
    if (y < ClipRect.top)  // 上側
    {
        y = ClipRect.top;
        rcRect.top += (ClipRect.top - y);
    }
    else if (ClipRect.bottom < y + height)  // 下側
    {
        rcRect.bottom -= ((y + height) - ClipRect.bottom);
    }

    /* 描画処理(切り取った矩形と座標を使う) */
    return g_pDDSBack->BltFast(x, y, g_pDDSOne, &rcRect, dwTrand);

}

クリッピング領域から完全にはみ出していたら、DD_OKを返して、以降の処理を行わない。結果、マイキャラが画面外に出ても弾が表示されるようになる。

※キャラクタが完全にはみ出しても移動処理がそのまま行われるため、ゲーム的に正しいかどうかは別問題である

[ 実行結果サンプル ]


[ TOP ]