ずいぶんと日が空いてしまいましたがFPS講座はじまります.
夜猫子 :というわけで前回の続きで今回は描画部分を
クラス化してしまおうと思います.
ショータ:あれ?名前が変わってる
ねここ :前回の総会でバレバレだったらしいのですよー
夜猫子 :まさかいきなりばれているとは思いませんでした.
どうやら敗因というかばれた原因はは難易度らしく.
私が絶対やらなさそうな対話形式で進めたのになぁ.
ショータ:難易度でばれたって結構だめなんじゃない?
ねここ :こんな文章じゃきっと誰もわからないのですよー
ショータ:それって難易度以前の問題だよね
ねここ :ついに×評価ももらってしまいましたしねー
夜猫子 :O...TL そこまで言わなくてもいいじゃんかよぅ.
とはいえ難易度でばれたのはたしかにまずいな.
とりあえずはこれは講義じゃないので更新ペースは
別と考えて,自分のペースで進めてみてください.
あとプログラムの大半は詳しい意味がわからなくても
「この命令の塊はこういうことが出来るんだ!」
というところから始めても問題ないと思います.
とりあえず書いてみて細かいところは気になってから
後々調べるという形式でやると気が楽になると思います.
後々ってまぁ数日先か数ヶ月先か数年先かは知りませんけど.
特にこの章のクラス化からその傾向が強くなるので
気楽にやってみてください.
ねここ :逃げてるだけじゃないでしょうね
夜猫子 :半分ぐらい. でも半分は本気ですのでご安心ください.
ねここ :まぁそういうことにしておくですよ
夜猫子 :では早速名前改め夜猫子のFPS講座IIIの始まり始まり〜
今日はせっかくですので,とりあえず目先の目標として
簡単な移動ができるプログラムを作ってみました.
ダウンロードはこちらからrelease03.zip
あと1,2回の講座の後ならこれくらいのプログラムは
作れるようになると思いますのでがんばってみましょう.
ねここ :とりあえず移動とかはできるみたいです
ショータ:基本中の基本だね
夜猫子 :では早速クラスとは何かから進めます.
クラスというのは簡単に言えばひとまとめにしたほうが
都合のいい物はひとまとめにして管理してしまいましょう
というものです.
ねここ :たとえば?
夜猫子 :たとえば何か模型をたくさん作りたかったとします.
そのときにパーツごとに金型を作ってもいいですが,
一つの模型の部品は一つの金型でまとめてしまった方が
管理が楽になりますよね.
それから金型で部品を作った後も,この金型から
とれた部品なんだからこの模型に使う部品だ! と
使う方ときも楽になります.
これがクラス.
ショータ:わかりやすいような… わかりにくいような….
夜猫子 :まぁ使っているうちにだんだんわかってくると思いますので
とりあえず書いてみましょう.
第一章で実践あるのみっていったの誰でしたっけ?
ねここ :・・・
夜猫子 :さて,下のコードは前回書いたコードの一部です.
このコード,主に二つの部分が入り交じっています.
GUIアプリに必要な部分とDirectXに必要な部分です.
DirectX系の命令にはpD3DDeviceやpD3Dなど名前に
D3Dという文字が入っているのでわかりやすいと思います.
というわけでコードを読んでみましょう
#pragma comment(lib, "d3dxof.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "winmm.lib") #ifdef _DEBUG #pragma comment(lib, "d3dx9d.lib") #else #pragma comment(lib, "d3dx9.lib") #endif #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <d3dx9.h> IDirect3D9 *pD3D = NULL; IDirect3DDevice9 *pD3DDevice = NULL; LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ switch(msg){ case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { const int dispx = 800; const int dispy = 600; MSG msg; ZeroMemory(&msg, sizeof(msg)); WNDCLASSEX wc; ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_CLASSDC; wc.lpfnWndProc = MsgProc; wc.hInstance = hInstance; wc.lpszClassName = CLASSNAME; wc.hCursor = LoadCursor(NULL , IDC_ARROW); if (RegisterClassEx(&wc)==NULL) PostQuitMessage(0); HWND hWnd = CreateWindow(CLASSNAME, APPNAME, WS_OVERLAPPEDWINDOW, 0, 0, dispx, dispy, NULL, NULL, wc.hInstance, NULL); if (hWnd==NULL) PostQuitMessage(0); //ウィンドウサイズ微調整 RECT Rect; GetClientRect(hWnd, &Rect); MoveWindow(hWnd, NULL, NULL, dispx+(dispx-Rect.right), dispy+(dispy-Rect.bottom),false); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); pD3D = Direct3DCreate9(D3D_SDK_VERSION); d3dpp.BackBufferCount = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.Windowed = true; d3dpp.BackBufferHeight = dispy; d3dpp.BackBufferWidth = dispx; d3dpp.hDeviceWindow = hWnd; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice); // Show the window ShowWindow(hWnd, SW_SHOWDEFAULT); UpdateWindow(hWnd); // Enter the message loop while( msg.message!=WM_QUIT ){ if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ){ TranslateMessage( &msg ); DispatchMessage( &msg ); }else{ pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 190, 255 ), 1.0f, 0 ); pD3DDevice->BeginScene(); pD3DDevice->EndScene(); pD3DDevice->Present( NULL, NULL, NULL, NULL ); } } pD3DDevice->Release(); pD3D->Release(); UnregisterClass(CLASSNAME, hInstance); return (int)msg.wParam; }
夜猫子 :ウェブ連載っていいね. 紙面の都合なんて考えず
ソース丸ごと乗っけちゃえるし.
どうでしょう. 何かつかめてきましたか?
ねここ :うーん...一番上の
//ライブラリのインクルード #pragma comment(lib, "d3dxof.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3d9.lib") #ifdef _DEBUG #pragma comment(lib, "d3dx9d.lib") #else #pragma comment(lib, "d3dx9.lib") #endif #include <d3dx9.h> IDirect3D9 *pD3D = NULL; IDirect3DDevice9 *pD3DDevice = NULL;
と、中盤の
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); pD3D = Direct3DCreate9(D3D_SDK_VERSION); d3dpp.BackBufferCount = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.Windowed = true; d3dpp.BackBufferHeight = dispy; d3dpp.BackBufferWidth = dispx; d3dpp.hDeviceWindow = hWnd; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice);
それから
pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 190, 255 ), 1.0f, 0 ); pD3DDevice->BeginScene(); pD3DDevice->EndScene(); pD3DDevice->Present( NULL, NULL, NULL, NULL );
そして,最後にある
pD3DDevice->Release(); pD3D->Release();
がそうっぽいのですよ.
ショータ:本当にd3dがつく文を抜き出しただけだけどね
夜猫子 :うん.そんな感じ.実はあと #pragma comment(lib, "winmm.lib")もあげてくれたら
完璧だったのですがまぁ名前が名前だから仕方がないな.
ではこれをクラスにまとめてしまいましょう.
この講座ではVisual Studioを使いこなそうという意味合いもあるので,
Visual Studioの機能を使ってクラスを作ってみます.
といってもたいした物を作ってくれるわけでもないので,
不便に感じる人は自分で新しいファイルを二つ作ってしまってもいいと思います.
ねここ :いきなり弱気になったのですよ
夜猫子 :だってどっちが使いやすいかは人によるし.
夜猫子 :とりあえず,図解で説明します. 今回は描画をメインで扱いたいので
Graphicsクラスという名前にしてみたいと思います.
ねここ :また単純な…
夜猫子 :ではこんな感じでクリックしたり入力したりしてみてくださいー.
夜猫子 :最後にクラス名にGraphicsと入れて完了を押せばできあがり.
新しく二つのファイルが追加されて,画面が変わったかと思います.
ねここ :なんだか
#pragma once class Graphics { public: Graphics(void); virtual ~Graphics(void); };
こんなのがでたのですよ
夜猫子 :とりあえずこれで何もしないクラスが完成したのでこれに書き足していきましょう.
このファイルでは関数名の宣言と変数の宣言を行います.
実際の内容は別のファイルに書いていきますので今は気にしないで大丈夫です.
最初のGraphics(void);というのはクラスが作成されたときに必ず呼ばれる部分です.
これをコンストラクタと言います.開発環境によってはCtorと略されたりします.
DirectXの初期化などをここで行えば必ず実行されるので便利だと思います.
具体的には最初の方にある
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); pD3D = Direct3DCreate9(D3D_SDK_VERSION); d3dpp.BackBufferCount = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.Windowed = true; d3dpp.BackBufferHeight = dispy; d3dpp.BackBufferWidth = dispx; d3dpp.hDeviceWindow = hWnd; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice);
この部分ですね.ただし,dispx, dispy, hWndはいろいろ変わりますので
引数にして使うときに渡してもらうことにしましょう.
次の塊は
pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 190, 255 ), 1.0f, 0 ); pD3DDevice->BeginScene(); pD3DDevice->EndScene(); pD3DDevice->Present( NULL, NULL, NULL, NULL );
この部分です.これは描画をしている部分になるのでDraw()という関数を追加して,
とりあえずはその関数の中に移動してしまいましょう.
こんな感じでプログラム部分をクラスに移動していきます.
それから最後にある~graphics(void)というのはこのクラスが消えるときに呼ばれる部分で
デストラクタと呼ばれます.開発環境によってはDtorと書かれたりするので覚えておきましょう.
これは最後に必ず呼ばれる部分なので,確保したメモリとかをリリースするのに使うと便利です.
今回の場合は この二行かな.
pD3DDevice->Release(); pD3D->Release();
ねここ :そういえば最初の
//ライブラリのインクルード #pragma comment(lib, "d3dxof.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "winmm.lib") #ifdef _DEBUG #pragma comment(lib, "d3dx9d.lib") #else #pragma comment(lib, "d3dx9.lib") #endif #include <d3dx9.h> IDirect3D9 *pD3D = NULL; IDirect3DDevice9 *pD3DDevice = NULL;
これはどうするですか?
夜猫子 :これもヘッダに書いてしまいましょう.どこに書くかというと変数は大概下の方に書きます.
それから#includeはいつもどおり一番上でOKです.
というわけで書き加えたあとのヘッダはこんな感じになります
#pragma once //ライブラリのインクルード #pragma comment(lib, "d3dxof.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "winmm.lib") #ifdef _DEBUG #pragma comment(lib, "d3dx9d.lib") #else #pragma comment(lib, "d3dx9.lib") #endif #include <d3dx9.h> class Graphics { public: Graphics(HWND hWnd, int dispx, int dispy); //ウィンドウハンドル,高さ,幅は外からもらう void Draw(); //描画したいときに呼び出す virtual ~Graphics(void); //役目を終えたときに自動で呼び出される private: IDirect3D9 *pD3D; IDirect3DDevice9 *pD3DDevice; };
ショータ:=NULLが消えてるけどいいの? それからprivateって?
ねここ :そういえばpublicというのも気になるのですよ.
夜猫子 :実はクラス宣言の中では変数の初期化が出来ない決まりなのです.
というわけでNULLを入れたければコンストラクタで初期化することになります.
それからprivateというものはこれ以下にある関数や変数への外部からの参照や
呼び出しを出来ないようにするための修飾語です.
勝手に書き換えられると困るような物には必ずつけるようにしましょう.
ちなみにこのようにアクセスを制限したりしてまとめることをカプセル化
といいます.
逆によそからアクセスしたい変数や関数はpublicのあとに書きましょう.
そうしないとよそから使えなくなってしまいます.
ねここ :むむむ…ややこしくなってきたのですよ
夜猫子 :まぁC++について詳しくなろう!じゃなくてDirectXを使ってみよう!が目的ですので
C++の詳しいことは後ほど気になったときに調べればいいと思います.
では次に関数の中身を書いていきましょう.といっても書式さえ覚えれば
今回はほとんどコピペですんでしまいますね.
ではまず,Graphics.cppの方を開いてください.
おそらくこんな感じになっていると思います.
#include "Graphics.h" Graphics::Graphics(void) { } Graphics::~Graphics(void) { }
ねここ :ショーちゃん大変なのですよ さっきDraw()を追加したはずなのにDraw()がないのですよ
ショータ:ほんとだ。 そういえばGraphics()の中もvoidになってる
夜猫子 :Drawはさっき私たちが追加したので自分で追加しないとだめだったり.
VC++は残念ながらそこまで面倒はみてくれないようです.
というわけでそこら辺を修正した物がこれ.
ちなみにGraphics::というものがついていますが
これはGraphicsクラスのという意味です.
いろんなクラスを作ると同じ関数名が出てくることがあるので
それを識別するために必要な物になります.
#include "Graphics.h" Graphics::Graphics(HWND hWnd, int dispx, int dispy) { } void Graphics::Draw() { } Graphics::~Graphics(void) { }
夜猫子 :あとはコピペでさっきピックアップした部分を
該当する関数に入れてみてください.
ねここ :とりあえずコピペしてみたのですよ.
#include "Graphics.h" Graphics::Graphics(HWND hWnd, int dispx, int dispy) { D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); pD3D = Direct3DCreate9(D3D_SDK_VERSION); d3dpp.BackBufferCount = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.Windowed = true; d3dpp.BackBufferHeight = dispy; d3dpp.BackBufferWidth = dispx; d3dpp.hDeviceWindow = hWnd; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDevice); } void Graphics::Draw() { pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 190, 255 ), 1.0f, 0 ); pD3DDevice->BeginScene(); pD3DDevice->EndScene(); pD3DDevice->Present( NULL, NULL, NULL, NULL ); } Graphics::~Graphics(void) { pD3DDevice->Release(); pD3D->Release(); }
夜猫子 :はい.よくできましたー.
ではそろそろ仕上げ. このクラスを早速使ってみましょう!
とりあえずメインからクラス部分に移植した部分は全部消してしまいましょう.
ねここ :がらあきになったのですよ
夜猫子 :そしたら一番上でGraphics.hをインクルードして
//ウィンドウサイズ微調整 RECT Rect; GetClientRect(hWnd, &Rect); MoveWindow(hWnd, NULL, NULL, dispx+(dispx-Rect.right), dispy+(dispy-Rect.bottom),false);
の直後に
Graphics graphics(hWnd, dispx, dispy);
と入れてみてください.
これでGraphicsクラスをgraphicsに作って現在のウィンドウハンドルと
ウィンドウの高さと幅で初期化が出来ます.
ねここ :「できます」って
夜猫子 :つぎに
// Enter the message loop while( msg.message!=WM_QUIT ){ if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ){ TranslateMessage( &msg ); DispatchMessage( &msg ); }else{ } }
のelseのブロック内に graphics.Draw();
と入れたら実行してみてください.
ねここ :実行はCtrl+F5でいけるのですよー ショーちゃんもやってみるです
ショータ:なんか一個前と変わらない画面がでたよ.
夜猫子 :まぁ一個前のプログラムをクラスでまとめただけなので
同じ画面がでればOKです.
最初は何か面倒ですけどこうやってまとめておくと
あとで使いたいときに使い回しが簡単にできるなどいろいろメリットがあるので
積極的にクラス化などはやってみてください.
ショータ:確かにこれならDirectXの細かい部分を毎回いじらなくても
HWNDと大きさを指定するだけでDirectXの初期化ができるね.
夜猫子 :では今回はここで終了.お疲れ様でした.
次回はFPSを作るためにはどのようなクラスを作ればいいかという
クラスの作成ではなくクラス構造の設計についての話をします.
ではでは.
今回のプロジェクトファイルのダウンロードはこちらから.lesson03.zip