【C】winapiでRPGを作る#1

「ゲームエンジンを使わずRPGを作りたい」
そん思ったことはありませんか？
では
**WINAPI（＋α）だけでRPGを作りましょう**

## 引用元と参考元
わかりやすいのでぜひ見に行ってください
> [かいてい.netさん](http://kaitei.net/winapi/ "Windows API 入門")

> [とある科学の備忘録さん](https://shizenkarasuzon.hatenablog.com/archive/category/Win32%20API "とある科学の備忘録 Win32 API")

> [viva Cocoaさん](https://vivacocoa.jp/winapiprimer/index.php "Windows API Primer   ビットマップ画像の表示")

# 環境とか

・Windows 11
・VSCode V1.95
・windows gcc x64

:::note warn
`VisualStudio`は使いません.`VSCode`一本です.
:::
### 使用ライブラリ

・`windows.h`
・`stdlib.h`
・`stdio.h`
#### 使うかもしれないもの
・`python3`

# 画像描画

コピペ用
```c:main.c
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

//pixel数
int pixel = 48;

//関数宣言
//呼び出し
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//画像描画
int DrowImage(HDC hdc, char* str, int x, int y, int cx, int cy );

//main関数
int WINAPI WinMain
(
    //引数
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow
)
{
    //アプリ関数名
    TCHAR szAppName[] = TEXT("TestApp");
    //変数宣言
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;//スタイル指定
    wc.lpfnWndProc   = WndProc;// ウィンドウプロシージャを登録
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);//アイコン
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);//カーソル
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;//error吐いた時の処理

    // ウィンドウを作成
    hwnd = CreateWindow
    (
        szAppName, 
        TEXT("Title"),//ウィンドウ名
        WS_OVERLAPPEDWINDOW|WS_MAXIMIZEBOX|WS_MINIMIZEBOX,//ウィンドウスタイル
        CW_USEDEFAULT,//表示位置
        CW_USEDEFAULT,//表示位置
        CW_USEDEFAULT,//x 
        CW_USEDEFAULT,//y
        NULL, 
        NULL,
        hInstance, 
        NULL
    );

    if (!hwnd) return 0;//error排他っと期の処理

    //ウィンドウ作成
    ShowWindow(hwnd, nCmdShow);
    //ウィンドウアップデート
    UpdateWindow(hwnd);

    //mainloop
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        //キー入力メッセージを文字メッセージに変換する。
        TranslateMessage(&msg);
        //メッセージをウィンドウプロシージャへディスパッチする。
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

//メッセージ処理
LRESULT CALLBACK WndProc
(
    //引数
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    //変数宣言
    int id;
    HDC hdc;
    PAINTSTRUCT ps;
    LPCREATESTRUCT lpcs;

    //メッセージの分岐
    switch (uMsg) 
    {
    case WM_CLOSE:
        //罰ボタン押された時の処理
        id = MessageBox(hwnd, TEXT("exit?"),TEXT("exit"), MB_YESNO | MB_ICONEXCLAMATION );
        if (id == IDYES)
        {
            MessageBox(hwnd, TEXT("OK"), TEXT("OK"), MB_OK);
            DestroyWindow(hwnd);
        }
        return 0;
    //ウィンドウを消す
    case WM_DESTROY:
        //ウィンドウ破壊
        PostQuitMessage(0);
        return 0;
    case WM_CREATE:
        return 0;
    //ペイント
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        //画像表示
        DrowImage(hdc, "ファイル名.bmp",150, 150, 200,300);//ファイル名.bmpを描画
        EndPaint(hwnd, &ps);
        return 0;

    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

//画像描画   HDCobj,アドレス,場所x,場所y,大きさx,大きさy,
int DrowImage(HDC hdc, char* str, int x, int y, int cx, int cy )
{
    HDC hdc_2;
    //画像読み込み用
    HBITMAP image;
    //image
    image = LoadImage(NULL, str, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    //hdcodj作成
    hdc_2 = CreateCompatibleDC(hdc);
    //select
    SelectObject(hdc_2,image);
    //画像描画
    BitBlt(hdc, x, y, cx, cy, hdc_2, 0, 0, SRCCOPY);
    //hdcobj破壊
    DeleteDC(hdc_2);
    //画像削除
    DeleteObject(image);
    
    //エラーが出れば1を返す
    if (image == NULL){return 1;}
    else{return 0;}
}

```


> [かいてい.netさん](http://kaitei.net/winapi/ "Windows API 入門")

をメッセージウィンドウのコードを ~~参考~~ 引用しています.

このコードでは`ファイル名.bmp`を表示しています.

ここでは`DrowImage`関数に`hdc`と`ファイル名（char*）`と場所を入力することで画像を表示させてます.
ただ、WINAPIの仕様で`bmp`ファイルにしか表示できません.
`jpg`とかは頑張って変換しましょう()

これを改造して動かせるようにします.


# キャラを動かす!

グローバル変数に`int型`の`x`,`y`,`Up`,`Right`,`has_run`を追加します.

```c:main.c
//x,yの座標
int x=0 ,y=0;
//右左左右キーフラグ
int Up = 0,Right = 0;
//走るフラグ
int has_run = 0;
```
また、`WndProc`関数の`switch`文に以下のコードを追加します
```c:main.c
case WM_KEYDOWN:
    int vkey = wParam;
    if (vkey == VK_UP){Up = -1;}//上キー
    if (vkey == VK_DOWN){Up = 1;}//上キー
    if (vkey == VK_LEFT){Right = -1;}//上キー
    if (vkey == VK_RIGHT){Right = 1;}//上キー
    //走りフラグ
    if (vkey == VK_SHIFT){has_run = 1 - has_run;}
    //リセット
    if (vkey != VK_UP&&vkey != VK_DOWN){Up = 0;}
    if (vkey != VK_LEFT&&vkey != VK_RIGHT){Right = 0;}
    InvalidateRect(hwnd, NULL, TRUE);  //再描画
```
:::note info
#### 何をやってるか
ここでは`vkey`にキーの入力を代入しています.
`if`でキーの番号によって`Up`,`Right`を制御しています.
:::

`WndProc`関数の`switch`文の`case case WM_PAINT:`に以下のコードを追加します

```c:main.c
//走るスピード
int run_spped = 10;
//ダッシュする倍率
int run_as = 2;
if (has_run == 1){run_spped = run_spped * run_as;}
        
//座標処理
x += Right*run_spped;
y += Up*   run_spped;

//画像表示
DrowImage(hdc, "プレイヤー.bmp",x-pixel/2, y-pixel/2, 200,300);
```
`プレイヤー.bmp`を移動させています.なので適宜他のものに変換してください.

:::note info
#### 何をやってるか
`run_speed`に`has_run`が真なら`ran_as`を掛けるようにしています.
:::
出来たらこんな感じになるはず
![Title 2024_12_06 18_20_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3926274/dcbd589d-5c60-49d5-29a8-f80f6b07b9ce.png)



# おわり

これで動かせるようになりました.
今回はこれで終わりです.
<details><summary>コピペ用</summary>

```c:main.c

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

//pixel数
int pixel = 48;

//x,yの座標
int x=0 ,y=0;
//右左左右キーフラグ
int Up = 0,Right = 0;
//走るフラグ
int has_run = 0;

//関数宣言
//呼び出し
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//画像描画
int DrowImage(HDC hdc, char* str, int x, int y, int cx, int cy );

//main関数
int WINAPI WinMain
(
    //引数
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow
)
{
    //アプリ関数名
    TCHAR szAppName[] = TEXT("TestApp");
    //変数宣言
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;//スタイル指定
    wc.lpfnWndProc   = WndProc;// ウィンドウプロシージャを登録
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);//アイコン
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);//カーソル
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;//error吐いた時の処理

    // ウィンドウを作成
    hwnd = CreateWindow
    (
        szAppName, 
        TEXT("Title"),//ウィンドウ名
        WS_OVERLAPPEDWINDOW|WS_MAXIMIZEBOX|WS_MINIMIZEBOX,//ウィンドウスタイル
        CW_USEDEFAULT,//表示位置
        CW_USEDEFAULT,//表示位置
        CW_USEDEFAULT,//x 
        CW_USEDEFAULT,//y
        NULL, 
        NULL,
        hInstance, 
        NULL
    );

    if (!hwnd) return 0;//error排他っと期の処理

    //ウィンドウ作成
    ShowWindow(hwnd, nCmdShow);
    //ウィンドウアップデート
    UpdateWindow(hwnd);

    //mainloop
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        //キー入力メッセージを文字メッセージに変換する。
        TranslateMessage(&msg);
        //メッセージをウィンドウプロシージャへディスパッチする。
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

//メッセージ処理
LRESULT CALLBACK WndProc
(
    //引数
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    //変数宣言
    int id;
    HDC hdc;
    PAINTSTRUCT ps;
    LPCREATESTRUCT lpcs;

    //メッセージの分岐
    switch (uMsg) 
    {
    case WM_CLOSE:
        //罰ボタン押された時の処理
        id = MessageBox(hwnd, TEXT("exit?"),TEXT("exit"), MB_YESNO | MB_ICONEXCLAMATION );
        if (id == IDYES)
        {
            MessageBox(hwnd, TEXT("OK"), TEXT("OK"), MB_OK);
            DestroyWindow(hwnd);
        }
        return 0;
    //ウィンドウを消す
    case WM_DESTROY:
        //ウィンドウ破壊
        PostQuitMessage(0);
        return 0;
    case WM_KEYDOWN:
        int vkey = wParam;
        if (vkey == VK_UP){Up = -1;}//上キー
        if (vkey == VK_DOWN){Up = 1;}//上キー
        if (vkey == VK_LEFT){Right = -1;}//上キー
        if (vkey == VK_RIGHT){Right = 1;}//上キー
        //走りフラグ
        if (vkey == VK_SHIFT){has_run = 1 - has_run;}
        //リセット
        if (vkey != VK_UP&&vkey != VK_DOWN){Up = 0;}
        if (vkey != VK_LEFT&&vkey != VK_RIGHT){Right = 0;}
        InvalidateRect(hwnd, NULL, TRUE);  //再描画
    case WM_CREATE:
        return 0;
    //ペイント
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        //画像表示
        //走るスピード
        int run_spped = 10;
        //ダッシュする倍率
        int run_as = 2;
        if (has_run == 1){run_spped = run_spped * run_as;}

        //座標処理
        x += Right*run_spped;
        y += Up*   run_spped;       

        //画像表示
        DrowImage(hdc, "プレイヤー.bmp",x-pixel/2, y-pixel/2, 200,300);
        EndPaint(hwnd, &ps);
        return 0;

    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

//画像描画   HDCobj,アドレス,場所x,場所y,大きさx,大きさy,
int DrowImage(HDC hdc, char* str, int x, int y, int cx, int cy )
{
    HDC hdc_2;
    //画像読み込み用
    HBITMAP image;
    //image
    image = LoadImage(NULL, str, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    //hdcodj作成
    hdc_2 = CreateCompatibleDC(hdc);
    //select
    SelectObject(hdc_2,image);
    //画像描画
    BitBlt(hdc, x, y, cx, cy, hdc_2, 0, 0, SRCCOPY);
    //hdcobj破壊
    DeleteDC(hdc_2);
    //画像削除
    DeleteObject(image);
    
    //エラーが出れば1を返す
    if (image == NULL){return 1;}
    else{return 0;}
}


```

</details>