【C++】Windowsで2Dアクションゲームを作っていく解説2 ~ウィンドウ表示とゲームループの作成~

created by DALLE3 2025.03.09 C/C++
created by DALLE3

こんにちわ(^-^)/
前回の記事から結構空いてしまったのですが、Windows 2Dゲーム作成の記事を書いていこうと思います。

なんか当ブログにアクセスしてくる人の大半がこの記事を見て帰ってるんですよね。
需要あるんでしょうか?

そんなに見られたら解説2以降も書かないといけない気がするじゃないですか(´艸`*)ヤダー
別にいいのですが。

こんなご時世に今更ファミコンかよ?って思われるかもしれませんが、一定層には結構人気のジャンルだったりするわけで、それもこの記事が注目される要因なのかな、と思ったりもしてます。近年、リバイバルブームと呼ばれるセンセーションが存在するのは確かです。


このブログでは、ファミコンゲームを題材としてC++のゲームプログラミングを行っています。
ファミコン時代は横スクロール型のアクションゲームが全盛期でしたが、その代表格とも言える作品をWindowsで復活させようとするプロジェクトを進行しています。

そう、「ロックマン」をWindowsに移植する企画です!
(そこはスーパーマリオじゃないのかよ!と思ったらダメです)

そんなわけでそのプロジェクトを題材にして、Windowsで2Dアクションゲームを制作する過程を具体的な技術情報を交えて紹介していってみよう、と言うのがこのエントリーの趣旨になります。

今回はその第2弾です。
今回はWindowsでVisual Studioを使い、C++のGUIアプリケーションとしてゲームプログラムの根幹部分を構成する部分を作成していきます。


Windowsでファミコンゲームを再現するために必要なこと

これから始める人

まずは座学です。(さっさと作れよ、と思った方は次の見出しに進んでください)

まずファミコンについてですが、これは言うまでもなく8bitの機械です。任天堂が作りました。
そんなちっぽけな8bitのゲーム機で日本中100万人の子供たちを魅了していたと考えると、ファミコンがいかにすごい存在だったかが伺えますよね。

そんなファミコンにはゲーム専用にカスタマイズされたCPUを持っていました。それでテレビにあの独特なドット絵やチップチューン(ピコピコ音楽)が燦々と輝いていたのです。

一方のWindowsですが、そんなものとは比べ物にならないくらい高機能のOSです。これはMicrosoftが作りました。2025年現在主流なのは64bit OSです。
Windows 11やWindows 10などがあります。

そんなWindowsでならファミコンなんて楽勝で再現できるだろう、と考えるかと思います。

まあ外れではないのですが、いくつか考えていかないといけない事があります。

まずは当然ですが、Windowsでゲームを作れるのかどうかという事です。
そしてファミコンのゲームを再現するとして、あの独特の世界観をどう表現するのか、という事です。

これらを実行できなければそもそも話が始まりません。
まあそれを解説するのがこの特集記事なのですが。

Windowsという環境はあっても、そこで動作するプログラムを作る技量があるかどうかは別問題です。
それはあなた自身が身につける他ありません。

そのためにまずやるべき事を洗い出してみました(^^)

  • Windowsのアプリケーション(ソフトウェア)の作り方を学ぶ
  • ゲームプログラミングの流儀を知る
  • 作ろうとしているもの(ファミコン)の制約について知る
  • 必要な環境を揃える
  • 段階的な制作工程を確認し、マイルストーンを作成する(任意)

ざっと並べてこんな感じです。

この中で最も重要で難しい事は、「Windowsのアプリケーション(ソフトウェア)の作り方を学ぶ」でしょう。いわゆるプログラミングの学習です。
これが最も時間と労力を要すると思います。


【★大事!】ゲームを開発する時に忘れないでほしい事

ゲームを作る事は、それだけ時間が掛かるので最初から大きな目標を掲げて進みだすのはあまりおすすめしません。
目先のゴールにばかり気を取られて、ゲームを作るために必要な過程を甘く見た結果、あなたは悲観する他ありません。

現実、半分以上のゲーム作りたい意思表明者?はそんな感じで煙のように蒸発して消えていくからです。

そうならないためにはスタートの目標を低く設定して、目標を徐々に引き上げていくスタンスが必要になってきます。
なぜそうしないといけないのか?

なぜならこういった創作は、楽しい時間であるからこそ意味を成すからです。ゲームは楽しむためにする憩いなので、作り手もその楽しさを感じなければ本当に良いゲームは生み出せません。

これは当たり前のようで、最も大事な事です。絶対に忘れないようにしましょう。


ウィンドウの作成

このエントリーでは、Microsoft Visual Studio 統合開発環境を使用した説明をしています。
それ以外のコンパイラにおけるコンパイル等については、それぞれのコンパイラの解説を参照して適宜読み替えてください。

ゲーム用ライブラリの制定(DXライブラリ)

それでは早速始めていきましょう٩(′ω′)و

まず初めにゲームが動作する画面(ウィンドウ)が生成されなければ意味がありませんね。
Windowsのアプリケーションの基本となるウィンドウ生成について説明していきます。

まずは前回に記事のおさらいを兼ねて説明していきます。

#include <Windows.h>

int WINAPI WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow
) {
    return 0;
}

このソースコードをcppファイルとしてプロジェクトの中に保存し、コンパイルして結果を確認してください(ビルドとも呼ばれます)。
Visual Studioの場合は、デバッグ(D)→デバッグの開始(S)です。

うまくコンパイルできた場合は
「プログラム ‘[###] <projectName>.exe’ はコード 0 (0x0) で終了しました。」
が出力されるはずです。

この時点でコンパイルがうまくいかない場合は以下を確認してみて下さい。

  1. プロジェクトのプロパティ(Alt+Enter)→構成プロパティ→リンカー→システム→サブシステムの値が「Windows (/SUBSYSTEM:WINDOWS)」になっているか確認する
  2. 「エラー一覧」に何か表示されていないか確認する。表示されている場合は問題を解決する
  3. cppファイル(.cpp、.cc)にソースコードが記載されているか確認する

ちなみにこの時点ではまだウィンドウは生成されませんので、そこはご安心ください(^-^;


さて次はゲームの動かす画面を生成していきます。

方法としては、Windows APIを使う方法が一般的です。

Windows API(ウィンドウズ エーピーアイ)とは、「Windows上で動くアプリを作るための機能をまとめたもの」です。API(Application Programming Interface)という言葉は「プログラムがOS(Windows)とやり取りするためのルールや命令のセット」という意味です。

簡単に言うと、Windows APIを使うことで、プログラムが以下のようなことをできるようになります。

ウィンドウを作る(ゲームの画面やアプリのウィンドウ)
ボタンやメニューを作る(アプリのUI)
キーボードやマウスの入力を受け取る
音を鳴らす
ファイルを読み書きする
ネットワーク通信をする

しかしこの仕組みを使ってウィンドウを生成するよりももっと簡単にウィンドウを生成する事ができる方法を今回利用します。

それがDXライブラリと呼ばれるC++用のライブラリです。

DXライブラリ置き場 HOME

上記サイトに記載されている通り、このライブラリを導入する事でWindowsアプリケーションやDirectXなどのゲーム開発用関数類が簡単に利用する事ができますヾ(*´∀`*)ノワーイ

