カーソルの移動制御(メニューの選択)

この章で使用するプログラムおよび画像ファイル(01でダウンロードできるものと同じ)

次は、カーソルを上下キーで移動し、STARTが選択されているときにスペースキーを押すとゲームがスタートするようなプログラムを考える。復習の意味も込めて、時間の取得から紹介する。上のプログラムと画像ファイルをダウンロードし、プロジェクトを作成しておくこと。

とりあえずカーソルを移動させる

カーソルキーの上を押したら上へ、下を押したら下へ移動するようプログラムを修正する。キャラクタの移動と違い、カーソルが移動できる場所はメニュー文字の横であるため、座標を+移動量すればよいわけではないことに注意。
メニューを表示するのはスタート処理なので、Start.cppを次のように修正する。

1.メニュー番号を管理するグローバル変数を宣言&初期化

今回のサンプルの場合、メニューは4種類あるが、カーソルがどの位置にいるかを示す変数があれば、その値によってどのメニューが選ばれているかがすぐに分かるだけでなく、表示する座標の計算にも使える。

// グローバル変数
/* 自ソースでのみ利用するもの */
static TLVERTX BackVertex[4];               // 背景用頂点情報配列
static TLVERTX MenuVertex[4];               // メニュー用頂点情報配列
static TLVERTX CursorVertex[4];             // カーソル用頂点情報配列
static int gl_MenuNo;                       // 選択されているメニュー番号
・
・
・
//-----------------------------------------------------------------------------
// 関数名 : StartInit()
// 機能概要: スタート画面初期化処理
//-----------------------------------------------------------------------------
void StartInit(void)
{

    // スタート画面で使用するテクスチャの作成
    CreateStartTexture();

    //--------------------------------------------------- 各変数の初期化
    /* 背景 */
    InitVertex(BackVertex, (float)gl_rcScreen.left, (float)gl_rcScreen.top, (float)gl_rcScreen.right, (float)gl_rcScreen.bottom, 255);
    /* メニュー */
    MenuInitVertex(MenuVertex, 200.0f, 250.0f, 400.0f, 442.0f);
    /* カーソル */
    MenuInitVertex(CursorVertex, 150.0f, 250.0f, 200.0f, 300.0f);
    /* カーソル表示位置(選択されたメニュー番号) */
    gl_MenuNo = 0;

    //--------------------------------------------------- フレームナンバーセット
    g_FrameNo = START_FRAME;

}
・
・
・

2.カーソルの頂点情報配列を修正

カーソルの表示位置がメニューの場所によって変化するが、サンプルの様に「メニューが規則正しく並んでいるとは限らない」ため、表示位置ごとに頂点情報を持つことにする。座標の自動計算を行わないためプログラムが多少長くなるが、融通は利くだろう。

//=============================================================================
//  スタート処理関係の自作関数群
//  Copyright NKC Game Staff(←自分の名前) 
//-----------------------------------------------------------------------------
#include "NKC_Common.h"

// マクロの定義
#define MENU_COUNT 4

// グローバル変数
/* 自ソースでのみ利用するもの */
static TLVERTX BackVertex[4];               // 背景用頂点情報配列
static TLVERTX MenuVertex[4];               // メニュー用頂点情報配列
static TLVERTX CursorVertex[MENU_COUNT][4]; // カーソル用頂点情報配列
static int gl_MenuNo;                       // 選択されているメニュー番号
・
・
・
//-----------------------------------------------------------------------------
// 関数名 : StartInit()
// 機能概要: スタート画面初期化処理
//-----------------------------------------------------------------------------
void StartInit(void)
{

    // スタート画面で使用するテクスチャの作成
    CreateStartTexture();

    //--------------------------------------------------- 各変数の初期化
    /* 背景 */
    InitVertex(BackVertex, (float)gl_rcScreen.left, (float)gl_rcScreen.top, (float)gl_rcScreen.right, (float)gl_rcScreen.bottom, 255);
    /* メニュー */
    MenuInitVertex(MenuVertex, 200.0f, 250.0f, 400.0f, 442.0f);
    /* カーソル */
    MenuInitVertex(CursorVertex[0], 150.0f, 250.0f, 200.0f, 300.0f);
    MenuInitVertex(CursorVertex[1], 150.0f, 298.0f, 200.0f, 348.0f);
    MenuInitVertex(CursorVertex[2], 150.0f, 346.0f, 200.0f, 396.0f);
    MenuInitVertex(CursorVertex[3], 150.0f, 394.0f, 200.0f, 444.0f);
    /* カーソル表示位置(選択されたメニュー番号) */
    gl_MenuNo = 0;

    //--------------------------------------------------- フレームナンバーセット
    g_FrameNo = START_FRAME;

}
・
・
・

3.カーソルの描画を修正

カーソルの頂点座標情報を配列にしたため、どの頂点情報配列で描画を行うかを決めなければならない。もちろん、メニュー番号を配列の要素番号に使用する。

