PDAのひとりごと

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

ポポペのプロ、忘れ物

2004-07-24 16:53:13 | Lesson
「ポポペのプロ~4.動かさないと」が抜けていたことに気がつきました。
別のところにまとめておきましたので、そちらを参照してください。

もっとも、誰も指摘しなかったということは、誰も気にしてなかったと言うことでしょうね・・・(^^;

ポポペのプロ~インデックス

ポポペのプロ~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キーを押すと弾が発射されます。発射された弾は勝手に上へ飛んでいき、上端で消えます。まだ当たり判定を入れていないため、インベーダーに当たっても何も起こりませんが、弾とインベーダーの位置関係によってはおかしな表示になるのが分かるでしょうか?これについては後ほど解決することになります。

ちょっと小休止

2004-07-10 22:17:25 | Weblog
さっきから入力していて、どうもリストが思い通りに表示できないことが分かりました。

不等号がHTMLのカッコとしてとられたり、returnが命令(?)として扱われるのか、表示に出なかったり、非常に面倒なことになっています。

どこかおかしな所が見つかったらお知らせ願います。

ポポペのプロ~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プログラミングに必要な事なので慣れるしかありません。
 ビルドして実行すると画面左上にインベーダーが表示されます。
シミュレータで実行したところ