世の中便利なものが手に入る時代なんですね~スバラシイ

というわけで、このライブラリを使わせてもらう事にしましょう。

DirectXとは

DirectX(ダイレクトエックス) は、Windowsでゲームや3Dグラフィックを動かすための機能(API)をまとめたもの です。
特に、高速な描画 や リアルな3D表現 を実現するために使われます。
XBox Oneのゲームはこの技術を使用して開発が行われています。

「DXライブラリのダウンロード」をクリック。

ここではVisual Studio用のライブラリをクリックしてファイルを取得します。
(他のコンパイラを使用している方は同じコンパイラ名が書かれているものをファイルを取得してください)

ダウンロードしたzipを解凍して、階層の浅い場所へ置いてください。
(例えばCドライブ直下の空フォルダなど)

フォルダの名前は分かりやすい名前に変えておけばOK

次に、プロジェクトでDXライブラリを使用するためにいくつかのプロジェクトの設定を変更する必要があります。


ライブラリの(プロジェクトへの)インクルード

Visual Studioでプロジェクトを選択し、プロジェクトのプロパティを開きます。

まずはプロパティウィンドウの上部にある「構成(C)」を「すべての構成」に変更します。
「プラットフォーム(P)」を「すべてのプラットフォーム」に変更します。

「C/C++」→「全般」→「追加のインクルードディレクトリ」に先ほど配置したDXライブラリのフォルダパスを設定します。
(例:C:\C++\DxLib)

