PDAのひとりごと

黄昏時、携帯情報端末は薄闇の向こうに何を見るのか

ポポペのプロ~10.得点をつけよう

2004-08-13 07:04:27 | Lesson
 ゲームなのだからスコアぐらい欲しいものですよね。最初からそのつもりで数字の絵も用意してあるのですから、早速スコアを付けましょう。
 まず変数です。スコアを保持するためにグローバル変数として iScore を作りましょう。

int iScore;


 変数は初期化しなくてはなりません。例によって初期化はWM_CREATEが通知されたときの処理 DoCreateMain 関数の中になります。また、この時にスコアも表示してしまいます。スコア表示は少し手間がかかるので、関数にまとめてしまいましょう。GmDispScore関数がそれです。

iScore = 0;             // スコア初期化
GmDispScore();          // スコア表示


 スコアは、インベーダーを撃墜したときに増やします。つまり、砲台の弾の移動処理であるGmMoveTamaHou関数の中の当たり判定の中でカウントアップする事になります。

        if ((posTamaHou.x == posInv.x) && (posTamaHou.y == posInv.y)) {
	    PatBlt( hdcScreen,          // 対象のHDC
	        posTamaHou.x,           // 表示位置 X座標
	        posTamaHou.y,           // 表示位置 Y座標
	        32,                     // 表示サイズ 幅
	        24,                     // 表示サイズ 高さ
	        WHITENESS );            // 白で塗りつぶす
            posInv.x = 320 - 32;
            posInv.y = 24;
            posTamaHou.x = -1;          // 表示終了
            iScore++;                   // スコア+1
            GmDispScore();              // スコア表示
        }


 今回は、これ以外に得点追加となるものがないため、後は表示処理だけです。スコアは、下2桁だけ表示します。

void GmDispScore(void)
{
    int a, b;

    a = iScore / 100;        // 1234 / 100 = 12
    b = iScore - a * 100;    // 1234 - 12*100 = 34
    a = b / 10;              // 34 / 10 = 3
    b -= a * 10;             // 34 - 3*10 = 2

    BitBlt( hdcScreen,       // 転送先HDC
        104,                 // 表示位置 X座標
        0,                   // 表示位置 Y座標
        16,                  // 表示サイズ 幅
        24,                  // 表示サイズ 高さ
        hdcBmp,              // 転送元HDC
        a*16,                // 転送元 X座標
        0,                   // 転送元 Y座標
        SRCCOPY );           // そのまま転送
    BitBlt( hdcScreen,       // 転送先HDC
        104+16,              // 表示位置 X座標
        0,                   // 表示位置 Y座標
        16,                  // 表示サイズ 幅
        24,                  // 表示サイズ 高さ
        hdcBmp,              // 転送元HDC
        b*16,                // 転送元 X座標
        0,                   // 転送元 Y座標
        SRCCOPY );           // そのまま転送
}


 これでスコアは表示されるのですが、今のままではコマンドバーに隠れて見えません。そこで、コマンドバーを表示しないようにしてしまいます。
 まずは、DoWmCreateの最初の部分にある3行がコマンドバーを作成している部分なので、これをコメントアウトします。

//    hwndCB = CommandBar_Create(hInst, hWnd, 1);
//    CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0);
//    CommandBar_AddAdornments(hwndCB, 0, 0);


 次に、InitInstanceの最後の部分に、コマンドバーを表示する部分があるのでこれもコメントアウトします。

//    if (hwndCB)
//        CommandBar_Show(hwndCB, TRUE);


 最後に、WndProcのWM_DESTROY通知の処理の中にコマンドバーを破棄する処理があるので、これを消して終わりです。

//            CommandBar_Destroy(hwndCB);


 さて、これでコマンドバーが消えたのですが、今度はコマンドバーにあった「アプリケーションの終了」がなくなってしまいました。そこで、ESCキー(ポポペの「もどる」キー)で終了するように処理を追加します。

 WndProcのWM_KEYDOWNの処理の中で、ESCキーを判定してプログラムを終了するようにします。

        case WM_KEYDOWN:
            wKey = wParam;
            if (wParam == VK_ESCAPE) {
                DestroyWindow(hWnd);
            }
            break;


 これでようやくスコア表示できるようになりました。スコアが表示されるとずいぶんゲームらしくなりますね。

