iPhone/iPad用ドット絵エディタ「EDGE touch」

最近のエントリー

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、
お持ちの方はVisual C++でプロジェクトファイルを開いてください(「ddraw_04.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「ddraw_04.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化し、『GDIによるテキストの表示』という文字列が表示されると思います。
今回は、サーフェイスにGDI関数を使用して描画をします。

GDI概要

GDIとはGraphic Device Interfaceの略であり、ウィンドウプログラミングには欠かせないグラフィックライブラリです。
第1回でも記述しましたが、DirectDrawの関数には文字や線、円などの描画関数が用意されていません。その場合、GDIを使用してサーフェイスに描画する事になります。
ただ、GDIの描画速度はあまり速くないので、速度重視ゲームの場合、多用は控えた方が良いでしょう。

デバイスコンテキストハンドルの作成、解放

今回は、テキストを出力する部分を『DdTextOut』関数にまとめました。さぁコードを見てみましょう。

//----------[ GDI関数を使用してテキストの出力 ]----------------------------------------------
void DdTextOut(LPDIRECTDRAWSURFACE sf,int x,int y,char *str){
	HDC hdc;
	sf->GetDC(&hdc);
	TextOut(hdc,x,y,str,strlen(str));
	sf->ReleaseDC(hdc);
}

略

//関数の呼び出し
DdTextOut(lpFront,0,0,"GDIによるテキストの表示");

GDIで描画するためには、デバイスコンテキストを認識するハンドルというものを取得しなければなりません。
デバイスコンテキストを認識するハンドルとは、描画可能なメモリのパスを収納しておくような感じのもので、GDI関数呼び出し時によく引数として使用します。
では、ハンドルの取得方法を説明します。

書式 HRESULT GetDC( HDC FAR *lphDC );
lphDC デバイス コンテキストが返されるハンドルへのポインタ。
戻り値 DirectDrawオブジェクトの作成に成功するとDD_OKが返ってくるらしい。

「lpFront->GetDC(&hdc);」のように、途中「->」を記述しています。これらはC++で使われるクラスというものですが、ここでは解説省略。クラスについては他のHPを参照してください。

こうして、GetDCを実行すると、そのサーフェイス(今回はプライマリサーフェイス)のデバイスコンテキストを認識するハンドルを取得出来ます。

では、次にGDI関数の『TextOut』について説明します。

書式 BOOL TextOut( HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString );
hdc デバイスコンテキストを認識するハンドル
今回は「hdc」を引き渡す。
nXStart 描画先のX座標
nYStart 描画先のY座標
lpString 描画したい文字列
hdc 文字列のバイト数(文字数とも言うかも)
戻り値 成功した場合はTRUEが返ってくるらしい。

こうして、一通りGDI関数を使用したら、今度はハンドルを解放しなければなりません。
解放には、次の関数(サーフェイスのメンバ関数)を呼び出します。

書式 HRESULT ReleaseDC( HDC hDC );
hDC GetDCを使用して取得したデバイスコンテキストのハンドル。

今回はhdcを引き渡す。GetDCとは違い、ポインタでは無いことに注意しましょう。

戻り値 DirectDrawオブジェクトの作成に成功するとDD_OKが返ってくるらしい。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしていますので、
お持ちの方Visual C++でプロジェクトファイルを開いてください
(「ddraw_03.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「ddraw_03.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います
(何かキーを押すと終了します)。
今回は、DirectDrawを使用した画面のフルスクリーン化について解説したいと思います。

Windowsプログラミング基礎

いくらDirectDrawを使うとは言え、結局はWindows上で動作しています。
ですから多少Windowsプログラミングの知識が必要になります。
ここでは、「ウィンドウ作成」と「ウィンドウメッセージ」について適当に説明します。

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
	WNDCLASS wc;
	MSG msg;
	if(!hPrevInstance){
		wc.lpszClassName="dd";
		wc.lpfnWndProc=WndProc;
		wc.style=CS_HREDRAW | CS_VREDRAW;
		wc.hInstance=hInstance;
		wc.hIcon=NULL;
		wc.hCursor=NULL;
		wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
		wc.lpszMenuName=NULL;
		wc.cbClsExtra=0;
		wc.cbWndExtra=0;
		if(!RegisterClass(&wc)) return FALSE;
	}

	//ウィンドウ作成
	hw=CreateWindowEx(WS_EX_APPWINDOW,"dd","DD簡易講座第3回", WS_POPUPWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL);
	
	//ウィンドウの表示
	ShowWindow(hw,nCmdShow);		
	UpdateWindow(hw);

	続く

UNIX(Linux)やMS-DOSのC言語では、一番始めに実行されるのは「main」関数でした。
ですが、Windowsでは「WinMain」から始まります。
ウィンドウ作成については、あまり考えずに、こうすればとりあえずウィンドウを作成、
表示出来ると覚えてしまった方が良いです。
と言うより、私自身ウィンドウには興味が無いのでよく解りません(゜゜)☆O(–;)oばこ

Windowsには「ウィンドウメッセージ」というものが有り、OSとアプリケーションがコミュニケーションをとるために使用します。
例えば、ユーザーがアプリケーションのウィンドウを動かそうとした場合、OSはアプリケーションに対し、「ウィンドウが動くぞ」みたいな感じのメッセージを送信します。
そしてアプリケーションは、そのメッセージを受信し、なんらかの処理を行うことになります。
ここで、もしもアプリケーションがこのメッセージを無視してしまうと、ウィンドウが動かせないという状況が生まれます。

では、どうやってメッセージを処理するのでしょうか。コードを見てみましょう。

	while(1){
		while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
			if(!GetMessage(&msg,NULL,0,0)) 
				Quit();
			TranslateMessage(&msg); 
			DispatchMessage(&msg);
		}
	}

この処理は、一般的にメッセージループと呼ばれ、OSが送ってくるメッセージをひたすら待ち、
メッセージを受信したら、「WndProc」(コールバック)関数が呼ばれるようになっています。

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){
	switch(iMessage){

	//キー押された
	case WM_KEYDOWN:
		Quit();
		break;

	省略

WndProc関数では、このようにメッセージiMessageをswitchによって分岐させ、
それぞれ処理しています。
例えば、ユーザーが、アプリケーションに対し、
何かキーを押した場合、OSが「キーが押されたぞ」みたいな感じのメッセージを送信し、
アプリケーションは、メッセージループ処理中、このメッセージを受信し、
WndProcを呼び出します。
WndProcでは、「キーが押された」というメッセージは「WM_KEYDOWN」
というように表されます(マクロ)。
この辺はWindows関連のHPに行けば、丁寧に解説されているのでそちらを参照してください。
手抜きすぎかも(゜゜)☆O(–;)oばこ

DirectDraw初期化

では、DirectDrawの初期化をします。

サンプルコードを見てみましょう。

#include <ddraw.h>

略

LPDIRECTDRAW lpDD=NULL; 	//DirectDrawオブジェクト
LPDIRECTDRAWSURFACE lpFront=NULL;	//サーフェイス

略

BOOL StartDirectDraw(HWND hw){
	DDSURFACEDESC ddsd;
   
	//DirectDrawオブジェクト作成
	if(DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK) 
		return FALSE;

	//協調レベル設定
	if(lpDD->SetCooperativeLevel(hw,DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE)!=DD_OK)
		return FALSE;
	
	//解像度設定
	if(lpDD->SetDisplayMode(640,480,8)!=DD_OK)
		return FALSE;

	//プライマリサーフェイスの作成
	ZeroMemory(&ddsd,sizeof(ddsd));
	ddsd.dwSize=sizeof(ddsd);
	ddsd.dwFlags=DDSD_CAPS;
	ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
	
	if(lpDD->CreateSurface(&ddsd,&lpFront,NULL)!=DD_OK)
		return FALSE;

	return TRUE;
}

第2回でも記述しましたが、メニューの「ビルド」 → 「設定」 → 「リンク」タブ → オブジェクト/ライブラリモジュールに「ddraw.lib」を書き込む → 「OK」 、ソースの始めの方で「ddraw.h」をインクルードしてください。
これでDirectDrawの関数が使用可能となります(サンプルのプロジェクトでは、既に設定済みです)。

では、順番に解説していきたいと思います。

	if(DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK) 
		return FALSE;

ここでは、DirectDrawオブジェクトを作成します。
とか言ってるけど、DirectDrawオブジェクト言われてもよーわからんて。
とりあえず、DirectDrawを使うために最初に実行しなきゃいけないらしいです。

では、構文を見てみましょう。

書式 HRESULT DirectDrawCreate( GUID FAR* lpGUID,LPDIRECTDRAW FAR* lplpDD,IUnknown FAR* pUnkOuter );
lpGuid DirectDrawオブジェクトを作成するディスプレイデバイスのGUIDを指定します。

とか言ってるけど、よくわからん(爆)。どうやら、表示したいディスプレイを変更できるらしいのですが、変更する必要が無いのでNULLにしてまう。

lplpDD DirectDrawオブジェクトを指すポインタのアドレスを指定します。

ここでは、グローバル変数として定義した「LPDIRECTDRAW lpDD;」のアドレスを引き渡します。

pInkOuter 将来拡張される時に使うらしいので、とりあえずNULL。DirectXの関数にはこういう物が多いが、使う時が来るのか謎。
戻り値 DirectDrawオブジェクトの作成に成功するとDD_OKが返ってくるらしい。
	if(lpDD->SetCooperativeLevel(hw,DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE)!=DD_OK)
		return FALSE;

ここでは、さきほど作成したDirectDrawオブジェクト(lpDD)とやらを使用して
協調レベルを設定しています。
またしてもよくわからん(爆)<協調。
というか私に言語能力が無いだけかも(死。

どうやら、ウィンドウに対する設定をいろいろと変更できるらしい。

というわけで構文いってみよぅ。

書式 HRESULT SetCooperativeLevel( HWND hWnd , DWORD dwFlags );
hWnd アプリケーションのメインウィンドウへのHWNDハンドルを指定します。これはDirectDrawオブジェクトと関連付ける
ウィンドウです。ここでは、ウィンドウ作成時に使用した「hw」を引き渡します。
dwFlags 設定する協調レベルを表すDWORD値を指定します。下に指定できる値の一覧を記述します。これらはビット毎のOR演算子(|)を使って組み合わせて使用します。
ここでは、フルスクリーンに必要な2つの値「DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE」を指定します。
戻り値 成功するとDD_OKが返ってくるらしい。

SetCooperativeLevelに指定できるDWORD値

DDSCL_ALLOWREBOOT 排他的(フルスクリーン)モード時に、CTRL+ALT+DELの機能の許可。
DDSCL_EXCLUSIVE 排他的レベルの要求。このフラグは、DDSCL_FULLSCREENフラグと共に用いる必要がある。

どうやら、このフラグを使用すると、他のアプリケーションでこのフラグを使用した時に、エラーが発生するようになるらしい。同時に2つ以上のアプリケーションがフルスクリーン化出来なくするためとか。

DDSCL_FULLSCREEN フルスクリーン化出来ます。必ずDDSCL_EXCLUSIVEと組み合わせて使用しなければならない。
DDSCL_NORMAL アプリケーションが、通常のWindowsアプリケーションとして機能することを表す。このフラグは、DDSCL_ALLOWMODEX、 DDSCL_EXCLUSIVE、またはDDSCL_FULLSCREENフラグと共に用いることはできない。
DDSCL_NOWINDOWCHANGES アクティベート時、DirectDrawがアプリケーションウィンドウを最小化したりリストアしたりさせない。
	if(lpDD->SetDisplayMode(640,480,8)!=DD_OK)
		return FALSE;

ここでは、DirectDrawオブジェクト(lpDD)を使用して、ディスプレイの解像度、色数を設定します。

書式 HRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP );
dwWidth 画面解像度の横幅を指定します。
dwHeight 画面解像度の縦幅を指定します。
dwBpp 使用したい色数をビット数で指定します。例えば、8を指定すると、256色、16を指定するとHigh Colorとなります。
戻り値 成功するとDD_OKが返ってくるらしい。

画面解像度は、適当に指定できるわけではありません。指定できる解像度は、主に640×480、800×600、1024×768と言った一般的な解像度です(謎。色数も8(256色)、16(High Color)、24(True Color)、32(True Color)
です。ゲームでは、主に640×480×8(256色)を使用します。これよりも広かったり色数が多いと、
キャラクターデータを置くビデオメモリが足りなくなったり、画像転送速度に影響したりします。
また、256色モードの時だけは、パレットの変更が出来ます。パレットについては後で説明します。
それから、320×240(MODE X)と言った特別な解像度も指定出来ますが、これはいろいろと面倒なので後で説明しますパート2(ぉ。

	ZeroMemory(&ddsd,sizeof(ddsd));
	ddsd.dwSize=sizeof(ddsd);
	ddsd.dwFlags=DDSD_CAPS;
	ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
	
	if(lpDD->CreateSurface(&ddsd,&lpFront,NULL)!=DD_OK)
		return FALSE;

ここでは、プライマリサーフェイスを作成します。サーフェイスとは、ビデオメモリを意味し、実際に描画を行う場所となります。
プライマリサーフェイスは、画面に表示されているビデオメモリを意味します。ですから、ここへなんらかの描画を行った瞬間、画面に
表示される事になります。一般的には、複数作業用のサーフェイス(オフスクリーンサーフェイスと呼ぶ)を作成し、そこへキャラクターイメージを配置し、それらのデータを一度バックバッファと呼ばれるサーフェイスへ転送し、フリッピングという作業をすると、バックバッファとフロントバッファ(この場合プライマリサーフェイスの事)の内容がそっくりそのまま入れ替わり、画面へ表示される事になります。
オフスクリンサーフェイスや、バックバッファを作成する方法は、また後で説明します。ここではとりあえずプライマリサーフェイスだけを作成します。

まず、DDSURFACEDESCという構造体を使用して、作成するサーフェイスの情報をここへ詰め込みます。

DDSURFACEDESC構造体の主な内容は以下の通りです。

dwSize この構造体のサイズ(バイト数)
dwFlags 有効なメンバをDWORD値で指定します。どうやら、ここで使用したいメンバを指定し、そのメンバへ値を代入するようです。
指定できるメンバは下の方に記述してあります。
dwHeight サーフェイスの縦幅
dwWidth サーフェイスの横幅
lPitch メモリの水平行間のピクセル数
dwBackBufferCount 作成するバックバッファの数
ddsCaps サーフェイスの機能を指定します。

dwFlagsで指定できる主なメンバは以下の通りです。これらはビット毎のOR演算子(|)を使って組み合わせて使用します。

DWORD値 対応するメンバ
DDSD_DDSCAPS ddsCaps
DDSD_HEIGHT dwHeight
DDSD_WIDTH dwWidth
DDSD_BACKBUFFERCOUNT dwBackBufferCount

ここでは、プライマリサーフェイスを作成するために、DDSURFACEDESC ddsd構造体を使用し、ddsd.dwFlagsにDDSD_CAPSを指定し、ddsd.ddsCaps.dwCaps(何故かメンバの中にまたメンバ)にDDSCAPS_PRIMARYSURFACEを指定します。ここは、丸暗記ですかね(^^;
そうしたら、次にサーフェイスを実際に作成するためにCreateSurfaceを呼び出します。この構文は以下の通りです。

書式 HRESULT CreateSurface( LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE FAR *lplpDDSurface, IUnknown FAR *pUnkOuter );
lpDDSurfaceDesc 要求されたサーフェスを記述するDDSURFACEDESC構造体のアドレス。

今回は、先ほど設定したddsdのアドレスを引き渡しています。

lplpDDSurface 呼び出しが成功した場合、有効なDirectDrawSurfaceポインタによって初期化されるサーフェスへのポインタへのポインタ。

ややこしいですが、今回はあらかじめ宣言しておいた「LPDIRECTDRAWSURFACE lpFront;」のアドレスを引き渡しています。

pUnkOuter 将来拡張される時に使うらしいので、とりあえずNULL。
戻り値 成功するとDD_OKが返ってくるらしい。

DirectDraw後始末

コードを見てみましょう。

#define RELEASE(i) if(i){i->Release();i=NULL;}
BOOL EndDirectDraw(void){
	RELEASE(lpFront);
	RELEASE(lpDD);
	return TRUE;
}

サーフェイスや、DirectDrawオブジェクトはRelease();を実行すると終了する事が出来ます。
例えば、プライマリサーフェイスなら「lpFront->Release();」となります。
ここは、マクロ(define)を使用すると、簡潔に記述する事が出来ます。
プログラムを終了したい場合は、必ずこれらを実行しなければなりません。これを忘れてしまうと、Windowsに戻ったとき、パソコンが正常に動作しない可能性があります。

DirectXのSDK入手

さて、DirectXを使用したソフトウェアを作成するためには、DirectX SDKと言うものが必要になります。これは、DirectXを処理するためのライブラリ群であり、インクルードファイルや、ライブラリファイルが含まれています。ちなみに、Visual C++4.x以降のバージョンでは、既にDirectX2(未確認)辺りのSDKがあらかじめ含まれているため、「Visual C++6.0の設定」へ飛んで下さい。

一体DirectX SDKは何処で入手出来るのでしょうか。一応、MicrosoftのDirectX開発者ページにて、配布していますが、最近はファイルサイズが膨大になったため(100M以上)、そこからダウンロードするのは、あまり現実的ではありません。
(2004/05/30追記:いやほら、書いた当時はダイアルアップだったの。現在はブロードバンドの時代ですから、200MぐらいサクッとDLしましょう)

一番手っ取り早い方法は、SDKの入った雑誌を買ってくる事です。メジャーなトコロでCマガジンでしょうか。それか、DirectX入門系の本を購入し、こんなHPチマチマ見てないで自分で勉強する事です(爆)。

DirectX SDKのインストール

DirectX SDKを自分のコンピュータにインストールしたら、VC側にDirectX SDKの「有りか」を教えてあげる必要があります。

まず、VCのメニュ→ツール→オプションを実行し、その中の「ディレクトリ」タブを選びます。
setup05

「表示するディレクトリ」を「インクルードファイル」にし、「新規作成」ボタン(画像の赤丸)を押し、そこへインストールしたDirectX SDKのinlcludeフォルダを指定してやります。
同様に、「表示するディレクトリ」を「ライブラリファイル」にし「新規作成」ボタンを押して、そこへインストールしたDirectX SDKのlibフォルダを指定してやります。

この設定は一度行えばOKです。

DirectX SDKを使ったアプリケーションを新規作成する手順

Visual C++6.0の環境で、DirectXを使用するアプリケーションを新規作成するには、以下の手順が必要です。

1.メインメニュー → 「新規作成」 を選び、下のように設定し、OKをクリックします。
setup00

2.本当はどれでも良いのですが、とりあえずそのまま「終了」をクリックします。
setup01

3.単なる確認画面が出ますので、そのままOKをクリックします。
setup02

4.もう一度 メインメニュー → 「新規作成」 を選び、下のように設定し、OKをクリックします。
setup03

ここへプログラムをタラタラ書くことになります。
普通はCPPファイルを複数作成するのですが、この講座では見やすくするために、基本的には1ファイルで進めていきます。

5.メインメニュー → 「プロジェクト」→ 「設定」 を選び、「リンク」タブをクリックします。
setup04

設定の対象を「すべての構成」にします。
オブジェクト/ライブラリモジュールへ、「ddraw.lib」を書き加えます。

これで設定は終わりです。
ちなみに、この講座で配布しているサンプルコードは、こうした設定を既にやってあります。

はじめに

さぁ、始まりました第1回DirectDraw講座!DirectXで描画を担当するのがDirectDrawです。最近オンラインゲームで起動時に画面がフルクリーンに切り替わるものが増えてきましたが、これらは全部DirectDrawを使用していると考えて結構です。とりあえず私が今まで制作してきたゲーム並の事は出来ると言うことで、それ以上の事は出来ません(謎。今までGDIやらWinGやらで地味なゲームを作っていたあなた!たまにはDirectX使ってド派手なゲーム作りましょう!みたいな。

DirectDrawの長所

DirectDrawが今までのGDIやWinGと異なる点は、ハードウェアを使用するという点です。フルスクリーン化して色数を256色なんかにすれば、画面に表示するために必要なビデオメモリは640×480で300K程度です。現在世界で普及しているビデオメモリは最低でも2Mはあるので、残りの1.7Mに必要な画像を読み込んでおき、後はビデオメモリ間のハードウェア転送機能を使用して高速に描画すれば、派手なゲームもたぶん、作れます。まぁ、描画がいくら速くても、その他の部分でプログラム出来なければ、お話になりませんが・・・。とりあえず、DirectDrawでは、透過色付きの転送も出来ます。GDIのようにマスクイメージを用意してBitBltでAND、XORする必要もありません(WinGの場合は関係ないですけど)。DirectDrawでやる事といえば、長方形領域の転送くらいなものです。

DirectDrawの短所

DirectDrawではハードウェアが対応していない場合、ソフトウェアでエミュレートします。DirectDrawで、拡大、縮小付きの長方形領域転送があるのですが、現在対応しているビデオカードが少なく、めっちゃ描画遅いです。GDIのStretchBltとやらよりも遅いです(爆。また、ビデオメモリに直接アクセスする事も出来るのですが、そのデータ転送速度が遅すぎます。使わない方が良いです。それから、DirectDrawには円や線、文字と言った描画関数は用意されていません。その代わり、デバイスコンテキストとやらを取得出来るので、これを使用してGDIで描画します。

記事検索

コミュニティ

Banner designed by 写楽様
ドット絵掲示板
ドット絵掲示板
自作の「ドット絵」を投稿することができます。ドッターさん同士の交流の場としてご利用ください。
TakaboSoft Wiki
TakaboSoft Wiki
ソフトウェアに関する質問・不具合報告・要望などを書き込むことができます。