基本プログラムを改造し、テニスゲームを作る。
1.common.hの修正
common.hに次の行を追加する。
//----------------------------------------------------------------------------- // 構造体宣言 //----------------------------------------------------------------------------- typedef struct _Status { int x; //X座標 int y; //Y座標 RECT rect; //画像矩形 int width; //幅 int height; //高さ int move; //移動量 int anime; //アニメーションパターン数 int anime_no; //現在のコマ数 int cnt; //勝った回数 } Status;2.game.cppの修正
game.cppのローカル変数宣言を次のように修正、追加する。
//----------------------------------------------------------------------------- // ローカル変数 //----------------------------------------------------------------------------- static RECT rcRect = {0, 0, 640, 480}; // 画面全体矩形 static RECT GameRect = {150, 0, 630, 480}; // ゲーム画面矩形 static RECT MyCharRect; // ボール画像の矩形 static int MyChar_x, MyChar_y; // ボールの座標 static int MyChar_move; // ボールの移動量 static Status Player1, Player2; // バーの情報
バーを上下に表示し、1P側のバー(上)はRIGHTキー、LEFTキーで、2P側のバー(下)はZキー、Xキーでそれぞれ左右に動くようにする。ただし、バーは左右の壁までしか動けない。
<HINT>
- バーを表示する位置や移動量などの初期化が必要。初期化はgame_init関数内。バー1、2の矩形はそれぞれ(0, 80, 48, 86)、(96, 80, 144, 86)。
- バーの幅、高さも指定しておくとプログラムが美しく作れる。
- バーの表示、移動はgame_frame関数内。
ボールの情報を格納している変数「MyCharRect」「MyChar_x」「MyChar_y」「MyChar_move」を削除し、Status構造体でボールの情報を格納する変数を作成する。
ボールを画面中央に配置し、ゲームが始まったら左下に移動するようプログラムを修正する。ボールは壁やバーに当たると跳ね返る。
ボールの当たり方 左右の壁に当たった場合、跳ね返る。プログラム的にはX方向の移動量が反転(1なら−1、−1なら1)する。
GameRectの値を利用すること。上下の壁に当たった場合、ゲームオーバーということでスタート画面に戻る。
GameRectの値を利用すること。バーの上部(下部)に当たった場合、跳ね返る。プログラム的にはY方向の移動量が反転(1なら−1、−1なら1)する。 (余裕があったら組み込む)
バーの側面に当たった場合、跳ね返る。プログラム的にはX方向の移動量が反転(1なら−1、−1なら1)する。(さらに余裕があったら組み込む)
バーの角に当たった場合、その方向に跳ね返る。プログラム的にはX方向Y方向の移動量が共に反転する。
勝敗の判定を付ける。
- ボールが後ろにそれたら負けとなり、勝ったプレーヤーのポイントが1増える。
(プレーヤーのステータス「勝った回数(cnt)」を使う。GameInitで初期化を行い、ゲームに勝利すると1増える。)- プレーヤのポイントはゲーム画面左の適当な領域に、次のように表示する。
PLAYER 1
POINT 0
PLAYER 2
POINT 0- 3ポイント先取したプレーヤーの勝ちとする。画面中央に「PLAYER 1 WIN」または「PLAYER 2 WIN」と表示し、リターンキーを押したらスタート画面に戻る。
画面中央に出す文字列は、タイトル画像(title.bmp)に作っておく。任意の文字列の出し方
FPSの表示と同じやり方で、文字配列内の任意の文字列を画面に出すことができる。しかし、この処理をいろんな場所に埋め込んで使うのは無駄なので、文字列を表示する関数を作成し、いろんな場所で使えるようにする。
(この関数は、String.bmpにあわせて作られているため、他のビットマップでは使えない)
- MyDraw.hの最下部に次の行を追加
BOOL StringDraw(char *, int, int, int); - MyDraw.cppのプロトタイプ宣言の最後に次の行を追加
BOOL StringDraw(char *, int, int, int); - MyDraw.cppの一番最後に次の関数を追加
//----------------------------------------------------------------------------- // 関数名 : StringDraw() // 機能概要: 文字列の描画 // 引数 : *str:表示したい文字列の先頭アドレス // x, y:転送先座標 // colorNo:色番号(文字列画像の1段目、2段目、・・・) //----------------------------------------------------------------------------- BOOL StringDraw(char *str, int x, int y, int colorNo) { HRESULT hRet; RECT strRect = {264, colorNo*16, 472, colorNo*16+16}; int i, j, wx; for(i = 0; i < (int)strlen(str); i++) { wx = i * 8 + x; j = str[i] - ' '; strRect.left = j * 8; strRect.right = strRect.left + 8; hRet = myBltFast(g_pDDSBack, wx, y, g_pDDSStr, &strRect, DDBLTFAST_SRCCOLORKEY); if (hRet != DD_OK) return FALSE; } return TRUE; }- 使い方
例えば画面(100,200)の位置に「PLAYER 1」と表示させたい場合、次のように指定する。また、変数の値を表示させたい場合、次のように指定する。
char str[] = "PLAYER1"; if (!StringDraw(str, 100, 200, 1)) return;
char str[80]; int cnt = 1; sprintf(str, "CNT = %02d", cnt); if (!StringDraw(str, 100, 200, 1)) return;※この関数は、strings.bmpファイルの画像を利用しているため、その画像にない文字(小文字や日本語など)を出力できないことに注意!!
ボールをアニメーションさせる。ボールの画像はアニメーション用に6つ用意されているため、ゲームループのたびに1つ目、2つ目・・・と表示する画像を変えていけばアニメーションしているように見える。当然、6つ目の画像を出した後は1枚目の画像に戻る。
<プログラム方法>
- 初期化処理で、ボールのステータス情報の「アニメーションパターン数」を「6」に、「現在のコマ数」を「0」に、「幅」を「8」に指定。
- ボール描画前、ボールの矩形のうち、leftとrightに対して計算を行う。
leftは「幅×コマ数」で、rightは「left+幅」で求められる。- 描画後、コマ数を+1する。ただし、コマ数が6以上になったら0に戻す。
設問dの方法では、アニメーションがめちゃめちゃ早くなってしまうだけでなく、パソコンの性能によってアニメーションスピードが変わってしまう。どのパソコンでも、一定時間ごとにアニメーションが行えるようプログラムを改造する。
<プログラム方法>
一定時間ごとに処理をさせるには、ある一定の処理からの経過時間を調べ、経過時間が指定した時間以上になったらコマ数を+する。
- Game.cppのローカル変数宣言に、次の変数を追加。
static DWORD ballActionTime; // ボールのアニメーション後経過時間 - GameInit関数内で、ballActionTimeを初期化する。
ballActionTime = g_thisTickCount; // 現在の経過時間をセット - GameFrame関数内のボール描画前に、次の処理を追加
/* ボールのアニメーション処理 */ if ( g_thisTickCount - ballActionTime >= 100 ) { // 100ミリ秒毎現在のコマ数を+する処理を入れるballActionTime = g_thisTickCount; // クリア }補足
g_thisTickCountという変数は、WinMain.cppのUpdateFrame関数内で
g_thisTickCount = timeGetTime(); // 現在の時間を取得 と、経過時間が代入されている。つまり、上記if文実行時、
g_thisTickCountには常に最新の時間が入っているということになる。よって、前回アニメーションを行った時間とg_thisTickCountの差分を調べ、一定時間以上経過していれば処理を行えばよい。
ボールがバーにあたったときの場所により、跳ね返る角度を以下のように変える。
【バーとボールの跳ね返りの条件】
- バーの当たる位置により、30°45°60°の角度でボールが跳ね返るものとする。
- バーの中心を境界に右側にボールが当たった場合は右側に、左側に当たった場合は左側に跳ね返るものとする。
- 上記二つの条件からバーを6等分してあたりの位置を導くものとする。ただし、ボールのどの部分が当たったかを簡単に求めるために、ボールの左上端から4ドット目の位置をボールの中心として考え、この中心が6等分した部分のどこに入っているかで跳ね返る方向を決める。
- 跳ね返る角度を変えるために、x、y軸の移動を整数から実数値に変更して考える。また、増加する値を各角度ごとに配列に格納し計算量を減らすようにする。
条件を元に、図から考えてみよう!
![]()
![]()
上記の内容から、x , y の移動量を次のような配列に格納しておく(√3=1.73 √2=1.41とおくものとする)
ボールの跳ね返る角度の表 角度 30° 45° 60° 60° 45° 30° 30° 添え字 0 1 2 3 4 5 6 X -1.73 -1.41 -1 1 1.41 1.73 1.73 Y 1.0 1.41 1.73 1.73 1.41 1.0 1.0 《プログラムへの実装を考える》
データを記憶させる構造体を定義すると利用しやすい。
typedef struct { double x; double y; } dPoint; static dPoint bmv[7]={{-1.73,1.0},{-1.41,1.41},{-1.0,1.73},{1.0,1.73},{1.41,1.41},{1.73,1.0},{1.73,1.0}};後は、ボールとバーが重なったときのボールの中心位置からバーのどの部分になるかを計算で求めればよいことになる。表では、7番目(添え字の6)があるがこれは、プログラムによっては不必要になるので各自で利用するかどうかを検討してほしい。
また、ボールの座標を記憶させる変数も実数にする必要がある。これは次のことを考えれば自明である。
int x = 0 , i ; for ( i = 0 ; i < 5 ; i++ ) {x = x + 0.5 ;printf ( "x = %d\n ", x ); }この計算から、いくら 0.5 を x に加算しても x が整数型であることから小数点以下が切り捨てられてしまい、0になってしまう事がわかる。(T_T)
そこで次のようにキャストを使う。
double x = 0.0 ;int i ; for ( i = 0 ; i < 5 ; i++ ) { x = x + 0.5; printf ( "x = %d\n ",(int)x ); }キャストなどを使い強制的に型変換を行うことで望む値を得ることができる。(^_^)/
ボールやバーの移動量などゲームバランスや、ゲームオーバー処理などを考え、 それなりに遊べるものにする。