ポポペのプロ~9.当たり

2004-08-12 10:45:47 | Lesson
 弾がインベーダーに当たれば得点となるのがこのゲームのルールです。ということで、当たるようにしましょう。
 まずは変数を用意します。

int iStateHou;                          // 砲台表示状態


 砲台の弾の移動処理の中で当たったかどうかの判定を入れます。
void GmMoveTamaHou(void)
{
    if (posTamaHou.x == -1) {           // 表示中ではない
        return;
    }

    PatBlt( hdcScreen,                  // 対象のHDC
        posTamaHou.x,                   // 表示位置 X座標
        posTamaHou.y,                   // 表示位置 Y座標
        32,                             // 表示サイズ 幅
        24,                             // 表示サイズ 高さ
        WHITENESS );                    // 白で塗りつぶす

    posTamaHou.y -= 24;                 // 上へ昇る

    if (posTamaHou.y <= 0) {            // 上端を越えた時
        posTamaHou.x = -1;              // 表示終了
    } else {
        BitBlt( hdcScreen,              // 転送先HDC
            posTamaHou.x,               // 表示位置 X座標
            posTamaHou.y,               // 表示位置 Y座標
            32,                         // 表示サイズ 幅
            24,                         // 表示サイズ 高さ
            hdcBmp,                     // 転送元HDC
            0,                          // 転送元 X座標
            0,                          // 転送元 Y座標
            SRCCOPY );                  // そのまま転送
        if ((posTamaHou.x == posInv.x) && (posTamaHou.y == posInv.y)) {
	    PatBlt( hdcScreen,          // 対象のHDC
	        posTamaHou.x,           // 表示位置 X座標
	        posTamaHou.y,           // 表示位置 Y座標
	        32,                     // 表示サイズ 幅
	        24,                     // 表示サイズ 高さ
	        WHITENESS );            // 白で塗りつぶす
            posInv.x = 320 - 32;
            posInv.y = 24;
        }
    }
}


 弾とインベーダーの大きさが異なる場合や、移動量が異なる場合、当たり判定はもっと複雑になりますが、今回はキャラクタのサイズを規定して、移動量もそれにあわせているため、弾の座標値とインベーダーの座標値が一致しているかどうかで当たりの判定が行えます。
 次に、インベーダーの弾の移動処理の中に砲台との当たり判定を入れます。こちらも同様に、弾の座標値と砲台の座標値との比較で当たったかどうかの判定を行います。

void GmMoveTamaInv(void)
{
    PatBlt( hdcScreen,          // 対象のHDC
        posTamaInv.x,           // 表示位置 X座標
        posTamaInv.y,           // 表示位置 Y座標
        32,                     // 表示サイズ 幅
        24,                     // 表示サイズ 高さ
        WHITENESS );            // 白で塗りつぶす

    posTamaInv.y += 24;         // 下へ落ちる

    if ((posTamaInv.x == posHou.x) && (posTamaInv.y == posHou.y)) {
        iStateHou = 1;
        posTamaInv.x = posInv.x;        // 表示終了
        posTamaInv.y = posInv.y + 24;
    }

    if (posTamaInv.y >= 240-24) {       // 下端を越えた時
        posTamaInv.x = posInv.x;        // 表示終了
        posTamaInv.y = posInv.y + 24;
    }

    BitBlt( hdcScreen,          // 転送先HDC
        posTamaInv.x,           // 表示位置 X座標
        posTamaInv.y,           // 表示位置 Y座標
        32,                     // 表示サイズ 幅
        24,                     // 表示サイズ 高さ
        hdcBmp,                 // 転送元HDC
        0,                      // 転送元 X座標
        0,                      // 転送元 Y座標
        SRCCOPY );              // そのまま転送
}


 インベーダーの方は、弾が当たってもゲームが継続するので特別な処理は不要ですが、砲台は3台破壊されると終わりなので、その処理が必要となります。そこで、弾が砲台に当たったときにステータスを変更し、後でそのステータスを判定して砲台が破壊されたときの処理を行うようにします。
 WM_TIMERが通知されたときに弾とインベーダーが動いています、砲台が爆発している間はこれらを一時的に止めてしまいます。

