ゲーム好きがプログラマを目指すブログ

いつかゲーム作れるようになれたらいいなぁ。。。と思う日々

Win32APIのお勉強をしました(ウィンドウ生成~文字の描画)。後編

 後編ということで、文字列の描画編
 最終目標のタイピングゲーム作成までの道のりは長い。。。
 Win32の専門書とか買ったほうがいいのかも

【使う関数】
 文字列を表示するには、TextOut関数を使えばおk。デバイスコンテキストへのハンドラ(HDC)とかいう謎の構造体がでてきたが、これに対して描画指示を送ると画面に文字が表示される。(多分デバイスはディスプレイとかを指す?)

BOOL TextOut{
	HDC hdc,		//デバイスコンテキスト
	int x,			//位置(文字列の左上)
	int y,			//位置(文字列の左上)
	LPCSTR lpString,	//表示する文字列
	int c			//lpStringの文字数、lstrren(lpString)で文字数を取得できる
};

ここで使うHDCの取得には、BeginPaint()かGetDC()を使えばよさげらしいけど、今回はGetDC()を使ってみる。GetDCで取得したHDCは、使い終わったらReleaseDC()で解放する。
ということで、文字を描画する部分(ウィンドウプロシージャ内に書く)はこんな感じになる。

HDC hdc;
hdc = GetDC(hWnd);	//hWndは前回のWinProcの第一引き数
TCHAR output = _T("Hello, Win32API");
TextOut(hdc, 0, 0, output, lstrren(output));
ReleaseDC(hWnd, hdc);


【実際に表示してみる】
さっきのコードを、WinProcのWM_PAINTイベントが発生した場合の部分に追記してみる。

LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	TCHAR output[] = _T("Hello, Win32API");
	
	switch(uMsg){
	case WM_PAINT:
		hdc = GetDC(hWnd);
		TextOut(hdc, 0, 0, output, lstrlen(output));
		ReleaseDC(hWnd, hdc);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
};

こんな感じで表示された。

f:id:elizack_666:20210402123938p:plain
実行した画像

【小言】
これ前後編に分ける必要なかったな。。。と書き終わってから思った
次回はキー入力イベントを勉強しようかな

Win32APIのお勉強をしました(ウィンドウ生成~文字の描画)。前編

 今日はWin32APIについて勉強しました。
勉強のために「DirectX 12の魔導書」なる本を読もうと思ったが、これはWin32APIの知識がないと厳しそうだ!と判断し、Windowsプログラミングから始める決意をしたのである。。。
前編ではウィンドウの生成、後編では文字の描画とかについて書こうかと。(記事長すぎると読むとき嫌になるし)



 当面の目標は、「文字の描画のみを使って自作のタイピング練習ゲームを作る」にします。(タイピングにおける最適化を訓練する専用のツールが欲しかった)
それができたらDirectX12の勉強に取り組み始めようかな~とか考えています。
 Microsoftのリファレンスとか、必要に応じて偉大な先駆者の本とか記事とか読みながら進めていきます。
Microsoftのリファレンスへのリンク。これとにらめっこすることになりそう。
docs.microsoft.com




とりあえず今日勉強したこと。。。
アプリがどうやって動いているか、ウィンドウの生成方法、イベントの受け取り方、文字列の型について、文字の描画方法、とかとか


【OSによるアプリの管理】

どうやらOSからアプリに「メッセージ」として指令が発せられ、アプリの機能が動作するらしい(メッセージ駆動型)。

アプリ側は「メッセージループ」で常にメッセージの発生を監視していて、何か受信すると「ウィンドウプロシージャ」を実行する、という仕組みだそうな。

プロシージャってなんそれ!?と思ったので調べてみたら、「命令とか処理をまとめて外部から呼び出せる形にしたもの」、らしい。


【WinMain関数】
C言語プログラムとかC++プログラムではmainから始まってたが、Win32APIを使うとWinMainからプログラムが実行される。
docs.microsoft.com

という内容らしい。細かい内容はある程度勉強してから調べるつもり。


【ウィンドウの生成(WNDCLASSEX構造体)】
ウィンドウを生成するには、CreateWindow関数とShowWindow関数を使う。がその前に、ウィンドウの概要を設定するために「WNDCLASSEX」という構造体を宣言して値を設定する必要がある。
Microsoftのリファレンス見ると、WNDCLASSEX構造体の中身はこんな感じだって書いてあった。

typedef struct{
	UINT cbSize;		//構造体のサイズ(バイト単位)
	UINT style;		//ウィンドウのスタイル
	WNDPROC lpfnWndProc;	//ウィンドウプロシージャへのポインタ
	int cbClsExtra;		//?
	int cbWndExtra;		//?
	HINSTANCE hInstance;	//インスタンスハンドル
	HICON hIcon;		//アイコン
	HCURSOR hCursor;	//カーソル
	HBRUSH hbrBackground;	//背景ブラシ
	LPCTSTR lpszMenuName;	//メニューの名前
	LPCTSTR lpszClassName;	//クラスの名前
	HICON hIconSm;		//小アイコン
} WNDCLASSEX, *PWNDCLASSEX;

 ウィンドウのスタイルにはいろいろあり、ここに使える定数が全部載ってた。
docs.microsoft.com
 とりあえず今回は「CS_VREDRAW(0x0001)」と「CS_HREDRAW(0x0002)」の2つを使ってみる。
