ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

DXライブラリとWindowsAPIの連携その5【外部ファイル読み込み】

書き手:肥田野

前回はダイアログボックスにコントロールを置いて、ユーザーが行った設定をゲームに反映させる処理を行いました。

最終回となるこの記事では、画像ファイルをプログラムの実行中に読み込ませてみたいと思います。

DXライブラリにはLoadGraphという便利な関数があります。DXライブラリでゲームを作ったことのある人にはお馴染みですね。

これはファイルの絶対パスまたは相対パスを指定すると、その画像をメモリに読み込み、呼び出すためのハンドルを返してくれます。

通常はプログラムの起動時、もしくはシーンの切り替え時などに複数枚まとめて読み込むといった使い方をしますが、ここではWindowsAPIの「ファイルを開く」機能を使って、プログラムの実行中に新たに呼び出してみましょう。

Wordやメモ帳といったファイル編集ソフトにはほぼ必ず付いているあの機能です。本来は色々細かい指示を要求されるのですが、今回は絶対パスさえ分かればよいので比較的短く書けたのではと思います。

それではソースコードを見ていきましょう。

#include<DxLib.h>
#include<windowsx.h>
#include"resource.h"

#define ID_CHECK 100

WNDPROC dxWndProc;
HWND hMainWnd;
HINSTANCE hInst;
int FPS = 60;

//画像ハンドルの初期値は-1(=MyOpenでのエラー値)
int gHandle = -1;

int SettingBeforeInit(bool);
int SettingAfterInit();
LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
BOOL CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp);

//ファイル読み込みの関数。引数は親ウィンドウのハンドルで、返り値はLoadGraphの結果
int MyOpen(HWND);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) {
    hInst = hInstance;
    SettingBeforeInit(true);
    DxLib_Init();
    SettingAfterInit();

    int x = 320,vec = 5;

    while (!ProcessMessage() && !ScreenFlip() && !ClearDrawScreen()) {
        int startTime = GetNowCount();
        if (x + vec > 640) {
            x = 640;
            vec *= -1;
        }
        if (x + vec < 0) {
            x = 0;
            vec *= -1;
        }
        x += vec;

        //gHandleがエラーか否かで処理を分岐
        if (gHandle != -1) {
            DrawGraph(x, 240, gHandle, TRUE);
        }
        else {
            DrawCircle(x, 240, 5, 0xffffff, TRUE);
        }
        int endTime = GetNowCount();
        WaitTimer((1000 / FPS) - (endTime - startTime));
    }
    DxLib_End();
    return 0;
}

int SettingBeforeInit(bool wndMode) {/*省略*/}

int SettingAfterInit() {/*省略*/}

LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case WM_COMMAND:
        switch (LOWORD(wp)) {
            //「ファイル読み込み」メニューが選択された場合
        case IDM_FREAD:
            MyOpen(hWnd);
            break;
        case IDM_DIALOG:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, MyDlgProc);
            break;
        case IDM_END:
            SendMessage(hWnd, WM_CLOSE, NULL, NULL);
            break;
        }
        break;
    }
    return CallWindowProc(dxWndProc, hWnd, msg, wp, lp);
}

int MyOpen(HWND hWnd) {
    
    //ファイルを開く為の設定用構造体
    OPENFILENAME ofn;
    //ここに指定ファイルの絶対パスが代入される
    char szFile[MAX_PATH];
    //memset関数で変数・配列の中身を全てNULL、\0に
    memset(&ofn, NULL, sizeof(OPENFILENAME));
    memset(szFile, '\0', sizeof(szFile));

    //自分自身のサイズ
    ofn.lStructSize = sizeof(OPENFILENAME);
    //親ウィンドウのハンドル
    ofn.hwndOwner = hWnd;
    //表示させるファイルのフィルタリング
    ofn.lpstrFilter = "PNG Files {*.png}\0*.png\0"
        "JPEG Files {*.jpg}\0*.jpg\0"
        "BITMAP Files {*.bmp}\0*.bmp\0\0";
    //パスを代入する配列
    ofn.lpstrFile = szFile;
    //パスの最大文字数
    ofn.nMaxFile = MAX_PATH;
    //ファイル名で拡張子が指定されなかった場合に追加する文字列
    ofn.lpstrDefExt = ".png";
    //ウィンドウの名前
    ofn.lpstrTitle = "画像ファイルの選択";

    //ファイルオープンに失敗したらreturn
    if (GetOpenFileName(&ofn) == 0) return -1;

    //ファイル読み込み
    gHandle = LoadGraph(szFile);
    return 0;
}

BOOL CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) {/*省略*/}

コーディングをする前に、リソースエディタでメニューに「ファイルを開く」とでも書かれたメニューを追加してください。IDは任意の文字列でよいですが、このコードでは「IDM_FREAD」としています。

そういえば、私の環境ではメニューを追加した直後はIDが読み込まれない現象が発生しましたが、VisualStudioを再起動したら直りました。参考までに。

前回FPSを操作した際、変化が分かりやすいように左右に動く球を描画しました。今回はその球を画像に差し替えてみたいと思います。

グラフィックハンドルはグローバル変数にしています。

WinMainのwhile文も、画像の表示に合わせて修正しています。

画像がロードされていないときは前回と同じ球を表示させるように、graphicHandleの初期値は-1にしています。LoadGraphに失敗してグラフィックハンドルが取得できなかったときも-1が代入されるので、これで必ず画像か球が描画されます。

メニューを増やしたので、MyProc関数でswitch文の要素を書き足します。ただし、ファイルの読み込みは少し処理が長いので、MyOpen関数を新たに作って、その中で処理を書いています。

MyOpenでは大まかに、OPENFILENAME構造体に必要な情報を設定して、GetOpenFileName関数で起動、szFile配列にパスを代入しています。

詳細はコメント文に書いたので読んでいただければと思います。特に注意する点は、memset関数でofnとszFileを初期化するのを忘れないようにすることぐらいでしょうか。

ofn.lpstrFilterではちょっと特殊なテキストの書き方をしますが、これがどのように表示されるかは、実際に実行して確認してみると分かりやすいと思います。

またofn.lpstrDefExtでは、拡張子が入力されなかったときに「.png」を自動で差し込むようにしています。

あまり使う場面はないかもしれませんが、選択ウィンドウ内のファイル名から意図的に拡張子が消されたときに、エラーにならずPNGファイルとして取り扱われます。ただし、ファイル形式によっては読み込めないようなので過信は禁物です。まあユーザーが最初から余計なことをしなければいいんですけどね。

上手く画像を開くことが出来ましたか? WindowsAPIではこのように、必要最低限の情報を指定するだけで、多くのユーザーが一目で分かるUIでファイルを読み込むことが出来ます。

これに近い方法でファイルの上書き保存、名前をつけて保存も出来るので、興味のある方は調べてみてください。

さて、5回にわたってDXライブラリのゲームにWindowsAPIの機能を実装する方法をご紹介しました。他にも様々な機能があるので、DXライブラリと組み合わせることで、よりソフトウェアの可能性が広がるのではないかと思います。

もちろんWindowsAPIだけでゲームを作ることも可能ですが、DXライブラリの利便性とWindowsAPIの多機能性を両方生かしてコーディングができると良いですね。