case WM_TIMER:
    switch (iStateHou) {
    case 0:                     // ゲーム中
        GmMoveInv();
        GmMoveTamaInv();
        GmMoveTamaHou();
        GmMoveHou();
        break;
    case 1:                     // 破壊
        GmDestroyHou();
        break;
    }
    InvalidateRect(hWnd, NULL, FALSE);
    break;


 砲台が破壊されたときは、少しだけ爆発を表現してみます。二つの絵を交互に表示するとそれらしく見えることでしょう。枚数を増やせばもっと違った爆発も表現できますが、それは各自考えてみてください。

void GmDestroyHou(void)
{
    static int iInvTime = 0;

    iInvTime++;

    if (iInvTime % 2) {

        if (iInvTime > 10) {
            iInvTime = 0;
            posHou.x = 0;       // 表示位置 X座標
            iStateHou = 0;      // 元の処理へ戻す
        }

        BitBlt( hdcScreen,      // 転送先HDC
                posHou.x,       // 表示位置 X座標
                posHou.y,       // 表示位置 Y座標
                32,             // 表示サイズ 幅
                24,             // 表示サイズ 高さ
                hdcBmp,         // 転送元HDC
                64,             // 転送元 X座標
                24,             // 転送元 Y座標
                SRCCOPY );      // そのまま転送
    } else {
        PatBlt( hdcScreen,      // 対象のHDC
            posHou.x,           // 表示位置 X座標
            posHou.y,           // 表示位置 Y座標
            32,                 // 表示サイズ 幅
            24,                 // 表示サイズ 高さ
            WHITENESS );        // 白で塗りつぶす
    }
}


 さて、ここまで変更したらビルドしてみましょう。
 さきほど、砲台が3台破壊されるとゲームが終わると言いましたが、実は、まだ終わりの処理が入っていません。砲台の数も設定していないし、残機数の表示もしていないからです。それらは、もう少し先で実装します。その前に、次回は得点の表示を追加しましょう。


ポポペのプロ~8.爆撃