//-----------------------------------------------------------------------------
// 関数名 : StartFrame()
// 機能概要: スタート画面処理
//-----------------------------------------------------------------------------
void StartFrame(void)
{
    /* 描画処理 */
    // 背景
    gl_lpD3ddev-"SetTexture(0, gl_TXBack);
    gl_lpD3ddev-"DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, BackVertex, sizeof(TLVERTX));
    // メニュー
    gl_lpD3ddev-"SetTexture(0, gl_TXMenu);
    gl_lpD3ddev-"DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, MenuVertex, sizeof(TLVERTX));
    // カーソル
    gl_lpD3ddev-"SetTexture(0, gl_TXCursor);
    gl_lpD3ddev-"DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, CursorVertex[gl_MenuNo], sizeof(TLVERTX));
    ・
    ・
    ・

4.カーソルの移動処理を追加

後はカーソルの移動処理である。移動処理と言っても、頂点座標を変化させるわけではないため、メニュー番号をカーソルキーの動きに合わせて増減させるだけである。
また、ゲームスタートはSTARTメニューが選択されているときに行われるようにしなければならないので、カーソルキーの位置(メニュー番号)によって処理を振り分けるようにする。

//-----------------------------------------------------------------------------
// 関数名 : StartFrame()
// 機能概要: スタート画面処理
//-----------------------------------------------------------------------------
void StartFrame(void)
{
    /* 描画処理 */
    // 背景
    gl_lpD3ddev->SetTexture(0, gl_TXBack);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, BackVertex, sizeof(TLVERTX));
    // メニュー
    gl_lpD3ddev->SetTexture(0, gl_TXMenu);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, MenuVertex, sizeof(TLVERTX));
    // カーソル
    gl_lpD3ddev->SetTexture(0, gl_TXCursor);
    gl_lpD3ddev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, CursorVertex[gl_MenuNo], sizeof(TLVERTX));

    // 選択されたメニューによって、処理を振り分ける
    if ( gl_KeyTbl[VK_RETURN] & 0x80 ) {
        switch ( gl_MenuNo ) {
        case 0:
            ReleaseStartTexture();
            g_FrameNo = GAME_INIT;
            return;
//      case 1:
//          g_FrameNo = GAME_CONTINUE;
//          return;
//      case 2:
//          g_FrameNo = GAME_OPTION;
//          return;
        case 3:
            PostMessage(hWnd, WM_CLOSE, 0, 0); //WM_DESTROYを誘発させる
            return;
        default:
            OutputDebugString("StartFrame関数:g_FrameNoの値が不正です。\n");
        }
    }
    // カーソルの移動
    if ( gl_KeyTbl[VK_UP] & 0x80 && gl_MenuNo > 0 ) gl_MenuNo--;
    else if ( gl_KeyTbl[VK_DOWN] & 0x80 && gl_MenuNo < MENU_COUNT - 1 ) gl_MenuNo++;

}

※以上の修正を行ったら、プログラムをビルド・実行し、とりあえずカーソルが動くことを確認しよう。

《問題点》

カーソルの移動にウェイトをかける

カーソルの移動処理を修正し、一度移動したカーソルは一定時間経過するまで動かないようにする。これにより、カーソルがゆっくり動くようになる。

1.時間の取得

ゲーム・ループのたびに時間を取得し、他のソースから自由に利用できるよう、WinMain.cppとcommon.hをそれぞれ次のように修正する。

《WinMain.cpp》

