[menu.h]
●関数の宣言
メニューに関する関数の宣言です。
結構シンプルですけどね。
#ifndef INCLUDE_MENU_H
#define INCLUDE_MENU_H
//----------------------------------------------------------------------
// 関数の宣言
//----------------------------------------------------------------------
// メニュー初期化
extern void menu_Init();
// メニュー終了
extern void menu_End();
// メニュー処理
extern int menu_Main(int frame,PlayerParam_t *playerParam);
#endif /*INCLUDE_MENU_H*/
[menu.cpp]
プログラム本体です。
外部インターフェイスはシンプルですが、内部では状態遷移していたり色々とやっています。
●ヘッダと内部関数
内部関数はメニュー状態ごとの処理と、メニューのリスト作成の補助、ステータス表示ウィンドの処理に分かれます。
中でもmenu_ListSprintf()は可変引数を使っているので後ほど詳しく説明します。
#include
#include
#include "main.h"
#include "stateMng.h"
#include "winMng.h"
#include "party.h"
#include "menu.h"
//----------------------------------------------------------------------
// 内部関数
//----------------------------------------------------------------------
// メニュー系
static int menu_top(int frame); //メニューTOP
static int menu_power(PlayerParam_t *playerParam); //メニューつよさ
static int menu_item(PlayerParam_t *playerParam); //メニューアイテム
static int menu_magic(PlayerParam_t *playerParam); //メニューまほう
// 補助
static void menu_ListInit();
static char *menu_ListSprintf(char *format, ... );
// ステータス表示ウィンドウの制御
static void menu_MakeStatusList(PlayerParam_t *playerParam);
static int menu_NewWinStatusList();
enum MENU_STATE_??? メニューの遷移状態を示す。
s_topMenu TOPメニューの項目。
enum SELECT_??? TOPメニューの選択結果
//----------------------------------------------------------------------
// 定数
//----------------------------------------------------------------------
// メニューの状態
enum {
MENU_STATE_TOP, //メニューTOP
MENU_STATE_POWER, //つよさ
MENU_STATE_ITEM, //アイテム
MENU_STATE_MAGIC, //まほう
MENU_STATE_MAX, //メニューの状態の数
};
// TOPメニューの処理。
static char *s_topMenu[] = {
"つよさ",
"アイテム",
"まほう",
NULL,//ストッパ
};
// TOPメニューの選択結果
enum {
SELECT_POWER, //つよさ選択
SELECT_ITEM, //アイテム選択
SELECT_MAGIC, //魔法選択
};
s_MenuStateObj メニューの状態遷移を管理するオブジェクト
s_MainWinNo 主ウィンドウの管理番号
s_SubWinNo 補助ウィンドウの管理番号
s_menuList メニューのリスト用の文字列配列。menu_ListXXX系の関数で使用する。
s_menuAllocNo 上記リストの割り当て済みリストの番号
s_StatusList ステータス表示用のリスト管理用のポインタ配列
//----------------------------------------------------------------------
// 変数
//----------------------------------------------------------------------
// メニューに関連したデータ
static STATEMNG_OBJECT s_MenuStateObj; //メニューのオブジェクト
static int s_MainWinNo; //主ウィンドウ番号
static int s_SubWinNo; //補助ウィンドウ番号
// メニュー用文字列格納テーブル
#define MENU_STR_MAX (30)
#define MENU_STR_LENGTH (32)
static char s_menuList[MENU_STR_MAX][MENU_STR_LENGTH];
static int s_menuAllocNo=-1; //割り当て済番号
// ステータス表示用のテーブル
#define STATUS_LIST_MAX 2
static char *s_StatusList[STATUS_LIST_MAX+1]; // ステータス表示用のリスト
メニューの状態管理オブジェクトを生成します。
//----------------------------------------------------------------------
// メニュー初期化
//----------------------------------------------------------------------
void menu_Init()
{
// メニューの状態管理オブジェクトの作成。
s_MenuStateObj = STM_Init(MENU_STATE_MAX);
}
メニューの状態管理オブジェクトを破棄します。
//----------------------------------------------------------------------
// メニュー終了
//----------------------------------------------------------------------
void menu_End()
{
// メニューの状態管理オブジェクトの破棄
STM_End(s_MenuStateObj);
}
メニュー処理を行います。
最初のフレームなら、メニューの状態管理オブジェクトをメニューTOPで初期化します。
各メニュー状態で振り分けてますので、各状態毎に解説します。
●MENU_STATE_TOP メニューTOP
どのメニュー操作を行うかmenu_top()関数内で選択します。
選択は戻り値として返ってくるので、その値を元に状態を遷移しています。
ここでメニューを抜けるは特殊処理なので、メニューキーが押されたか直接確認しています。このmenu_Main()の戻り値をTRUEにしてメニュー処理から遷移します。それ以外の時は戻り値がFALSEで遷移せずメニューが継続します。メニュー内部の遷移と話がややこしいですが、別件ですので混同しないようにお願いします。
●MENU_STATE_POWER メニューつよさ
menu_power()関数で「つよさ」を表示します。抜ける場合は、TRUEが返ってくるのでメニューTOPに戻ります。
●MENU_STATE_ITEM メニューアイテム
menu_item()関数で「アイテム」を表示・操作します。抜ける場合は、TRUEが返ってくるのでメニューTOPに戻ります。
●MENU_STATE_MAGIC メニューまほう
menu_item()関数でフィールドで使える「まほう」を表示・操作します。抜ける場合は、TRUEが返ってくるのでメニューTOPに戻ります。
//----------------------------------------------------------------------
// メニュー処理
//----------------------------------------------------------------------
int menu_Main(int frame,PlayerParam_t *playerParam)
{
// 今のゲーム状態のフレームをウィンドウ管理に設定。
winmng_SetFrame(frame);
// 最初のフレーム?
if( frame == 0 ) {
// メニュー状態を初期化
STM_ChangeState(s_MenuStateObj,MENU_STATE_TOP);//メニューTOPに遷移
}
// メニュー状態の更新・フレームカウント
STM_UpdateState(s_MenuStateObj);
// メニューの状態で振り分ける。
switch( STM_GetState(s_MenuStateObj) ) {
case MENU_STATE_TOP: //メニューTOP
switch( menu_top(frame) ) {
case SELECT_POWER://つよさ選択
STM_ChangeState(s_MenuStateObj,MENU_STATE_POWER);//つよさに遷移
break;
case SELECT_ITEM://アイテム選択
STM_ChangeState(s_MenuStateObj,MENU_STATE_ITEM);//アイテムに遷移
break;
case SELECT_MAGIC://魔法選択
STM_ChangeState(s_MenuStateObj,MENU_STATE_MAGIC);//まほうに遷移
break;
}
// メニューを抜ける?
if( g_MainData.key[g_MainData.key_menu] == 1) {
// メニューが終了ならば全ウィンドウを消去。
winmng_DelAllWin();
// 次のフレームで遷移。
return TRUE;
}
break;
case MENU_STATE_POWER: //つよさ
if( menu_power(playerParam) ) {
// 次のフレームで遷移。
STM_ChangeState(s_MenuStateObj,MENU_STATE_TOP);//menu先頭に戻る。
}
break;
case MENU_STATE_ITEM: //アイテム
if( menu_item(playerParam) ) {
// 次のフレームで遷移。
STM_ChangeState(s_MenuStateObj,MENU_STATE_TOP);//menu先頭に戻る。
}
break;
case MENU_STATE_MAGIC: //まほう
if( menu_magic(playerParam) ) {
// 次のフレームで遷移。
STM_ChangeState(s_MenuStateObj,MENU_STATE_TOP);//menu先頭に戻る。
}
break;
}
// 状態遷移しない。
return FALSE;
}
メニューTOPの処理です。
最初のフレームにウィンドウを生成してします。
winmng_NewWinは新しいウィンドウを指定されたサイズと位置とタイトルで生成する関数です。
winmng_SetMenu()はアクティブなウィンドウにメニュー項目(s_topMenu)を設定しています。
winmng_SelectMenu()での戻り値をそのまま戻り値として戻していますが、選択されたメニューの番号が返ります。
//----------------------------------------------------------------------
// メニューTOP
//----------------------------------------------------------------------
static int menu_top(int frame)
{
// 最初のフレーム?
if( frame == 0 ) {
// 念のため全ウィンドウを破棄
winmng_DelAllWin();
// ウィンドウを生成してメニューリストを設定。
int sizex = 150;
int sizey = 100;
int posx = (SCREEN_X-sizex) / 2;
int posy = (SCREEN_Y-sizey) / 2;
winmng_NewWin(posx,posy,sizex,sizey,"メニュー・ウィンドウ");
winmng_SetMenu(s_topMenu);
}
// 毎フレームのメニュー処理
return winmng_SelectMenu();
}
「つよさ」を編集して表示します。 メニューのつよさリストは当たり前ですが数値が可変するので動的に組み立てる必要があります。
s_powerListのポインタリスト列にmenu_ListSprintf()で組み立てた一行づつを終端NULLぽいんたとして格納しています。
menu_ListSprintf()については後で説明します。単にメモリも管理してくれるsprintfだと思ってください。
「メニューつよさ」が初回なら「メニューTOP」同様にウィンドウを構築します。ただし、今回は選択せずに表示だけなので、winmng_SetMenu()ではなくwinmng_SetList()でリストを設定します。
winmng_CheckCloseList()でTRUEが戻れば、リストを閉じるのでこのリストウィンドウだけを削除して遷移するためにTUREを返します。それ以外は「メニューつよさ」を継続するためFALSEを返します。
//----------------------------------------------------------------------
// メニューつよさ
//----------------------------------------------------------------------
static int menu_power(PlayerParam_t *playerParam)
{
int menuFrame = STM_GetFrameCount(s_MenuStateObj); //メニューのフレーム
#define POWER_LIST_NUMS (7) // メニュー用つよさの項目数
static char *s_powerList[POWER_LIST_NUMS+1]; // 表示用のリスト
// メニューの構成を組み立てる準備
menu_ListInit();
// メニューの各表示項目を編集
s_powerList[0] = menu_ListSprintf( "Level %d", playerParam->level); //レベル
s_powerList[1] = menu_ListSprintf( "Exp %d", playerParam->expp); //経験値
s_powerList[2] = menu_ListSprintf( "HP %d/%d", playerParam->hp,playerParam->hp_max); //HP
s_powerList[3] = menu_ListSprintf( "MP %d/%d", playerParam->mp,playerParam->mp_max); //MP
s_powerList[4] = menu_ListSprintf( "攻撃 %d", playerParam->attack); //攻撃力
s_powerList[5] = menu_ListSprintf( "防御 %d", playerParam->def); //防御力
s_powerList[6] = menu_ListSprintf( "お金 %d", playerParam->money); //所持金
s_powerList[POWER_LIST_NUMS] = NULL;//ストッパ
// つよさメニューの最初?
if( menuFrame==0 ) {
// つよさメニューを設定
int sizex = 150;
int sizey = 160;
int posx = (SCREEN_X-sizex) / 3;
int posy = (SCREEN_Y-sizey) / 3;
winmng_NewWin(posx,posy,sizex,sizey,"つよさ");
winmng_SetList( s_powerList );
}
// 毎フレームの更新
if( winmng_CheckCloseList() ) { //閉じる待ち
// 表示終了ならばこのウィンドウを消去。
winmng_DelWin(winmng_GetActiveWin());
// 次のフレームで遷移。
return TRUE;
}
// 遷移しない。
return FALSE;
}
「アイテム」を編集して表示・操作します。 「メニューアイテム」のアイテム数や名称、並びは変化しますので、これも同様に動的に組み立てる必要があります。
自分のパラメータのアイテム保管庫にあるアイテムを1つづ取り出してparty_getItemName()で名称を得ます。リストの作成は先程も出たmenu_ListSprintf()を使用しています。無効なイテム以降は登録しません(前詰めで保管されている前提です)。
menu_MakeStatusList()は回復の目安となるHP/MP値を表示するためのリストを別途作成する関数です。
「メニューアイテム」が初回ならこれまでと同様にウィンドウを生成します。ただし、今回はステータス表示ウィンドウの生成menu_NewWinStatusList()を行いウィンドウ番号をs_SubWinNoに先に格納しています。あとでwinmng_NewWin()でアイテムリストを生成してウィンドウ番号はs_MainWinNoに保存します。このメニューは操作できるのでwinmng_SetMenu()でリストを生成します。で、ここが違うポイントは毎回メニューが変化する(アイテム使用でアイテムが無くなる可能性がある)のでwinmng_UpdateMenu()で毎回リストを更新しているところです。
winmng_SelectMenu()で選択されたアイテム選択番号(item_selno)が戻ってきたらparty_useItem()でそのアイテムを使用します。使ったアイテムは、アイテム保管庫のその後ろのアイテムを前に詰めることで消費します。この時最後には空のアイテムを入れるのを忘れないこと。HPやMPが満タンならアイテムは消費されません。
メニューキーが押されたらこのメニューを閉じます。今までと違うのは、2つのウィンドウ(s_MainWinNoとs_SubWinNo)を閉じていることですね。この後遷移するためにTUREを返します。それ以外は「メニューアイテム」を継続するためFALSEを返します。
//----------------------------------------------------------------------
// メニューアイテム
//----------------------------------------------------------------------
static int menu_item(PlayerParam_t *playerParam)
{
int menuFrame = STM_GetFrameCount(s_MenuStateObj); //メニューのフレーム
static char *s_itemList[ITEM_MAX_NUMS+1]; // 表示用のリスト
// アイテムメニューの構成を組み立てる準備
menu_ListInit();
// アイテムメニューの構成を組み立てる。
int items = 0;
for( int i=0 ; iitems[i] == ITEM_NON ) { //アイテムを所持していない。
break; //アイテム未所持なら抜ける。
}
s_itemList[i] = menu_ListSprintf( "%s", party_getItemName(playerParam->items[i]));//アイテム
items++;
}
s_itemList[items] = NULL;//ストッパ
// ステータス表示のウィンドウの設定
menu_MakeStatusList(playerParam);
// アイテムメニューの最初?
if( menuFrame==0 ) {
// ステータス表示ウィンドウの生成。
s_SubWinNo = menu_NewWinStatusList();
// アイテムメニューを設定
int sizex = 150;
int sizey = 120;
int posx = (SCREEN_X-sizex) / 3;
int posy = (SCREEN_Y-sizey) / 3;
s_MainWinNo = winmng_NewWin(posx,posy,sizex,sizey,"アイテム");
winmng_SetMenu( s_itemList );
} else {
// アイテムメニューを更新
winmng_UpdateMenu( s_itemList );
}
// アイテムの選択をする。
int item_selno = winmng_SelectMenu();
if( item_selno!=-1 ) { //選択されたら
// アイテムの処理を行う。
if( party_useItem(playerParam,playerParam->items[item_selno]) ) {
// アイテムを消費する(アイテムテーブルを前に詰める)。
for( int i=item_selno ; iitems[i] = playerParam->items[i+1];
}
playerParam->items[ITEM_MAX_NUMS-1] = ITEM_NON; //最後を空にする。
}
}
// キャンセル
if( g_MainData.key[g_MainData.key_menu] == 1 ) {
// 表示終了ならばこのアイテム関連ウィンドウを消去。
winmng_DelWin(s_MainWinNo);
winmng_DelWin(s_SubWinNo);
// 次のフレームで遷移。
return TRUE;
}
// 遷移しない。
return FALSE;
}
「メニューまほう」を表示・操作します。 基本的なところは「メニューアイテム」と変わりません。
違いは使えるまほうの一覧がレベルによって変化すること、残りMPによって使えるかどうかが変わるところです。
違いを見ていきましょう。
party_getMagicData()で魔法のデータを1つづつ得ていますが、これはレベルアップで増えていきます。魔法にはフィールドで使えるかの区分もありますので引数で渡してます。ここで戻ってきた魔法データをみて使用するに十分なMPがない場合は、メニュー上で灰色に表示します。"/g"は、その為のキーワードです。
あと違うところは、winmng_SelectMenu()で魔法が選択されたときの処理がparty_useMagic()に全て頼っていることです。その他は、メニューアイテムと同様です。
//----------------------------------------------------------------------
// メニューまほう
//----------------------------------------------------------------------
static int menu_magic(PlayerParam_t *playerParam)
{
int menuFrame = STM_GetFrameCount(s_MenuStateObj); //メニューのフレーム
static char *s_magicList[MAGIC_MAX_NUMS+1]; // 表示用のリスト
// まほうメニューの構成を組み立てる準備
menu_ListInit();
// まほうメニューの構成を組み立てる。
int magics = 0;
for( int i=0 ; imp mp ) {
// 色をグレーにする。
s_magicList[i] = menu_ListSprintf( "/g%-10s %d", pMagicData->name, pMagicData->mp );//-10sは10文字左寄せ文字列表示。
} else {
// 色は白
s_magicList[i] = menu_ListSprintf( "%-10s %d", pMagicData->name, pMagicData->mp );//-10sは10文字左寄せ文字列表示。
}
magics++;
}
s_magicList[magics] = NULL;//ストッパ
// ステータス表示のウィンドウの設定
menu_MakeStatusList(playerParam);
// まほうメニューの最初?
if( menuFrame==0 ) {
// ステータス表示ウィンドウの生成。
s_SubWinNo = menu_NewWinStatusList();
// まほうメニューを設定
int sizex = 150;
int sizey = 120;
int posx = (SCREEN_X-sizex) / 3;
int posy = (SCREEN_Y-sizey) / 3;
s_MainWinNo = winmng_NewWin(posx,posy,sizex,sizey,"まほう");
winmng_SetMenu( s_magicList );
} else {
// まほうメニューを更新
winmng_UpdateMenu( s_magicList );
}
// まほうの選択をする。
int maig_list_no = winmng_SelectMenu();
if( maig_list_no!=-1 ) { //選択されたら
// まほうの処理を行う。MPの消費も関数側
party_useMagic(playerParam,maig_list_no,FALSE);
}
// キャンセル
if( g_MainData.key[g_MainData.key_menu] == 1 ) {
// 表示終了ならばこのまほう関連ウィンドウを消去。
winmng_DelWin(s_MainWinNo);
winmng_DelWin(s_SubWinNo);
// 次のフレームで遷移。
return TRUE;
}
// 遷移しない。
return FALSE;
}
メニュー用リスト(s_menuList)の割り当てを初期化します。
//----------------------------------------------------------------------
// メニュー専用の
//----------------------------------------------------------------------
static void menu_ListInit()
{
// 割り当てを先頭にする。
s_menuAllocNo = 0;
}
メニュー用リスト(s_menuList)の一行分を確保して、そこに編集した文字列を設定します。
ここでややこしいのは、可変引数と言うものを使っていることです。
ようはprintfなどのライブラリが使っている機能と同じなのですが、あまり目にすることは少ないと思います。
詳細は下記サイトなどを御覧ください。
http://www.geocities.jp/ky_webid/c/057.html
http://wisdom.sakura.ne.jp/programming/c/c62.html
この関数では、引数をそのままvsprintfというsprintfの内部処理用の関数にそのまま渡しています。
結果がbufに反映されますが256文字を超えるとバグリます(^^;ご注意下さい。
出来上がった文字列は、s_menuListから割り当てた一行分のバッファにコピーされます。このときMENU_STR_LENGTHを超えているとアサートします。
一行分のバッファのポインタは戻り値として返ります。
//----------------------------------------------------------------------
// メニュー専用の文字編集
// すいません可変パラメータで書いてしまいました。
//----------------------------------------------------------------------
static char *menu_ListSprintf(char *format, ... )
{
va_list vlist; //可変引数
char buf[256]; //文字編集バッファ
char *str = NULL;
// 可変パラメータでspirntfする。可変引数に付いては説明を省略します。
va_start(vlist, format); // 可変引数の先頭位置を指定して取得開始
vsprintf(buf,format,vlist); // 可変引数でバッファにsprintf
va_end(vlist); // 可変引数の取得を終了
// 長さを調べる
MACRO_ASSERT( strlen(buf) hp,playerParam->hp_max); //HP
s_StatusList[1] = menu_ListSprintf( "MP %d/%d", playerParam->mp,playerParam->mp_max); //MP
s_StatusList[STATUS_LIST_MAX] = NULL;//ストッパ
}
HP/MPを表示するためのリストをウィンドウとして登録します。
戻り値は割り当てたウィンドウ番号です。
//----------------------------------------------------------------------
// ステータス表示ウィンドウの生成
//----------------------------------------------------------------------
static int menu_NewWinStatusList()
{
int sizex = 150;
int sizey = 60;
int posx = (SCREEN_X-sizex) / 3 * 2;
int posy = (SCREEN_Y-sizey) / 3 * 2;
int winNo = winmng_NewWin(posx,posy,sizex,sizey,"ステータス");
winmng_SetList( s_StatusList );
return winNo;
}
さて最後の仕上げ、メニューをゲームシステムに組み込みましょう。
次回に続きます。