2004-08-01 17:48:43 | Lesson
(1回分抜けていたため、番号がずれてしまいました。修正も含めて過去の分はこちらにあります→めざせ!ポポペのプロ

 今度はインベーダー側の攻撃です。基本は砲台の時と同じで、移動方向が逆になります。
 まず、インベーダーの弾の位置を示す変数を作ります。場所は posTamaHou を定義した下で良いでしょう。

POINT posTamaInv;


 インベーダーの弾は、勝手に発射されます。処理のタイミングは、WM_CREATEが通知された時です。ここでは、弾が1発だけと決めているので、1つの弾が消えたら次の弾を出すようにしますが、それは別の処理になります。

posTamaInv.x = posInv.x;
posTamaInv.y = posInv.y + 24;
BitBlt( hdcScreen,		// 転送先HDC
        posTamaHou.x,	// 表示位置 X座標
        posTamaHou.y,	// 表示位置 Y座標
        32,		// 表示サイズ 幅
        24,		// 表示サイズ 高さ
        hdcBmp,		// 転送元HDC
        0,		// 転送元 X座標
        0,		// 転送元 Y座標
        SRCCOPY );	// そのまま転送


 弾が移動する処理は、定期的に実行しなければなりません。タイミング的には砲台の弾と同じで良いでしょう。WM_TIMER通知処理の中です。

case WM_TIMER:
    GmMoveInv();
    GmMoveTamaInv();
    GmMoveTamaHou();
    GmMoveHou();
    InvalidateRect(hWnd, NULL, FALSE);
    break;


 インベーダーの弾の移動方向は、砲台の弾と逆になります。

void GmMoveTamaInv(void)
{
    PatBlt( hdcScreen, 		// 対象のHDC
            posTamaInv.x,		// 表示位置 X座標
            posTamaInv.y,		// 表示位置 Y座標
            32,			// 表示サイズ 幅
            24,			// 表示サイズ 高さ
            WHITENESS );		// 白で塗りつぶす

    posTamaInv.y += 24;		// 下へ落ちる

    if (posTamaInv.y >= 320-1-24) {	// 下端を越えた時
        posTamaInv.x = posInv.x;	// 表示終了
        posTamaInv.y = posInv.y + 24;
    }

    BitBlt( hdcScreen,		// 転送先HDC
            posTamaInv.x,		// 表示位置 X座標
            posTamaInv.y,		// 表示位置 Y座標
            32,			// 表示サイズ 幅
            24,			// 表示サイズ 高さ
            hdcBmp,			// 転送元HDC
            96,			// 転送元 X座標
            24,			// 転送元 Y座標
            SRCCOPY );		// そのまま転送
}


 これでインベーダーの弾が移動するようになりました。ビルドして実行してみましょう。
 発射された弾は勝手に下へ飛んでいき、下端で消えます。まだ当たり判定を入れていないため、砲台に当たっても何も起こりません。

ポポペのプロ~6.ミサイル発射

2004-07-17 17:35:00 | Lesson
 弾は、インベーダーの分と砲台の分の2つがありますが、まずは砲台の方から始めましょう。
 今までと同様に砲台の弾の位置を示す変数を作ります。場所はグローバル変数を定義するところ、posHou を定義した下で良いでしょう。

struct position_t posTamaHou;


 砲台の弾は、発射ボタンが押されてから表示するので、はじめに表示する事はありませんが、表示位置は初期化しておきましょう。場所はWM_CREATE通知の処理の中なのですが、処理っすることがだいぶ増えたので、別関数に分けてしまいましょう。
 DoWmCreateという関数を作ります。場所は、DoWmPaintの前が良いでしょう。

// WM_CREATE
void DoWmCreate(HWND hWnd)
{
    hwndCB = CommandBar_Create(hInst, hWnd, 1);
    CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0);
    CommandBar_AddAdornments(hwndCB, 0, 0);

    hdcBmp = CreateCompatibleDC(GetDC(hWnd));
    hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
    SelectObject(hdcBmp, hBmp);

    hdcScreen = CreateCompatibleDC(GetDC(hWnd));
    hScreen = CreateCompatibleBitmap(GetDC(hWnd), 320, 240);
    SelectObject(hdcScreen, hScreen);

    PatBlt( hdcScreen, 0, 0, 320, 240, WHITENESS );

    posInv.x = 320;
    posInv.y = 24;

    SetTimer(hWnd, 1, 200, NULL);

    iInvTime = 0;
    wKey = 0;

    posHou.x = 0;
    posHou.y = 240 - 48;
    BitBlt( hdcScreen,   // 転送先デバイスコンテキスト
        posHou.x,        // 表示位置 X座標
        posHou.y,        // 表示位置 Y座標
        32,              // 表示サイズ 幅
        24,              // 表示サイズ 高さ
        hdcBmp,          // 転送元デバイスコンテキスト
        64,              // 転送元 X座標
        24,              // 転送元 Y座標
        SRCCOPY );       // そのまま転送

    posTamaHou.x = -1;
    posTamaHou.y = 0;
}


 砲台の弾はこの時点では表示しません。そのためposTamaHouに入れるのはどんな値でも良いのですが、後で表示するかどうかを判定するためにposTamaHou.xを利用します。posTamaHou.xが-1の時は表示しないという取り決めにしておくのです。その処理は後で出てきます。
 WndProcのWM_CREATE通知の部分は、DoWmCreateをコールするよう書き換えます。

        case WM_CREATE:
            DoWmCreate(hWnd);
            break;


 ENTERキーが押されたときに弾を発射するためには、GmMoveHou関数の中でキー入力値を判定して発射処理を行うようにします。