どちらもウィンドウの移動や、領域が変更されたときに再描画メッセージを送るためのもので、CS_VREDRAWが領域の幅が変更されたとき用、CS_HREDRAWが領域の高さが変更されたとき用らしい。
  lpfnWndProcには、ウィンドウプロシージャ用に作った関数へのポインタを設定する。どうやらWin32APIポインタを意味する変数はlpから始まるっぽい。ウィンドウプロシージャ用の関数は後で作る(名前はWinProcにしとく)。
 次の二つはよくわからん。追加のメモリ容量がなんとか、、、って書いてあったけど今のところどちらも0で良さそう。
 HICONはLoadIcon(HINSTANCE hInstance, LPCSTR lpIconName)関数を使って設定できる。第一引き数にはNULL、第2引き数にはIDI_APPLICATIONを渡してみる。ここで使えるアイコンの定数もMicrosoftのリファレンスに載ってた。自前の画像とかも使えるのかな?今度調べてみよう。HCURSORもアイコンと似たような感じ。
 今回は以下のように値を入れてみる。値を入れたらRegisterClassEx(PWNDCLASSEX)を呼んで設定すればよい。この関数はBOOL値を返してきて、設定に失敗したらfalseが返ってくるっぽい。

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_VREDRAW | CS_HREDRAW;
wcex.lpfnWndProc = WinProc;
wcex.cbClsExtra = wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPRICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = windowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPRICATION);

if(!RegisterClassEx(&wcex)){
	return 1;
}

これができたら、CreateWindow関数を呼ぶ。


【ウィンドウの生成(CreateWindow関数)】

HWND CreateWindow(
	LPCTSTR lpClassName,	//アプリの名前
	LPCTSTR lpWindowName,	//タイトルの文字列。ウィンドウの左上らへんに表示される。
	DWORD dwStyle,		//ウィンドウのタイプ?
	int x,			//最初に表示する位置(x)
	int y,			//最初に表示する位置(y)
	int nWidth,		//最初のサイズ(幅)
	int nHeight,		//最初のサイズ(高さ)
	HWND hWndParent,	//親ウィンドウへのポインタ
	HMENU hMenu,		//メニューバー
	HINSTANCE hInstance,	//インスタンスのハンドル
	LPVOID lpParam		//WM_CREATE情報
);

 引き数の内容はコメントの通り。。。
 dwStyleはここから選ぶらしい。
docs.microsoft.com
 とりあえず最小化、最大化、終了ボタンがついてるWS_OVERLAPPEDWINDOWにしてみよう。
hWndParentとhMenuは使わないのでNULL、hInstanceはWinMainの第一引き数で受け取ってた値を使う。
lpParamはよくわかんなかったけどNULLでおkっぽいのでNULLで。

HWND hWnd = CreateWindow(
	windowClass, title,
	WS_OVERLAPPEDWINDOW,
	100, 100,
	WIN_WIDTH, WIN_HEIGHT,
	NULL, NULL, hInstance, NULL
);

うまくいかないとhWndにはNULLが入ってるらしい。
ここで返ってきたhWndと、WinMain関数の第四引き数をShowWindowに渡すとウィンドウが表示されるそうな。


【ウィンドウプロシージャの作成】
WNDCLASSEX構造体に設定するウィンドウプロシージャを作る。
ウィンドウプロシージャの作り方はここに書いてあった。
docs.microsoft.com
どうやら第二引き数にイベントの内容が入ってて、それに応じた処理を書けばよさそう。今回は終了イベントのみで十分なので、WM_DESTROYだけ見る。アプリを終了するときはPostQuitMessage関数を使ってOSに終了のメッセージを送ればよい。

LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message){
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}
	return 0;
};

【ウィンドウを実際に表示してみる】
必要なものは学習できたので、ウィンドウを表示するアプリを作ってみる。真っ白のウィンドウをただ表示するだけのシンプルなやつ。
ソースコードはこんな感じになった。

#include <windows.h>
#include <tchar.h>

#define WIN_WIDTH 400
#define WIN_HEIGHT 300

LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg){
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
		break;
	}
	return 0;
};

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nShowCmd)
{
	TCHAR windowClass[] = _T("SampleApp");
	TCHAR title[] = _T("さんぷる");
	
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_VREDRAW | CS_HREDRAW;
	wcex.lpfnWndProc = WinProc;
	wcex.cbClsExtra = wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = windowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
	
	if(!RegisterClassEx(&wcex)){
		return 1;
	}
	
	HWND hWnd = CreateWindow(
		windowClass, title,
		WS_OVERLAPPEDWINDOW,
		100, 100,
		WIN_WIDTH, WIN_HEIGHT,
		NULL, NULL, hInstance, NULL
	);
	
	if(!hWnd){
		return 1;
	}
	
	ShowWindow(hWnd, nShowCmd);
	UpdateWindow(hWnd);
	
	MSG msg;
  //これがメッセージループ
	while(GetMessage(&msg, NULL, 0, 0)){
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
	return 0;
}

ウィンドウの生成はここまで。後編では文字列の描画をします。。。

【小言】
もっとごりごり進めたいなー。
DirectXの本読む前にGPUの勉強もしてみようかな。これとか良さそう。
グラフィック系でおすすめの本あったら教えてください。

このブログについて

いつか自分でゲームを作るぞ!と決意したただのゲーム好きが何かを頑張るブログです。

ゲームエンジン使ってもいいけど、せっかくなら描画とか物理とかも自力でやりたいな~とか思ってます。

プログラミングは大学のときに少しやったくらいで知識も技術もからっきし。まずは勉強しなくては。。。

 

当面はC++、Win32API、DirectX12、とかその辺の勉強をしたら、備忘録として記事を残すという感じになりそう。

あとはやったゲームとか読んだ本とか日記とかの記事も書くかもしれない。かもしれない。

 

(とりあえず勉強もブログも3日坊主にならないようにしたい)