次に「リンカー」も同様にライブラリのフォルダパスを設定します。
「リンカー」→「全般」→「追加のライブラリ ディレクトリ」に先ほど配置したDXライブラリのフォルダパスを設定します。
(例:C:\C++\DxLib)

追加したら、OKボタンを押して画面を閉じてください。

これでDXライブラリを使う必要最低限の準備ができました。
一度サンプルコードでライブラリの関数を使ってみましょう( `・∀・)ノ

次のコードをcppファイルに貼り付けて、コンパイルを行ってください。

#include <Windows.h>
#include "DxLib.h"

int WINAPI WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow
) {
    DxLib::ChangeWindowMode(TRUE);  // ウィンドウモードを指定
    DxLib::SetGraphMode(640, 480, 32);  // ウィンドウのサイズを指定
    DxLib::DxLib_Init();  // ウィンドウを生成

    /* キー入力されるまで待機する */
    while (DxLib::CheckHitKeyAll() == 0) {
        if (DxLib::ProcessMessage() == -1) {
            break;
        }
    }

    DxLib::DxLib_End();  // ウィンドウを破棄
    return 0;
}

デバッグを開始してください。

次のようなウィンドウが生成されたら成功です。

いとも簡単にウィンドウが生成できました!


#include “DxLib.h”
を書くとDXライブラリの関数群を使用する事ができます。

今回はDXライブラリを使った動作検証なので解説は省略しますが、DXライブラリのウェブサイトに関数の使用方法やサンプルコードがたくさんありますので、色々試してみるのもいいと思います。

ここまでをDirectX APIなどでやろうとすると軽く数日は掛かりますので、DXライブラリは知識がなくとも簡単にゲーム開発に取り掛かれる魔法の杖のようなライブラリなのです。

作成者には感謝しかありませんね。素晴らしいです(^^)


ゲームループの作成

処理の基本構造

まずゲームの仕組みについて説明していきます。

そもそもゲームはどういった仕組みで実行されているのか。それらの処理を分解して解説していくと以下のようになります。

みなさんも想像はつくかと思いますが、例えば十字キーを押せばプレイヤーオブジェクトが移動しますよね。

それは言い換えると、キーを押すと画面の出力内容が変わるという事です。

更に言い換えると、コントローラなどで入力した情報がゲームプログラム内部へと伝達され、プログラムが入力値を解釈し、画面へと出力している、と言えます。

つまり、キーの入力と内部の演算処理、画面に出力する工程に分けられるわけです。

この工程を繰り返し続ける事で、ゲームは成り立っています。

それをプログラミング的に変換すると、ループ処理の中でキー情報を取得し、動作させたいように内部で値を計算して、それの結果を画面に表示する事になります。


ループ処理を実装する

通常であればゲームアプリケーションを制作するにあたり考えなければならない事が3つあります。

  • Windowsウィンドウメッセージ処理
  • フレームレート制御(60FPSなど)
  • 画面の更新

これはWindowsというOSでゲームを実行する際には必ず考慮する必要があり、これらを無視してWindowsでゲームを作ろうとしても残念ながらまともに動いてくれません。

しかしラッキーな事にDXライブラリを使えば、それらの必要な対応を全て裏で行ってくれます!

まず初めにゲームプログラムの根幹となるループ部分で毎回必要な処理を行わせます。

DxLib::SetDrawScreen(DX_SCREEN_BACK);

/* キー入力されるまで待機する */
while (!DxLib::ProcessMessage()) {
    DxLib::ClearDrawScreen();  // 画面クリア

    DxLib::ScreenFlip();  // ダブルバッファリング
}

whileループで毎回「ProcessMessage関数」を実行する事で、Windowsのウィンドウメッセージ処理を自動的に実行してくれます。
Windowsのメッセージ処理については説明が長くなってしまうので割愛しますが、ウィンドウがたくさんあって、それを自由に操作して、といったOSにとってその情報をPC内部で制御するために根幹部分で多くのメッセージのやりとりがあるんだよ、とだけ覚えておきましょう。

また、直接画面に対してグラフィック画像などを描画すると画像描画処理とモニターの画像更新周波数が不一致のため、変な画像が表示されたりします。

それを回避するために裏画面に画像を作って、それを表画面に反映させるような仕組み(いわゆるダブルバッファリング)を利用する必要があります。

そのために
「SetDrawScreen関数」で裏画面に描画するように指定し、
「ClearDrawScreen関数」で表画面の描画内容を消し、
「ScreenFlip関数」で裏画面の内容を表画面に転記するようにします。

これで画面がチラついたりする事を防ぎます。

そして問題となるのがフレームレート処理です。

これは一般的にPCでゲームを実行する環境ではエミュレーション速度(俗に言うFPSです)が60となっています。
つまり1秒間に60回画面更新をします。

これを実現するために「WaitTimer関数」を使って1/60秒分の処理にするのですが、実際にどのくらい待てばいいのでしょうか?
そこが課題となります。

#include <Windows.h>
#include "DxLib.h"

int WINAPI WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow
) {
    int x, y;
    int GraphHandle;

    DxLib::ChangeWindowMode(TRUE);  // ウィンドウに指定
    DxLib::SetGraphMode(640, 480, 32);  // ウィンドウのサイズを指定
    DxLib::DxLib_Init();  // ウィンドウを生成
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    GraphHandle = DxLib::LoadGraph(L"画像/サンプル1.png");
    x = 0; y = 0;

    /* メインのゲームループ */
    while (!DxLib::ProcessMessage()) {        
        DxLib::ClearDrawScreen();  // 画面クリア

        if (DxLib::CheckHitKey(KEY_INPUT_LEFT) == 1) x -= 8;
        if (DxLib::CheckHitKey(KEY_INPUT_RIGHT) == 1) x += 8;
        if (DxLib::CheckHitKey(KEY_INPUT_UP) == 1) y -= 8;
        if (DxLib::CheckHitKey(KEY_INPUT_DOWN) == 1) y += 8;
        
        DxLib::DrawGraph(x, y, GraphHandle, FALSE);  // 画像を描画

        DxLib::ScreenFlip();  // ダブルバッファリング
        DxLib::WaitTimer(20);  // 50分の1秒待つ
    }

    DxLib::DxLib_End();  // ウィンドウを破棄
    return 0;
}

試しに”サンプル1.png”という画像ファイルを用いて、画像を描画してみます。

ファイルの場所は「GraphHandle = LoadGraph(L”***/サンプル1.png”);」の箇所で指定します。

これで実行してみましょう。

キーボードの矢印キーを押せば、サンプル1.pngの画像が動くと思います。

実際にはこれでもまともに画像が表示されていそうですが、FPSはモニターによって変わっているためゲーム用の高リフレッシュレート対応のモニターでは動作速度が変わってしまいます。

120Hzのモニターでは画像が高速で動きます。

60Hzのモニターではリフレッシュが≒16.66msなのに対し、120Hzのモニターではリフレッシュが≒8.33msに1度行われます。

これを制御するために、たとえば「FPSは60固定とする」などに対応させる必要があります。

上記のサンプルコードでは「WaitTimer(20)」と書いてありますよね。
(20ミリ秒処理を止める)

でもこれはとりあえずの数値なので、20ミリ秒処理を止めるだけでは60fpsに固定されません。

60Hz(一般的なPCモニターのリフレッシュレート)のモニターで、20ミリ秒のWaitTimerを設定した場合はおよそ50fpsになります。

さきほどの実行画面をよく見てみると、ほんの少し画像がカクついて移動していますよね。
この挙動はそのせいです。

ではこれらの値を計算して、メインループの処理に適用していきましょう。

#include <Windows.h>
#include "DxLib.h"
#include <chrono>
#include <thread>

class Fps {
private:
    using Clock = std::chrono::steady_clock;
    Clock::time_point lastTime;
    double frameDuration;

public:
    explicit Fps(int targetFps)
        : lastTime(Clock::now()), frameDuration(1000.0 / targetFps) {
    }

    void wait() {
        auto now = Clock::now();
        double elapsed = std::chrono::duration<double, std::milli>(now - lastTime).count();
        if (elapsed < frameDuration) {
            std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(frameDuration - elapsed)));
        }
        lastTime = Clock::now();
    }
};

int WINAPI WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow
) {
    int x, y;
    int GraphHandle;

    DxLib::ChangeWindowMode(TRUE);  // ウィンドウに指定
    DxLib::SetGraphMode(640, 480, 32);  // ウィンドウのサイズを指定
    DxLib::DxLib_Init();  // ウィンドウを生成
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    Fps fps(60);  // 60FPSで固定

    GraphHandle = DxLib::LoadGraph(L"画像/サンプル1.png");
    x = 0; y = 0;

    /* メインのゲームループ */
    while (!DxLib::ProcessMessage()) {
        fps.wait();  // FPS調整
        
        DxLib::ClearDrawScreen();  // 画面クリア

        if (DxLib::CheckHitKey(KEY_INPUT_LEFT) == 1) x -= 8;
        if (DxLib::CheckHitKey(KEY_INPUT_RIGHT) == 1) x += 8;
        if (DxLib::CheckHitKey(KEY_INPUT_UP) == 1) y -= 8;
        if (DxLib::CheckHitKey(KEY_INPUT_DOWN) == 1) y += 8;
        
        DxLib::DrawGraph(x, y, GraphHandle, FALSE);  // 画像を描画

        DxLib::ScreenFlip();  // ダブルバッファリング
    }

    DxLib::DxLib_End();  // ウィンドウを破棄
    return 0;
}

フレームレートを固定するためのウエイトを計算するfpsクラスを作成しました。

このクラスを使って、メインループの中で毎回呼び出しましょう。
(それに伴ってWaitTimer関数は削除しました)

さきほどよりもカクつきがなくなっている事がわかると思います。

これでグラフィックを描画するために必要最低限のゲームループの処理が整いました!

後は実際にゲームを動作させる処理をガリガリコーディングしていけばOKです( `・∀・)ノ