void GmMoveHou(void)
{
    switch (wKey) {
    case VK_LEFT:
        GmMoveHouSub(-1);    // 左へ移動
        break;
    case VK_RIGHT:
        GmMoveHouSub(1);    // 右へ移動
        break;
    case VK_RETURN:
        if (posTamaHou.x == -1) {    // 弾が表示中でなければ発射
            posTamaHou.x = posHou.x;
            posTamaHou.y = posHou.y;
            GmMoveTamaHou();
            GmMoveHouSub(0);
        }
        break;
    }
}


 仮想キーコードのVK_RETURNは、キーボード上のENTERキーに対応しています。キーが押されていたら砲台の弾が表示中かどうかを判定し、表示中ではない時、つまりposTamaHou.xが-1の時に表示座標を決めます。すると、posTamaHou.xには画面内のX座標値である0~239の値が入ることになり、-1ではなくなります。
 GmMoveTamaHou関数は砲台の弾の移動を行います。
 弾の移動は、弾が表示されている間のみ行います。ここでは、弾が表示中ではないときを判定し、関数を終了させてしまっています。表示中であれば、今表示されている砲台の弾を消し、Y座標を変更します。現在のY座標に縦幅分の24を引くことで上方への移動を行います。座標を変更したら、そこへ砲台の弾を表示します。

// 砲台の弾移動処理
void GmMoveTamaHou(void)
{
    if (posTamaHou.x == -1) {    // 表示中ではない
        return;
    }

    PatBlt( hdcScreen,       // 対象のHDC
        posTamaHou.x,        // 表示位置 X座標
        posTamaHou.y,        // 表示位置 Y座標
        32,                  // 表示サイズ 幅
        24,                  // 表示サイズ 高さ
        WHITENESS );         // 白で塗りつぶす

    posTamaHou.y -= 24;      // 上昇する

    if (posTamaHou.y <= 0) { // 上端を越えた時
        posTamaHou.x = -1;   // 表示終了
    } else {
        BitBlt( hdcScreen,   // 転送先HDC
            posTamaHou.x,    // 表示位置 X座標
            posTamaHou.y,    // 表示位置 Y座標
            32,              // 表示サイズ 幅
            24,              // 表示サイズ 高さ
            hdcBmp,          // 転送元HDC
            96,              // 転送元 X座標
            24,              // 転送先 Y座標
            SRCCOPY );       // そのまま転送
    }
}


 弾が移動する処理は、定期的に実行しなければなりません。タイミング的にはインベーダーを動かすのと同じで良いでしょう。

case WM_TIMER:
    GmMoveInv();
    GmMoveTamaHou();
    GmMoveHou();
    InvalidateRect(hWnd, NULL, FALSE);
    break;


 これで砲台の弾が移動するようになりました。ビルドして実行してみましょう。
 ENTERキーを押すと弾が発射されます。発射された弾は勝手に上へ飛んでいき、上端で消えます。まだ当たり判定を入れていないため、インベーダーに当たっても何も起こりませんが、弾とインベーダーの位置関係によってはおかしな表示になるのが分かるでしょうか?これについては後ほど解決することになります。

ポポペのプロ~5.キー入力とキャラの移動

2004-07-10 20:13:13 | Lesson
 さて、インベーダーが動いたので、今度は砲台も動かしましょう。砲台は、プレイヤーの操作によって左右に移動します。まずはキー入力処理を付け加えましょう。

 一定時間毎に画面を表示するゲームの場合、キー入力の通知を判定してすぐに表示を行うと、画面表示のタイミングが狂ってしまいます。そのため、キー入力を画面表示のタイミングにあわせられるように変数を設けて、それを判定に用いるようにします。場所は、ファイルの先頭部分、グローバル変数の宣言が並んでいる所です。

WORD wKey;

 作った変数は初期化しなければなりません。場所は、WM_CREATEが通知されたときとします。


wKey = 0;


 キー入力は、WinProc関数の中でWM_KEYDOWNとWM_KEYUPを判定します。WM_KEYDOWNは、キーが押されている間、一定の周期で通知されます。WM_KEYUPは、押されていたキーを離した時に通知されます。

case WM_KEYDOWN:
    wKey = wParam;
    break;
case WM_KEYUP:
    if (wKey == wParam) {    // 最後に押されたキー
        wKey = 0;
    }
    break;



 砲台の表示と移動そのものはインベーダーと同じですから処理も分かりますよね?砲台のビットマップとHDCはインベーダーのものと同じなので追加する必要はありません。しかし、砲台の位置を示す変数が必要になります。場所は posInv を定義した下で良いでしょう。

