前回まででmain関数(エントリポイント)を作成して実行できるようになりました。
現状では何もせずは即終了してしまいます。
今回は、Windowを表示して、即終了せずにユーザー操作で終了できるようにしていきます。
前回の記事:

3Dゲーム開発記事のまとめ:「3Dゲーム開発」
アプリの雛形作成
まずmain関数内でごちゃごちゃ書くよりも、制作アプリの処理を分離できるようにApplicationクラスを作成して、main関数から呼び出すことで、すっきりわかりやすい雛形を作りましょう。
Main関数を変更
まずは全体構造がわかりやすいように、Main関数から構成を見ていきましょう。使用するクラスの詳細な実装は後述していきます。
Main関数を以下のように、Applicationクラスで、初期化 → ループ → 終了という形で構成します。
#include <Windows.h>
#include "Application.h"
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, int)
{
Application* application = new Application();
// アプリ初期化
application->Init(hInst);
// ループ : アプリメインの実装を書くところ
application->Loop();
// アプリ終了処理
application->Terminate();
delete(application);
return 0;
}
こんな形にすることで、main関数自体はすっきりと、制作アプリに必要な実装はApplicationクラス内で、といったわかりやすい雛形ができます。
Applicationクラス
上記で呼び出しているApplicationクラスはひとまず以下のように実装します。
冒頭でも述べたとおり、まずWindowsアプリで必須のWindow表示を行うので、後述するWindowクラスも持たせています。
#pragma once
#include "Window.h"
class Application
{
public:
void Initialize(HINSTANCE hInst);
void Loop();
void Terminate();
private:
// アプリのメインループ
bool gameLoop();
private:
Window window_;
};
#include "Application.h"
void Application::Initialize(HINSTANCE hInst)
{
window_.Initialize(hInst);
}
void Application::Loop()
{
while (true) {
bool result = window_.MessageLoop();
if (result == false) break;
result = gameLoop();
if (result == false) break;
}
}
void Application::Terminate()
{
window_.Terminate();
}
bool Application::gameLoop()
{
return true;
}
Loop関数でwhileループとなり、ここがメインのループ処理になります。
Windowクラスの実装は後述ではありますが、Windowも初期化、ループ、終了処理を呼んでいまず。
そして、Loop関数内の gameLoop()
がこれからゲーム実装をしていくメインのゲームループとなります。
Windowクラス
以下がWindowsの「ウィンドウ」を処理するWindowクラスです。
Windowsのウィンドウ処理はWinAPIを使った定型的な処理です。
あまり深堀りしても意味はないので、基本的にはこう書けばWindowが表示されて、Windowsアプリの土台となるウィンドウ表示ができる、と思っておけば良いでしょう。
WinAPI特有の型や宣言などで分かりづらく感じる部分もありますが、Windowに必要なパラメータと、必要なAPIの呼び出しを行っているだけです。
Windowsアプリケーション自体の実装に興味がある場合、WinAPIやWindowsの実装を調査すると良いでしょう。
ただ、ゲームではない実用系のWindowsアプリなら、C#がフレームワークが豊富でより手軽に実装が可能です、あまりC++でWindowsアプリを掘り下げるのは得策ではないと考えています。
#include <windows.h>
class Window
{
public:
Window();
virtual ~Window();
bool Initialize(HINSTANCE hInst);
bool Terminate();
bool MessageLoop();
static LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void OnResize(UINT width, UINT height);
HWND GetWindowHandle() { return hWindow_; }
private:
WNDCLASS wc_;
HWND hWindow_ = nullptr;
SIZE sizeWindow_;
MSG msg_ = {};
};
#include "Window.h"
#include <tchar.h>
Window::Window()
: sizeWindow_()
{
sizeWindow_.cx = 640;
sizeWindow_.cy = 480;
}
Window::~Window()
{
}
bool Window::Initialize(HINSTANCE hInst)
{
// ウインドウ クラスの登録
wc_.style = CS_HREDRAW | CS_VREDRAW;
wc_.lpfnWndProc = (WNDPROC)Window::MainWndProc;
wc_.cbClsExtra = 0;
wc_.cbWndExtra = 0;
wc_.hInstance = hInst;
wc_.hIcon = LoadIcon(hInst, IDI_APPLICATION);
wc_.hCursor = LoadCursor(hInst, IDC_ARROW);
wc_.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc_.lpszMenuName = nullptr;
wc_.lpszClassName = _T("ShowWindow");
if (!RegisterClass(&wc_)) {
return false;
}
// メイン ウインドウ作成
RECT rect;
rect.top = 0;
rect.left = 0;
rect.right = sizeWindow_.cx;
rect.bottom = sizeWindow_.cy;
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
hWindow_ = CreateWindow(wc_.lpszClassName, _T("ShowWindow"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top,
nullptr, nullptr, hInst, nullptr);
if (hWindow_ == nullptr) {
return false;
}
// ウインドウ表示
ShowWindow(hWindow_, SW_SHOWNORMAL);
UpdateWindow(hWindow_);
return true;
}
bool Window::Terminate()
{
DestroyWindow(GetWindowHandle());
// ウインドウ クラスの登録解除
UnregisterClass(wc_.lpszClassName, wc_.hInstance);
hWindow_ = nullptr;
return true;
}
bool Window::MessageLoop()
{
// Windowsメッセージループ処理
if (PeekMessage(&msg_, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg_); DispatchMessage(&msg_);
}
// アプリケーションが終わるときにmessageがWM_QUITになる
if (msg_.message == WM_QUIT) {
return false;
}
return true;
}
/*static*/ LRESULT CALLBACK Window::MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_DESTROY:
// アプリ終了
PostQuitMessage(0);
return 0;
}
// デフォルト処理(必須)
return DefWindowProc(hWnd, msg, wParam, lParam);
}
結果表示
以上の、Application、Window、main処理を組み込めば以下のように、真っ白なWindowが表示、×ボタンで閉じることが可能となります。
次はDirectXを使った描画の準備を行っていきます。
次回の記事:【3Dゲーム開発 – 11】Direct3D11のセットアップ
3Dゲーム開発記事のまとめ:「3Dゲーム開発」
ソースコード
以下が今回の実装コード・リポジトリです。
コメント