ホームへ戻る

 3.3章 簡単な選択画面を作る

 今回も引き続きDXライブラリの関数をゲームプログラムに適用した使い方をみていきましょう。

本章は「構造体」を使用します。構造体を使う事で変数をまとめて扱うことが出来、非常に便利です。

ゲームプログラムに限らず必須知識ですので、知らない人は入門サイトで勉強して下さい。

例えば、学校であれば「生徒」には、必ず「出席番号」と「名前」があります。

必ず対になる物なので、セットで扱うことが出来れば便利そうです。

typedef struct{
  int 出席番号;
  char *名前;
} 生徒タグ ;

このように書けば、出席番号と名前がセットになった変数の型「生徒タグ」が出来上がります。

このように、「メニュー項目」には「x座標」「y座標」「名前」がセットになります。ですから

typedef struct{
  int x, y座標;
  char* 名前;
} メニュー項目タグ ;

とすることで、メニュー項目に必要な変数をセットで用意できる「メニュー項目タグ」が出来上がりこれを使うと便利です。

更に、メニュー項目は同じ構造をした要素がいくつも存在します。ですから、例えばメニュー項目が5個ある時は

メニュー項目タグ メニュー項目[5];

のように配列で用意することで計算をループで行うことが出来、効率的です。

では、下キーが押されるたびに選択項目を下にずらし、選択されている要素を少し左にずらして表示するサンプルを見てみましょう。


#include "DxLib.h"

int Key[256]; // キーが押されているフレーム数を格納する

// キーの入力状態を更新する
int gpUpdateKey(){
        char tmpKey[256]; // 現在のキーの入力状態を格納する
        GetHitKeyStateAll( tmpKey ); // 全てのキーの入力状態を得る
        for( int i=0; i<256; i++ ){ 
                if( tmpKey[i] != 0 ){ // i番のキーコードに対応するキーが押されていたら
                        Key[i]++;     // 加算
                } else {              // 押されていなければ
                        Key[i] = 0;   // 0にする
                }
        }
        return 0;
}

// メニュー項目の表示に必要な構造体を用意する
typedef struct{
        int x, y;       // 座標格納用変数
        char name[128]; // 項目名格納用変数
} MenuElement_t ;

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
        ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

        // メニュー項目要素を5つ作る
        MenuElement_t MenuElement[5]={
                {  80, 100, "ゲームスタート" }, // タグの中身の順番で格納される。xに80が、yに100が、nameに"ゲームスタート"が
                { 100, 150, "おまけ" },
                { 100, 200, "ヘルプ" },
                { 100, 250, "コンフィグ" },
                { 100, 300, "ゲーム終了" },
        };
        int SelectNum = 0; // 現在の選択番号

        // while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キー更新)
        while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 && gpUpdateKey()==0 ){

                // 計算フェーズ 

                if( Key[ KEY_INPUT_DOWN ] == 1 ){ // 下キーが押された瞬間だけ処理

                        SelectNum = ( SelectNum + 1 ) % 5; // 現在の選択項目を一つ下にずらす(ループする)

                        for( int i=0; i<5; i++ ){              // メニュー項目数である5個ループ処理
                                if( i == SelectNum ){          // 今処理しているのが、選択番号と同じ要素なら
                                        MenuElement[i].x = 80; // 座標を80にする
                                } else {                       // 今処理しているのが、選択番号以外なら
                                        MenuElement[i].x = 100;// 座標を100にする
                                }
                        }
                }

                // 描画フェーズ

                for( int i=0; i<5; i++ ){ // メニュー項目を描画
                        DrawFormatString( MenuElement[i].x, MenuElement[i].y, GetColor(255,255,255), MenuElement[i].name );
                }

        }

        DxLib_End(); // DXライブラリ終了処理
        return 0;
}

実行結果


サンプルでは構造体とその配列を使う事でスマートに要素の計算を行うことができました。

今回も「計算フェーズ」と「描画フェーズ」を意識しました。

計算フェーズでは選択状態を変更し、それに従った座標に要素を更新しています。

描画フェーズでは設定されたパラメータで表示を行うだけです。

このように必要な計算は全て計算フェーズで行い、描画フェーズではパラメータに従った表示のみを行うようにしましょう。

描画フェーズでも座標を計算するような処理を含めると計算フェーズとの境界が無くなってしまいます。

下に、描画フェーズでも座標計算を入れてしまい、計算フェーズとの境目が怪しくなっているあまり良くない例を示します。

(下のサンプルは上のサンプルと全く同じ振る舞いをします)



#include "DxLib.h"

int Key[256]; // キーが押されているフレーム数を格納する

// キーの入力状態を更新する
int gpUpdateKey(){
	char tmpKey[256]; // 現在のキーの入力状態を格納する
	GetHitKeyStateAll( tmpKey ); // 全てのキーの入力状態を得る
	for( int i=0; i<256; i++ ){ 
		if( tmpKey[i] != 0 ){ // i番のキーコードに対応するキーが押されていたら
			Key[i]++;     // 加算
		} else {              // 押されていなければ
			Key[i] = 0;   // 0にする
		}
	}
	return 0;
}

// メニュー項目の表示に必要な構造体を用意する
typedef struct{
        int x, y;               // 座標格納用変数
        char name[128]; // 項目名格納用変数
} MenuElement_t ;

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
        ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

        // メニュー項目要素を5つ作る
        MenuElement_t MenuElement[5]={
                { 100, 100, "ゲームスタート" }, // タグの中身の順番で格納される。xに80が、yに100が、nameに"ゲームスタート"が
                { 100, 150, "おまけ" },
                { 100, 200, "ヘルプ" },
                { 100, 250, "コンフィグ" },
                { 100, 300, "ゲーム終了" },
        };
        int SelectNum = 0; // 現在の選択番号

        // while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キー更新)
        while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 && gpUpdateKey()==0 ){

                // 計算フェーズ 

                if( Key[ KEY_INPUT_DOWN ] == 1 ){ // 下キーが押された瞬間だけ処理

                        SelectNum = ( SelectNum + 1 ) % 5; // 現在の選択項目を一つ下にずらす(ループする)

                }

                // 描画フェーズ

                for( int i=0; i<5; i++ ){ // メニュー項目を描画
                        if( i == SelectNum ){
                                DrawFormatString( MenuElement[i].x-20, MenuElement[i].y, GetColor(255,255,255), MenuElement[i].name );
			} else {
				DrawFormatString( MenuElement[i].x,    MenuElement[i].y, GetColor(255,255,255), MenuElement[i].name );
			}
		}

	}

	DxLib_End(); // DXライブラリ終了処理
	return 0;
}


描画フェーズで描画する位置を決定してしまっています。

現在はこれの何が悪いのか分からないかもしれませんが、描画に必要なパラメータの計算は全て計算フェーズに収めるようにした方が

プログラムが大きくなった時に管理が楽なのであり、処理のスキップも実装がし易いのです。

今回の例では、変数の代入を行っていないのでまだよい方ですが、

描画フェーズで変数の代入まで行ってしまうと、描画フェーズをスキップする処理を入れた時、計算結果がスキップした時としないときで異なってしまう現象が生じてしまいます。

ですから計算フェーズと描画フェーズがあることを意識してゲームプログラムを設計して下さい。

→分からないことがあれば掲示板で質問して下さい


- Remical Soft -