POINT posHou;


 砲台を最初に表示する場所は、左下になります。画面の一番下に残りの砲台を表示するので、Y座標は 240-48 にしておきましょう。記述位置は、WM_CREATEが通知されたときの処理 DoCreateMain 関数の中になります。

posHou.x = 0;
posHou.y = 240 - 48;
BitBlt( hdcScreen,    // 転送先デバイスコンテキスト
        posHou.x,     // 表示位置 X座標
        posHou.y,     // 表示位置 Y座標
        32,           // 表示サイズ 幅
        24,           // 表示サイズ 高さ
        hdcBmp,       // 転送元デバイスコンテキスト
        64,           // 転送元 X座標
        24,           // 転送元 Y座標
        SRCCOPY );    // そのまま転送


 移動するタイミングは、インベーダーの移動と同じです。WM_TIMERが通知された時の処理で砲台の移動処理を呼び出します。
case WM_TIMER:
    GmMoveInv();
    GmMoveHou();
    InvalidateRect(hWnd, NULL, FALSE);
    break;

 砲台は、キー入力にあわせて移動します。入力されたキーの値は、すでに変数に保存してあるので、その変数の値がカーソルキーかどうかを判定して移動させます。GmMoveHou関数が砲台の移動処理です。左カーソルが押されたときには左へ、右カーソルが押されたときは右へ動くようにします。左の時は-1、右の時は1を GmMoveHouSub関数の引数に入れています。

void GmMoveHou(void)
{
    switch (wKey) {
    case VK_LEFT:
        GmMoveHouSub(-1);    // 左へ移動
        break;
    case VK_RIGHT:
        GmMoveHouSub(1);     // 右へ移動
        break;
    }
}

 GmMoveHouSub関数は、引数に移動方向をとります。

 まず、今表示されている内容を消し、砲台のX座標を変更します。現在のX座標に横幅分の32を加える/引くことで移動を行います。この時、引数の移動方向を使用します。砲台は、左右の端でそれ以上進めなくなるように処理します。座標を変更したら、そこへ砲台を表示します。
void GmMoveHouSub(int d)
{
    PatBlt( hdcScreen,          // 対象のHDC
            posHou.x,           // 表示位置 X座標
            posHou.y,           // 表示位置 Y座標
            32,                 // 表示サイズ 幅
            24,                 // 表示サイズ 高さ
            WHITENESS );        // 白で塗りつぶす

    posHou.x += d*32;
    if (posHou.x < 0) {               // 左端を越えた
        posHou.x = 0;
    } else if (posHou.x > 320-32) { // 右端を越えた
        posHou.x = 320-32;
    }

    BitBlt( hdcScreen,          // 転送先HDC
            posHou.x,           // 表示位置 X座標
            posHou.y,           // 表示位置 Y座標
            32,                 // 表示サイズ 幅
            24,                 // 表示サイズ 高さ
            hdcBmp,             // 転送元HDC
            64,                 // 転送元 X座標
            24,                 // 転送元 Y座標
            SRCCOPY );          // そのまま転送
}


 砲台の方はこれで終わりです。簡単ですね。

 入力が終わったらビルドしてみましょう。砲台の移動は左右のカーソルキーです。

ポポペのプロ~4.右へ左へ

2004-07-10 18:12:55 | Lesson
 まず、前回の続きで、移動後のインベーダーの消去をから始めます。
 インベーダーが移動せずに増殖してしまったのは、移動前のキャラクタを消さなかったためです。
 通常のゲームでは背景があるので消すための処理に工夫がいりますが、今回のプログラムではバックが白1色なので、塗りつぶすだけで消去したことになります。
 インベーダー移動処理の最初に消去する処理を加えます。

