DIRECTX1 DirectPlay2 イベントの作成 + スレッドの起動

DirectPlayの初期化が終了したら次にイベントを作成します。イベントの作成というのは、Win32のイベントオブジェクトというものを作る、と説明されていますが、分かりずらい説明なので省略します。後でプレイヤの作成というのをするのですが、その時の引数に必要なので、今作っておきます。

これもCDxPlayのメンバ関数として作ります。ひとつひとつ個別に作れば理解しやすいでしょう。実際のゲームプログラムのソースでは、一通りの処理を一つの関数で済ませていることが多いようです。

DxCreateEventという名前のメンバ関数の中身は以下の通り。
 

HRESULT CDxPlay::DxCreateEvent()
{
 // イベントオブジェクトを作る
 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 if(hEvent == NULL){
  AfxMessageBox("イベントの作成に失敗した");
  return FALSE;
 }
 AfxMessageBox("イベントの作成に成功した");
 return DP_OK;
}
 

あとは、使っている  hEvent というハンドルを作成します。これは  LPDIRECTPLAY4A lpDP4A; の下つまり、DxPlay.cppファイルの上の方、グローバル領域に作ります。
以上でイベントの作成は終わりです。説明は難しいのですが、やっていることは簡単ですね。

これでビルドして、ボタン1のハンドルの初期化の後に呼び出します。
 

void CDxptestView::OnButton1()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 
HRESULT ret = dxp.DxInitDplay();
if(ret != DP_OK){
 AfxMessageBox("DirectPlayの初期化に失敗しました");
 return;
}
 
AfxMessageBox("DirectPlayの初期化に成功しました");
 
if(dxp.DxCreateEvent() != DP_OK){
 return;
}
 

イベントの作成がうまく行ったメッセージが出ればOKです。言うまでもありませんが、実際にゲームを作るときにはいちいちメッセージを表示する必要はありません。最低限のメッセージを表示してあとはやりやすいように勝手に処理します。

次は、メッセージを受け取るスレッドを作成します。なんか順序が変ですね。この辺はゲームのデザインにも関係してくるような気がしますが、単独で動くのか、対戦相手が居ないと動かないのか、起動時にコネクションを張るのか、ゲーム中に対戦相手を見つけるのか、といった方法でやり方が違ってくると思います。それぞれのやり方についてはクラスが出来上がってから考えましょう。

で、スレッドですが、通信データをスレッドで受け取るわけなので、その中では相当いろいろな事をする必要があります。自分の画面上にある相手のキャラクタを動かしたり、相手のチャットを受け取ったらそれを表示したりなどです。しかし、このあたりはゲームデザインに関係するのでこれもあとでいろいろテストしてみましょう。ここではスレッドが動いていることを確認するだけにします。

まず、CDxPlayクラスのメンバ変数にhReceiveThread と、 gidReceiveThread; を追加します。

HANDLE hReceiveThread; // メッセージ受信スレッドのハンドル
DWORD gidReceiveThread; // メッセージ受信スレッドのスレッドID

次はスレッドを起動するためのメンバ関数です。同じくCDxPlayクラスに書きます。
 

HRESULT CDxPlay::DxMakeReceiveThread()
{
// メッセージ受信用のスレッドを作る
hReceiveThread = CreateThread(NULL, 0, ReceiveThread, NULL, 0, &gidReceiveThread);
 
if (hReceiveThread == NULL)
{
 // スレッドの作成に失敗
 AfxMessageBox("イベントの作成に失敗した");
 lpDP4A->Release();
 CloseHandle(hEvent);
 return FALSE;
}
AfxMessageBox("イベントの作成に成功した");
return DP_OK;
}
 

次はスレッドの本体です。関数定義の中に   CDxPlay:: が含まれていないのに気をつけてください。これはグローバル関数として定義します。このスレッドの動作も説明しずらいのですが、メッセージが、メッセージキューに溜まっていないか監視して、溜まっていたら読み出す、という動作をします。
 

DWORD WINAPI ReceiveThread(LPVOID lpParams)
{
// メッセージを受信するスレッド
DPID idFrom, idTo;
LPDPMSG_GENERIC lpvMsgBuffer;
DWORD dwMsgBufferSize;
HRESULT hr;
HWND hwnd;
 
struct Mine
{
DWORD dwType;
int x;
int y;
int dpid;
};
 
 
hwnd = (HWND)lpParams;
while (WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0)
{
//メッセージの取り出し
lpvMsgBuffer = NULL;
dwMsgBufferSize = 0;
 
do
{
// まずは,メッセージの受信に必要なバイト数を取得する
hr = lpDP4A->Receive(&idFrom, &idTo, DPRECEIVE_ALL, NULL, &dwMsgBufferSize);
 
if (hr != DPERR_BUFFERTOOSMALL)
{
 break;
}
 
// 必要なバイト数を確保する
if (lpvMsgBuffer != NULL)
{
 GlobalFree(lpvMsgBuffer);
}
 
lpvMsgBuffer = (LPDPMSG_GENERIC)GlobalAlloc(GPTR, dwMsgBufferSize);
if (lpvMsgBuffer == NULL)
{
 // メモリが足りない
 hr = DPERR_OUTOFMEMORY;
 break;
}
 
// データを受信
hr = lpDP4A->Receive(&idFrom, &idTo, DPRECEIVE_ALL, lpvMsgBuffer, &dwMsgBufferSize);
if (FAILED(hr))
{
 break;
}
int a;
//=============================================================
a=0; // ここで受信データにあわせた処理をする
 
//=============================================================
 
} while (SUCCEEDED(hr));
 
 InvalidateRect(hwnd, NULL, TRUE);
 UpdateWindow(hwnd);
 
 if (lpvMsgBuffer != NULL)
  GlobalFree(lpvMsgBuffer);
 }
 
  ExitThread(0);
  return 0;
}
 

スレッドが出来たら、このスレッドの宣言を、CDxPlay.cppの上の方に書いておきます。こんな感じです。
 

// DxPlay.cpp: CDxPlay クラスのインプリメンテーション
//
///////////////////////////////////////////////////////////////
 
#include "stdafx.h"
#include "dxptest.h"
#include "DxPlay.h"
 
//#include "dplay.h"
 
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
 
LPDIRECTPLAY4A lpDP4A;
HANDLE hEvent;
 
DWORD WINAPI ReceiveThread(LPVOID lpParams);
 

ボタンのハンドルからメンバ関数を呼びます。
 

void CDxptestView::OnButton1()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 
HRESULT ret = dxp.DxInitDplay();
if(ret != DP_OK){
 AfxMessageBox("DirectPlayの初期化に失敗しました");
 return;
}
AfxMessageBox("DirectPlayの初期化に成功しました");
 
// イベントを作成する
if(dxp.DxCreateEvent() != DP_OK){
 return;
}
// 受信用スレッドを起動する
if(dxp.DxMakeReceiveThread() != DP_OK){
 return;
}

}
 

実行してみましょう。起動に成功したらOKです。スレッドがちゃんとデータを受け取っているかどうかは、プレイヤを作ってデータを送れるようになってからテストします。