課題2(テニスゲームを作る)

 基本プログラムを改造し、テニスゲームを作る。

ゲーム概要

前準備

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;               // バーの情報

設問a(キャラクタ情報を構造体で管理する・1)

 バーを上下に表示し、1P側のバー(上)はRIGHTキー、LEFTキーで、2P側のバー(下)はZキー、Xキーでそれぞれ左右に動くようにする。ただし、バーは左右の壁までしか動けない。

<HINT>

  1. バーを表示する位置や移動量などの初期化が必要。初期化はgame_init関数内。バー1、2の矩形はそれぞれ(0, 80, 48, 86)、(96, 80, 144, 86)。
  2. バーの幅、高さも指定しておくとプログラムが美しく作れる。
  3. バーの表示、移動はgame_frame関数内。

設問b(キャラクタ情報を構造体で管理する・2)

 ボールの情報を格納している変数「MyCharRect」「MyChar_x」「MyChar_y」「MyChar_move」を削除し、Status構造体でボールの情報を格納する変数を作成する。

設問c(なんとなく当たり判定)

 ボールを画面中央に配置し、ゲームが始まったら左下に移動するようプログラムを修正する。ボールは壁やバーに当たると跳ね返る。

ボールの当たり方
左右の壁に当たった場合、跳ね返る。プログラム的にはX方向の移動量が反転(1なら−1、−1なら1)する。

GameRectの値を利用すること。
上下の壁に当たった場合、ゲームオーバーということでスタート画面に戻る。

GameRectの値を利用すること。
バーの上部(下部)に当たった場合、跳ね返る。プログラム的にはY方向の移動量が反転(1なら−1、−1なら1)する。
(余裕があったら組み込む)

バーの側面に当たった場合、跳ね返る。プログラム的にはX方向の移動量が反転(1なら−1、−1なら1)する。
(さらに余裕があったら組み込む)

バーの角に当たった場合、その方向に跳ね返る。プログラム的にはX方向Y方向の移動量が共に反転する。

設問d(用意した文字画像の表示)

 勝敗の判定を付ける。

  1. ボールが後ろにそれたら負けとなり、勝ったプレーヤーのポイントが1増える。
    (プレーヤーのステータス「勝った回数(cnt)」を使う。GameInitで初期化を行い、ゲームに勝利すると1増える。)
  2. プレーヤのポイントはゲーム画面左の適当な領域に、次のように表示する。
    PLAYER 1
    POINT 0



    PLAYER 2
    POINT 0
  3. 3ポイント先取したプレーヤーの勝ちとする。画面中央に「PLAYER 1 WIN」または「PLAYER 2 WIN」と表示し、リターンキーを押したらスタート画面に戻る。
    画面中央に出す文字列は、タイトル画像(title.bmp)に作っておく。

任意の文字列の出し方

 FPSの表示と同じやり方で、文字配列内の任意の文字列を画面に出すことができる。しかし、この処理をいろんな場所に埋め込んで使うのは無駄なので、文字列を表示する関数を作成し、いろんな場所で使えるようにする。
(この関数は、String.bmpにあわせて作られているため、他のビットマップでは使えない)

  1. MyDraw.hの最下部に次の行を追加
    BOOL StringDraw(char *, int, int, int);
  2. MyDraw.cppのプロトタイプ宣言の最後に次の行を追加
    BOOL StringDraw(char *, int, int, int);
  3. 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;
    }
  4. 使い方
    例えば画面(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ファイルの画像を利用しているため、その画像にない文字(小文字や日本語など)を出力できないことに注意!!

設問e(いんちきアニメーション)

 ボールをアニメーションさせる。ボールの画像はアニメーション用に6つ用意されているため、ゲームループのたびに1つ目、2つ目・・・と表示する画像を変えていけばアニメーションしているように見える。当然、6つ目の画像を出した後は1枚目の画像に戻る。

<プログラム方法>

  1. 初期化処理で、ボールのステータス情報の「アニメーションパターン数」を「6」に、「現在のコマ数」を「0」に、「幅」を「8」に指定。
  2. ボール描画前、ボールの矩形のうち、leftとrightに対して計算を行う。
    leftは「幅×コマ数」で、rightは「left+幅」で求められる。
  3. 描画後、コマ数を+1する。ただし、コマ数が6以上になったら0に戻す。

設問f(本当のアニメーション)

 設問dの方法では、アニメーションがめちゃめちゃ早くなってしまうだけでなく、パソコンの性能によってアニメーションスピードが変わってしまう。どのパソコンでも、一定時間ごとにアニメーションが行えるようプログラムを改造する。

<プログラム方法>

 一定時間ごとに処理をさせるには、ある一定の処理からの経過時間を調べ、経過時間が指定した時間以上になったらコマ数を+する。

  1. Game.cppのローカル変数宣言に、次の変数を追加。
    static DWORD ballActionTime; // ボールのアニメーション後経過時間
  2. GameInit関数内で、ballActionTimeを初期化する。
    ballActionTime = g_thisTickCount; // 現在の経過時間をセット
  3. GameFrame関数内のボール描画前に、次の処理を追加
    /* ボールのアニメーション処理 */
    if ( g_thisTickCount - ballActionTime >= 100 ) { // 100ミリ秒毎
        現在のコマ数を+する処理を入れる
        ballActionTime = g_thisTickCount;  // クリア
    }

補足

 g_thisTickCountという変数は、WinMain.cppのUpdateFrame関数内で

g_thisTickCount = timeGetTime(); // 現在の時間を取得

と、経過時間が代入されている。つまり、上記if文実行時、g_thisTickCountには常に最新の時間が入っているということになる。よって、前回アニメーションを行った時間とg_thisTickCountの差分を調べ、一定時間以上経過していれば処理を行えばよい。

設問g(移動量の考え方)

 ボールがバーにあたったときの場所により、跳ね返る角度を以下のように変える。

【バーとボールの跳ね返りの条件】

 条件を元に、図から考えてみよう!

 上記の内容から、x , y の移動量を次のような配列に格納しておく(√3=1.73 √2=1.41とおくものとする)

ボールの跳ね返る角度の表
角度 30° 45° 60° 60° 45° 30° 30°
添え字
-1.73 -1.41 -1 1 1.41 1.73 1.73
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 );
}

 キャストなどを使い強制的に型変換を行うことで望む値を得ることができる。(^_^)/

設問h(完成)

 ボールやバーの移動量などゲームバランスや、ゲームオーバー処理などを考え、 それなりに遊べるものにする。


[ TOP ]