void GmMoveInv(void)
{
 PatBlt( hdcScreen,    // 対象のHDC
    posInv.x,          // 表示位置 X座標
    posInv.y,          // 表示位置 Y座標
    32,                // 表示サイズ 幅
    24,                // 表示サイズ 高さ
    WHITENESS );       // 白で塗りつぶす

 移動速度に関しては、タイマそのもののインターバルを長くすることも考えられますが、速度を可変にしたいため、カウンタを設けて時間を調整することとします。
 まず、カウンタを作ります。場所は、ファイルの先頭部分、グローバル変数の宣言が並んでいる所で良いでしょう。

int iInvTime; // 移動タイミングカウンタ

 次に、カウンタを初期化します。初期化はプログラムの最初に1度行えばよいので、WM_CREATEが通知されたときとします。

iInvTime = 0;

 タイマを数えて、1秒経ったらインベーダーが移動するようにします。タイマを200mSに設定しているので、WM_TIMERが5回通知されると1秒になります。その処理をGmMoveInv関数の先頭に追加します。

iInvTime++;
if (iInvTime < 5) {
  return;
}
iInvTime = 0;

 ここまで追加したら、ビルドしてみましょう。今度の動きはいかがでしょうか?インベーダーが右から左へ動いて見えますね。移動量が1キャラクタ分のためカクカクした動きに見えるかもしれませんが、それは移動量を少なくすることによって解消できます。しかし、ここでは以降のプログラムの都合上そのままにしておきます。

ポポペのプロ~3.いよいよプログラム

2004-07-02 22:49:38 | Lesson
 さて、本来ならここでデータ構造を決めた後、プログラムの構造を考えるのですが、そんなことをしていると飽きてしまいそうなので、早くもプログラムを作り始めてしまいます。
 まず、eMbedded Visual C++ 3.0(以下eVC)を起動します。
 プロジェクトを新規作成して、基となるプログラムを作ります。作り方は第1回目にやりましたね。
 最初は、インベーダーを表示するだけのプログラムから始めます。
 まずは、表示するキャラクタを作成します。リソースへビットマップを追加し、そこにインベーダーを描きます。今回は使いませんが砲台や他のキャラクタもここで作っておきましょう。
リソースの追加1
リソースの追加2
リソースの追加3ビットマップの追加
リソースの追加4追加されたビットマップ
 前回、画面サイズについて書きましたが、表示されるキャラクタは画面サイズに密接に関わってきます。320x240がポポペの画面サイズなので、中を動き回るキャラクタはそれよりも小さいほうが画面からはみ出さず、扱いが楽です。
 ということで、今回は、縦横を10分割した32x24を1つのキャラクタのサイズにします。得点を表す数字は1桁を16x24として、2桁で32x24になるようにします。
キャラクタを描く
作成するビットマップ
 ビットマップを作成したら、今度はそれを表示する処理を作ります。
 ビットマップはいったんメモリに読み込んでから使用します。プログラム開始時にデバイスコンテキストハンドル(HDC)を作成し、ビットマップを選択します。
 まず、グローバル変数としてHDCとビットマップのハンドルを用意します。

HDC hdcBmp; // キャラクタ用HDC
HBITMAP hBmp; // キャラクタ用ビットマップ

 このビットマップは、プログラム実行中継続して使用するため、プログラムの最初に読み込みます。場所は、WM_CREATE通知の処理の中です。

case WM_CREATE:
    hwndCB = CommandBar_Create(hInst, hWnd, 1);
    CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0);
    CommandBar_AddAdornments(hwndCB, 0, 0);
    // 画像の準備
    hdcBmp = CreateCompatibleDC(GetDC(hWnd));
    hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
    SelectObject(hdcBmp, hBmp);
    break;

 画面への表示は、WM_PAINTが通知されたときに行います。手順としては、まず画面のHDCを取得し、次にビットマップのキャラクタを画面にコピーします。最後に画面のHDCを解放して終わりです。これで、画面にインベーダーが表示されます。インベーダーは、ビットマップの(0,24)-(31,47)の座標に描かれています。
 文字列を表示している部分は要らないのでコメントにします。消してしまっても構いません。変数szHelloが未使用となるためビルド時にワーニングが出てしまいますので、szHelloの宣言は消しておいて下さい。