ここまでのソースコードで分からない事もあるかもしれませんが、Windowsの仕組み上どうしても必要な箇所や、ゲームプログラムに必要な部分は頑張って覚える必要はありません。

これらは概要くらいのレベルで理解しておけば大丈夫です(゚Д゚)/

今回の解説はここまで。

次回はファミコン素材の作成とスプライトの描画をするところまで進めたいと思います。


【コラム】作成したFpsクラスについて

この記事で作成したFpsクラスについての解説です。

ゲーム開発とは別のお話です。
興味ある人は見てみてね☆


class Fps {
private:
    using Clock = std::chrono::steady_clock;
    Clock::time_point lastTime;
    double frameDuration;

public:
    explicit Fps(int targetFps)
        : lastTime(Clock::now()), frameDuration(1000.0 / targetFps) {
    }

    void wait() {
        auto now = Clock::now();
        double elapsed = std::chrono::duration<double, std::milli>(now - lastTime).count();
        if (elapsed < frameDuration) {
            std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(frameDuration - elapsed)));
        }
        lastTime = Clock::now();
    }
};

この Fps クラスは、フレームレートを固定する ためのクラスで、
ゲームが 60FPS(1秒間に60回描画)で動くように制御する役割を持っています。


クラスの構成

class Fps {
private:
    using Clock = std::chrono::steady_clock; // 高精度な時間計測用のクロック
    Clock::time_point lastTime; // 前のフレームの時間を記録
    double frameDuration; // 1フレームの理想的な時間(ミリ秒単位)

public:
    explicit Fps(int targetFps); // FPSを指定するコンストラクタ
    void wait(); // FPSを維持するための待機処理
};
メンバ変数説明
Clock::time_point lastTime前回のフレームの時間を記録する変数
double frameDuration1フレームにかかる理想的な時間(ミリ秒)
メンバ関数説明
Fps(int targetFps)FPSの目標値を設定し、1フレームに必要な時間を計算
void wait()FPSを維持するために、必要な時間だけ待機する

