DIRECTX1 DirectPlay 5 データの送信&受信 1

送信と受信に関してですが、これはちょっと面倒です。送信と受信がちゃんと同期している(データを送る方と受け取る方の整合性が取れている・・)必要があるからです。うまく行かない場合はどちらが悪いのかわからないのでデバッグが難しいのですね。その辺はデバッガでカバーしてもらうことにしましょう。

では、まずメッセージの送り方の、数値データを1個送る場合です。これはプレイヤがカーソルキーを押したような場合です。単一のデータを送れば良いですね。複数のデータ(位置データ、テキストデータなど)を送るのも同じですが、送信するデータの量の指定の仕方がちょっと違います。で、まず、単一データの送り方です。送るためにはSendコマンドを使います。ぼたんを一つ追加してそのハンドルに送信コードを書きましょう。以下の通り。
 

DWORD testdata = 0; // テスト用データ
void CDxp3View::OnButton4()
{
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 SendDataByte msggen;
 
 msggen.dwType = -1; // システムメッセージではないというフラグ
 msggen.myType = 0; // 自分のアプリの中で使うフラグ、位置データか、シュートデータ、など
 msggen.mData0 = testdata; // テスト用のデータを送信する
 testdata ++;
 HRESULT ret = dxp.DxSendData(msggen);
 if(ret == DPERR_ALREADYINITIALIZED){
  AfxMessageBox("送信エラー");
 }
}
 

送信にしようする、 SendDataByte 構造体は、自分で定義します。下の例は、DWORD*4のデータを送ることができる構造体です。単純な3Dゲームなら位置データなどの送信に使えそうです。

CDxPlayクラスの上のグローバルエリアに定義しましょう。
 

typedef struct{
DWORD dwType;
DWORD myType;
DWORD mPosiX; // X位置
DWORD mPosiY; // Y位置
DWORD mPosiH; // 高さ
DWORD mDirect; // 向き
} SendDataByte;
 

CDxPLayクラスの中のDxSendDataは以下の通り。
 

//==================================================
// データ送信(データバイトを送る)
HRESULT CDxPlay::DxSendData(SendDataByte* mdb, DWORD size)
{
 
 HRESULT ret = lpDP4A->Send( playerDPID, // 送信者ID
 DPID_ALLPLAYERS, // 受信者IDあるいはフラグ
 //0, // DPSEND_ 型の値。
 //メッセージの送信方法を示します。
 //このパラメータに 0 を指定すると、
 //メッセージは保証されず、
 //標準的な優先順位で送信されます。
 DPSEND_GUARANTEED, // メッセージが保証されるフラグ
 mdb,
 size);
 
 return DP_OK;
}
 

このデータはどうやって受け取るかというと、当然、起動中のReceiveThreadにて受け取るわけですが、その部分のコードは以下の通り。// ここで受信データにあわせた処理をする と書いてある部分ですね。sdbというのは、CDxPlayのグローバルエリアに宣言しておいてください。
 

//=============================================================
// ここで受信データにあわせた処理をする

// システム・メッセージの処理
if (idFrom == DPID_SYSMSG)
{
 switch (lpvMsgBuffer->dwType)
 {
 case DPSYS_CREATEPLAYERORGROUP:
 {
 //ユーザーが作られた場合の処理
 break;
 }
 case DPSYS_DESTROYPLAYERORGROUP:
 {
 //ユーザーが減った場合の処理
 break;
 }
 }
}
else
{
 //===== ユーザメッセージの処理(idFrom == -1)
 
 SendDataByte * sd;
 sd = (SendDataByte*)lpvMsgBuffer;
 switch(sd->myType)
 {
 case 0:{ // 位置データ
 sdb.mPosiX = sd->mPosiX; // データを取り出す
 sdb.mPosiY = sd->mPosiY; // データを取り出す
 sdb.mPosiH = sd->mPosiH; // データを取り出す
 sdb.mDirect = sd->mDirect; // データを取り出す
 break;
 }
 case 1:{ // ストリングデータ
 break;
 }
 
 }
 
}
//=============================================================
 

これはスレッドの中でグローバルメモリ中にデータを転送するようになっています。これを表示しなければいけません。DirectXの解説書などにあるSDKベースのプログラムではこの中で処理しているのが普通のようです。しかり、MFCを使ったSDIではちょとうまく行きません。そこで、グローバルメモリに転送したデータをVirewクラスのOnDrawで処理(テキストを画面に表示する)することにします。

現実問題としては、ゲームを動かす場合はOnDrawで画面を更新しているわけですから、これで問題は無いはずです。とくにアクションゲームや、RPGではタイマなどで常に画面を書き換えているのですから、大丈夫でしょう。

今は、テキストを表示して通信が出来ていることを確認します。OnDrawのコードは以下の通り。
 

void CDxptestView::OnDraw(CDC* pDC)
{
 // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
 
 char * is;
 
 _itoa(dxp.GetReceiveData()->mPosiX, is, 10);
 
 pDC->TextOut(0,0,is);

}
 

上にあるdxp.GetReceiveData() は、CDxPlayのグローバルメモリを返すための関数です。うーん、かっこ悪し。でもうまい方法が考え付かないので今回はこれでいきます。
 

SendDataByte* CDxPlay::GetReceiveData()
{
 return &sdb;//ReceiveData;
}
 

これで数値データを送受信するコーディングは全部です。さっそくビルドして実行してみましょう。

実行すると、相手のウィンドウの左上に数字が表示されます。送信するたびに1づつ増えます。ちゃんと動きましたか?