case WM_PAINT:
    RECT rt;
    hdc = BeginPaint(hWnd, &ps);
    GetClientRect(hWnd, &rt);
    //LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
    //DrawText(hdc, szHello, _tcslen(szHello), &rt,
    // DT_SINGLELINE | DT_VCENTER | DT_CENTER);
    BitBlt(hdc, 0, 24, 32, 24, hdcBmp, 0, 24, SRCCOPY);
    EndPaint(hWnd, &ps);
    break;

 プログラム終了時には、メモリに読み込んだビットマップとデバイスコンテキストハンドルを解放しなければなりません。

case WM_DESTROY:
    DeleteDC(hdcBmp);
    DeleteObject(hBmp);
    CommandBar_Destroy(hwndCB);
    PostQuitMessage(0);
    break;

 一口に「表示」といっても、これだけの手順が必要になります。ちょっと初心者向きとは言えませんね。しかし、Windowsプログラミングに必要な事なので慣れるしかありません。
 ビルドして実行すると画面左上にインベーダーが表示されます。
シミュレータで実行したところ

ポポペのプロ~2.アイデアを練る

2004-06-27 21:21:38 | Lesson
 あなたには何か作りたいプログラムがありますか?
 もしあればそれを作れば良いのですが、とりあえず何か作りたいという漠然とした欲求を抱えている場合、まず何を作るのか考えなければなりません。
 ここでは、ゲームを考えます。ゲームは「遊ぶためのもの」という明確な目的があるので難しく考えなくてもいいし、作ったものもより多くの人に使ってもらえる利点があります。
 今回作成するのは、インベーダーゲームもどきです(笑)。この「もどき」がつかないとまずい気がする・・・というのは置いといて、処理を簡単にするためインベーダーは1匹、画面上の弾も1発とします。
 動作については、左右に動きまわり弾を落としてくるインベーダーを砲台から弾を発射して打ち落とす、という感じになります。
 次に、画面構成を考えます。
 画面を考える際、そのサイズを考慮しなければなりません。WindowsCEの場合、画面サイズは480x240、640x240、640x480、800x600、240x320とバラエティに富んでいますが、肝心のポポペはというと 320x240 となっています。このサイズはCEマシンとして特殊なため、ポポペで既存のソフトが使えない原因の一つとなっています。
 しかし、巷のソフトが使えなくても、自分で作るとなれば話は別です。どんなサイズであっても自分でそれにあわせて作ってしまえば良いのですから。
 画面いっぱいに使うことを考え、大ざっぱに縦横を10分割すると、1キャラクタのサイズは32x24となります。もちろんこのサイズである必要性はありませんが、今回はこれで行きましょう。インベーダーと砲台、弾と得点表示用の数字、それにゲームとは関係ありませんがアクセサリとしてシールドも作っておきましょう。
 なんだか絵ができると、プログラムも簡単に作れる気がしませんか?プログラミングで重要なのは、完成したものをリアルに思い浮かべることです。プログラミングというのは、あなたの頭の中にあるものをコンピュータを通じて現実とするための手段なのです。
 とはいえ、今回は私の頭の中にあるものを作っているので、あなたが今、完成したものを想像できないとしても仕方のないことです。そこは私が、頭の中身を徐々に目に見える形にしていきますので、段階を追ってどんなものになるのか確認してください。
 絵が決まれば、あとはそれをどう動かすかで、内容が固まります。
 動きは、インベーダーが一定の動作で砲台に近づいてくるようにします。まず、右上に出現し、そこから左へ動きます。左端にきたら一段下がり、今度は右へ動きます。右端にきたら、また一段下がって、左へ動きます。砲台と同じ高さまできたら、砲台を一つ失うこととします。
 インベーダーは、弾を発射します。弾は一定間隔で下へ降りていき、一番下へ落ちると消えます。この時、砲台に弾が当たるとその砲台は破壊されます。
 砲台は、弾を発射することができます。弾は下から上へと昇っていき、一番上までいくと消えます。この弾がインベーダーに当たるとインベーダーが消え、得点になります。
 砲台は、最初3つあり、それらがすべて破壊されるとゲームオーバーです。
 こんなところでしょうか。