コンストラクタ

explicit Fps(int targetFps) 
    : lastTime(Clock::now()), frameDuration(1000.0 / targetFps) {}

処理の流れ

  1. Clock::now() を使って現在の時間を lastTime に記録
    → これで「前回のフレームの開始時間」を持つことができる
  2. frameDuration1フレームにかかるべき時間(ミリ秒) を計算してセット
    • 1000.0 / targetFps → 例えば 60FPS なら 1000.0 / 60 = 16.666 ミリ秒

つまり、
FPS 60 の場合、 1フレームは 16.666ミリ秒で処理 するように設計される!(`・ω・´)


wait() メソッド

void wait() {
    auto now = Clock::now(); // 現在の時間を取得
    double elapsed = std::chrono::duration<double, std::milli>(now - lastTime).count(); // 経過時間をミリ秒で取得

    if (elapsed < frameDuration) {
        std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(frameDuration - elapsed)));
    }

    lastTime = Clock::now(); // 次のフレームの基準時間を更新
}

処理の流れ

  1. Clock::now()現在の時間 を取得
  2. elapsed前回のフレームからの経過時間(ミリ秒) を計算
  3. もし elapsedframeDuration より 短い 場合、std::this_thread::sleep_for()待機
    • 例えば、経過時間が 10ミリ秒 なら、16.666ミリ秒 – 10ミリ秒 = 6.666ミリ秒待つ
    • これにより、1フレームの長さを 16.666ミリ秒に調整 できる
  4. lastTime を更新して、次のフレームの基準時間にする

メインループでの使用

Fps fps(60);  // 60FPSで固定

while (!DxLib::ProcessMessage()) {
    fps.wait();  // FPS調整

    DxLib::ClearDrawScreen();  // 画面クリア
    DxLib::ScreenFlip();  // ダブルバッファリング
}

この fps.wait(); を呼び出すことで、
毎フレーム 16.666ミリ秒ごとに処理 されるので、
ゲームの動作が 安定して 60FPS に固定 される!✨


まとめ

Fps クラスは、ゲームのフレームレートを一定に保つ ためのクラス
std::chrono を使って、前回のフレームからの時間を測定 し、必要なら待機 する
✅ メインループで fps.wait(); を呼ぶことで、ゲームの動作を 60FPSに固定 できる

これで、どんなPCでも 一定のスピード でゲームが動作するようになるよ!
(処理落ちしない限りね!(๑•̀ㅂ•́)و✧)


参考情報です。

1フレームの時間からFPSを求める計算式

FPS(フレームレート)は、1秒間(=1000ミリ秒)を 1フレームの時間 で割ることで求められるよ。

コメント

タイトルとURLをコピーしました