// グローバル変数
/* 他ソースも利用するもの */
HWND hWnd;                              // ウィンドウハンドル
BOOL g_appActive = FALSE;               // ウィンドウの状態
BYTE g_FrameNo = START_INIT;            // フレーム選択用
DWORD gl_nowTime;                       // 現在の時間を保存
BYTE gl_KeyTbl[256];                    // キーボードの状態を格納
RECT gl_rcScreen = {0, 0, 640, 480};    // ウィンドウ領域
/* 自ソースでのみ利用するもの */
static char szWinName[] = "Exer008";    // ウィンドウクラス用文字列
static char szWinTitle[] = "時間制御";  // ウィンドウクラス用文字列
・
・
・
//-----------------------------------------------------------------------------
// 関数名 : UpdateFrame()
// 機能概要: ゲームメイン処理
//-----------------------------------------------------------------------------
BOOL UpdateFrame(void)
{

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

    /* 現在のキー情報を取得 */
    if ( !GetKeyboardState(gl_KeyTbl) ) {
        MessageBox(hWnd, "キー情報の取得に失敗", "ERROR", MB_OK);
        return (FALSE);
    }
    ・
    ・
    ・

《NKC_Common.h》

// グローバル変数
/* WinMain.cppで宣言されているもの */
extern HWND hWnd;                           // ウィンドウハンドル
extern BYTE g_FrameNo;                      // フレーム選択用
extern DWORD gl_nowTime;                    // 現在の時間を保存
extern BYTE gl_KeyTbl[256];                 // キーボードの状態を格納
extern RECT gl_rcScreen;                    // ウィンドウ領域
・
・
・

2.カーソルの移動時間を格納するグローバル変数を宣言

カーソルを一定時間動かないようにするには、カーソルが動いたときの時間を保有しなければならない。その変数をグローバル変数として宣言する。
ついでに、ウェイト(待機)時間もマクロで宣言しておく。

//=============================================================================
//  スタート処理関係の自作関数群
//  Copyright NKC Game Staff(←自分の名前) 
//-----------------------------------------------------------------------------
#include "NKC_Common.h"

// マクロの定義
#define MENU_COUNT 4
#define CURSOR_MOVETIME 500

// グローバル変数
/* 自ソースでのみ利用するもの */
static TLVERTX BackVertex[4];               // 背景用頂点情報配列
static TLVERTX MenuVertex[4];               // メニュー用頂点情報配列
static TLVERTX CursorVertex[MENU_COUNT][4]; // カーソル用頂点情報配列
static int gl_MenuNo;                       // 選択されているメニュー番号
static DWORD gl_CurMoveTime;                // カーソル移動後経過時間
・
・
・

3.初期化

スタート画面表示後すぐにカーソルが移動できるように、MoveTimeに0をセットする。(nowTimeをセットすると、スタート画面表示後一定時間が経たないとカーソルが移動しない)

//-----------------------------------------------------------------------------
// 関数名 : StartInit()
// 機能概要: スタート画面初期化処理
//-----------------------------------------------------------------------------
void StartInit(void)
{

    // スタート画面で使用するテクスチャの作成
    CreateStartTexture();

    //--------------------------------------------------- 各変数の初期化
    /* 背景 */
    InitVertex(BackVertex, (float)gl_rcScreen.left, (float)gl_rcScreen.top, (float)gl_rcScreen.right, (float)gl_rcScreen.bottom, 255);
    /* メニュー */
    MenuInitVertex(MenuVertex, 200.0f, 250.0f, 400.0f, 442.0f);
    /* カーソル */
    MenuInitVertex(CursorVertex[0], 150.0f, 250.0f, 200.0f, 300.0f);
    MenuInitVertex(CursorVertex[1], 150.0f, 298.0f, 200.0f, 348.0f);
    MenuInitVertex(CursorVertex[2], 150.0f, 346.0f, 200.0f, 396.0f);
    MenuInitVertex(CursorVertex[3], 150.0f, 394.0f, 200.0f, 444.0f);
    /* カーソル表示位置(選択されたメニュー番号) */
    gl_MenuNo = 0;
    /* カーソルの移動後経過時間 */
    gl_CurMoveTime = 0;

    //--------------------------------------------------- フレームナンバーセット
    g_FrameNo = START_FRAME;

}

4.カーソル移動処理の修正

一度動かしたカーソルが、一定時間(CURSOR_MOVETIME)を超えるまで移動できないよう、カーソルの移動処理を修正する。

//-----------------------------------------------------------------------------
// 関数名 : StartFrame()
// 機能概要: スタート画面処理
//-----------------------------------------------------------------------------
void StartFrame(void)
{
    ・
    ・
    ・
    // カーソルの移動
    if ( gl_nowTime - gl_CurMoveTime > CURSOR_MOVETIME ) {
        gl_CurMoveTime = gl_nowTime;
        if ( gl_KeyTbl[VK_UP] & 0x80 && gl_MenuNo > 0 ) gl_MenuNo--;
        else if ( gl_KeyTbl[VK_DOWN] & 0x80 && gl_MenuNo < MENU_COUNT - 1 ) gl_MenuNo++;
    }

}

※以上の修正を行ったら、プログラムをビルド・実行し、とりあえずカーソルが動くことを確認しよう。

《POINT》

《問題点》

キーの連続押しに対応する(参考)

上記のプログラムの場合、カーソルキーを押しっぱなしにすると、500ミリ秒ごとにカーソルが移動する。これを、カーソルキーを離して再度押したら、500ミリ秒以内でもカーソルが移動するようにプログラムするにはどうすればよいだろうか?

答えは簡単で、カーソルキーが押されていない場合にカーソルの移動時間をクリアすればよい。どのようにプログラムすればよいか、考えてみよう!

《答え》

// カーソルの移動
if ( !( gl_KeyTbl[VK_UP] & 0x80 || gl_KeyTbl[VK_DOWN] & 0x80) ) {
    gl_CurMoveTime = 0;
} else if ( gl_nowTime - gl_CurMoveTime > CURSOR_MOVETIME ) {
    ・
    ・
    ・

BACK(スタートボタンを押してから一定時間経過後、ゲームが開始する) NEXT(練習問題)