簡単RPG講座13-3。戦闘システムの3

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

簡単RPG講座13-3。戦闘システムの3

投稿記事 by softya(ソフト屋) » 14年前

長らく間が空いちゃいましたが戦闘システムの続きです。なんとか今月中に第一部完結できそうです。

変更はまだまだ続きますよ。
今度は小物な変更とゲームメインです。

[stateMng.cpp]
状態管理の変更点です。
●STM_Init
malloc()時のMACRO_ASSERT()でのガードが2ヶ所抜けていたので追加しました。

CODE:

//----------------------------------------------------------------------
//	状態管理の初期化。オブジェクトを返す。
//----------------------------------------------------------------------
STATEMNG_OBJECT STM_Init(int stateNums)
{
	STATEMNG_OBJECT object = NULL;
	
	//	まず状態管理オブジェクトメモリを確保する。
	object = (STATEMNG_OBJECT)malloc( sizeof(struct tag_StateMngObject)) ;
	MACRO_ASSERT( object!=NULL );
	
	//	状態管理オブジェクトの値を初期化する。
	object->nowState = -1;			//現在の状態
	object->beforeState = -2;		//前の状態(STM_UpdateStateで現在の状態が変化なら保存)
	object->nextState = -3;			//次の状態(STM_UpdateStateで現在の状態にコピー)
	object->stateNums = stateNums;	//状態数
	object->bBack = FALSE;			//状態を戻すフラグ
	object->bReset = FALSE;			//状態を強制設定するフラグ
	object->pFlameCounts = NULL;	//フレームカウント配列。状態数分。
	
	//	フレームカウント配列を確保する。
	if( stateNums > 0 ) {
		object->pFlameCounts = (int*)malloc( sizeof(int) * stateNums );
		MACRO_ASSERT( (object->pFlameCounts)!=NULL );
	}
	
	//	状態管理オブジェクトを返す。
	return object;
}
[menu.cpp]
メニュー処理のプログラム側です。
●menu_magic
魔法関係の関数でパラメータが変わったので対応です。
party_getMagicData()とparty_useMagic()でMAGIC_TYPE_FIELDを指定しています。

CODE:

//----------------------------------------------------------------------
//	メニューまほう
//----------------------------------------------------------------------
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,MAGIC_TYPE_FIELD);
	}
	
	//	キャンセル
	if( g_MainData.key[g_MainData.key_menu] == 1 ) {
		//	表示終了ならばこのまほう関連ウィンドウを消去。
		winmng_DelWin(s_MainWinNo);
		winmng_DelWin(s_SubWinNo);
		//	次のフレームで遷移。
		return TRUE;
	}
	//	遷移しない。
	return FALSE;
}
[shop.cpp]
お店処理のプログラム側です。
●定数
宿屋の料金を変更

CODE:

//	お宿の料金
#define INN_CHARGE_PRICE	(5)
●shop_ItemSelect
shop_ItemSelect()関数内でwinmng_SetMsg()のパラメータを変更してください。

CODE:

		winmng_SetMsg(mes,FALSE);	//	ウィンドウに表示するメッセージを設定
			↓変更
		winmng_SetMsg(mes,MSG_MODE_NO_MARK);	//	ウィンドウに表示するメッセージを設定
●shop_ComMsg
shop_ComMsg()関数内でwinmng_SetMsg()のパラメータを変更してください。

CODE:

		winmng_SetMsg(mes,TRUE);	//	ウィンドウに表示するメッセージを設定
			↓変更
		winmng_SetMsg(mes,MSG_MODE_PAUSE_MARK);	//	ウィンドウに表示するメッセージを設定
●shop_ComYnSel
shop_ComYnSel()関数内でwinmng_SetMsg()のパラメータを変更してください。

CODE:

		winmng_SetMsg(mes,FALSE);	//	ウィンドウに表示するメッセージを設定
			↓変更
		winmng_SetMsg(mes,MSG_MODE_NO_MARK);	//	ウィンドウに表示するメッセージを設定
●shop_InnSleep
宿の処理で「おやすみなさい」のメッセージが翌朝まで表示されるのは変なのでメッセージを上書きして消すようにしました。

CODE:

//----------------------------------------------------------------------
//	宿屋:宿泊
//----------------------------------------------------------------------
static int shop_InnSleep(PlayerParam_t *playerParam,int price)
{
	int shopFrame = STM_GetFrameCount(s_ShopStateObj);	//SHOPのフレーム
	#define	FADEOUT_IN_FRAMES	120
	
	//	最初?
	if( shopFrame==0 ) {
		//	宿泊料金を頂く
		playerParam->money -= price;
		//	回復処理
		party_Inn(playerParam);
		//	メッセージウィンドに表示
		winmng_SetActiveWin(s_MsgWinNo);
		winmng_SetMsg("",MSG_MODE_PAUSE_MARK);	//	ウィンドウに表示するメッセージを設定
	}
	
	//	毎フレームの更新
	winmng_UpdateMsg(MES_SPEED);
	
	//	所定フレームが過ぎたら遷移する。
	if( shopFrame > FADEOUT_IN_FRAMES ) {
	後略

[gameMain.cpp]
ゲームメインのプログラム側です。
●インクルード
ヘッダのインクルードを追加します。

CODE:

#include "battle.h"
●内部関数
デバッグ用の内部関数です。

CODE:

static void GameMain_DebugInfo(const char*func);
●マクロ
デバッグ処理用のマクロを追加します。

CODE:

//----------------------------------------------------------------------
//	マクロ
//----------------------------------------------------------------------

//	デバッグ情報をコンソール出力するマクロ
#define DEBUG_INFO() GameMain_DebugInfo(__FUNCTION__);
//	デバッグSW
#define DEBUG_INFO_SW (0)
●定数
バトル状態がシンプルになったのでGAMEMAIN_STATE_???を整理しました。

CODE:

//	ゲーム本編の状態
enum GameMainState_t {
	GAMEMAIN_STATE_MAPLOAD,		//マップロード処理
	GAMEMAIN_STATE_EVENT,		//イベント中
	GAMEMAIN_STATE_SHOP,		//お店で買い物中
	GAMEMAIN_STATE_MOVE,		//マップ移動中
	GAMEMAIN_STATE_MENU,		//メニュー中
	GAMEMAIN_STATE_BATTLE,		//戦闘中

	GAMEMAIN_STATE_MAX,		//ゲーム本編の状態の数
};

BattleState_tは無くなりました。削除してください。

CODE:

//	バトルの結果
enum BattleState_t {
	BATTLE_STATE_CONTINUE,		//バトル継続
	BATTLE_STATE_GAMEOVER,		//ゲームオーバー
	BATTLE_STATE_WIN,			//戦闘に勝利
};
●変数
ゲーム全体のフレーム数をカウントするカウンタを追加します。

CODE:

//	ゲーム全体のフレーム
static unsigned int s_GameTotalFrame = 0;
●GameMain_Init
初回の無条件初期化の呼び出し関数を整理して、battle_Init()を追加。

GAMEMAIN_INITTYPE_FIRSTSTARTなら、主人公の座標を初期化して"town"マップで初期化してEVENT_TYPE_NEW_STARTを起動イベントにして、最後にプレーヤーのパラメータを初期化します。

GAMEMAIN_INITTYPE_RESTARTなら、主人公の座標を初期化して"town"マップで初期化してEVENT_TYPE_GOV_RESTARTを起動イベントにして、最後にプレーヤーのパラメータを回復してお金を半分に減らします。

CODE:

//----------------------------------------------------------------------
//	ゲーム本編の初期化
//----------------------------------------------------------------------
void GameMain_Init(GameMainInitType_t initType)
{
	//	モードの設定
	s_GameMainData.initType = initType;
	
	//	初回?
	if( s_GameMainData.bFirstInit == FALSE ) {
		s_GameMainData.bFirstInit = TRUE;
		//	ゲーム本編の状態管理オブジェクトの作成。
		//	オブジェクトと言ってもC++のクラスとは無関係です。
		s_GameMainData.StateObj = STM_Init(GAMEMAIN_STATE_MAX);
		//	主人公キャラオブジェクトの作成。
		s_GameMainData.mapMoveData.PlayerObj = char_Load(PLAYER_FILENAME,PLAYER_CHAR_XNUM,PLAYER_CHAR_YNUM);
		//	マップ管理の初期化
		map_Init(g_MapDefDatas);
		//	メニュー初期化
		menu_Init();
		//	SHOP初期化
		shop_Init();
		//	バトルの初期化
		battle_Init();
	}
	
	//	初期化タイプ別の処理
	switch( initType ) {
	case GAMEMAIN_INITTYPE_FIRSTSTART:	//最初のスタート
		//	主人公キャラオブジェクトの仮の初期座標や向き
		s_GameMainData.mapMoveData.player_px = ((SCREEN_X/CHAR_PIXEL_SIZEX)/2-1) * CHAR_PIXEL_SIZEX;
		s_GameMainData.mapMoveData.player_py = ((SCREEN_Y/CHAR_PIXEL_SIZEY)/2+2) * CHAR_PIXEL_SIZEY;
		s_GameMainData.mapMoveData.player_muki = CHAR_MUKI_DOWN;
		//	イベント処理の初期化
		event_Init("town");	//初期スタートのマップ情報
		//	起動するイベントの情報を設定する。
		event_SetEvent(EVENT_TYPE_NEW_START,0);	//初期スタート時イベント
		//	パーティ(プレーヤーのパラメータ)を初期化
		party_init(&(s_GameMainData.playerParam));
		break;
		
	case GAMEMAIN_INITTYPE_RESTART:		//ゲームオーバーのリスタート
		//	主人公キャラオブジェクトの仮の初期座標や向き
		s_GameMainData.mapMoveData.player_px = ((SCREEN_X/CHAR_PIXEL_SIZEX)/2-1) * CHAR_PIXEL_SIZEX;
		s_GameMainData.mapMoveData.player_py = ((SCREEN_Y/CHAR_PIXEL_SIZEY)/2+2) * CHAR_PIXEL_SIZEY;
		s_GameMainData.mapMoveData.player_muki = CHAR_MUKI_DOWN;
		//	イベント処理の初期化
		event_Init("town");	//初期スタートのマップ情報
		//	起動するイベントの情報を設定する。
		event_SetEvent(EVENT_TYPE_GOV_RESTART,0);	//初期スタート時イベント
		//	パーティ(プレーヤーのパラメータ)のHP/MPを戻してお金を半分に。
		s_GameMainData.playerParam.hp = s_GameMainData.playerParam.hp_max;
		s_GameMainData.playerParam.mp = s_GameMainData.playerParam.mp_max;
		s_GameMainData.playerParam.money /= 2;
		break;
	}
	
	//	ゲーム本編の状態を強制初期化
	STM_ResetState(s_GameMainData.StateObj,GAMEMAIN_STATE_MAPLOAD);//マップロード処理へ遷移。
}
●GameMain_End
バトルの終了処理battle_End();を追加。

CODE:

//----------------------------------------------------------------------
//	ゲーム本編の終了
//----------------------------------------------------------------------
void GameMain_End()
{
	//	ゲーム本編の状態管理オブジェクトの破棄
	STM_End(s_GameMainData.StateObj);
	//	主人公キャラオブジェクトの破棄。
	char_Delete(s_GameMainData.mapMoveData.PlayerObj);
	//	マップ管理の終了
	map_End();
	//	メニュー終了
	menu_End();
	//	SHOP終了
	shop_End();
	//	バトルの終了処理
	battle_End();
}
●GameMain
GAMEMAIN_STATE_BATTLE以降の状態を整理してバトルの状態をシンプルにしました。
GameMain_Battle()の戻り値を受けて、次の状態遷移を決定します。
BATTLE_RET_CONTINUEやBATTLE_RET_BATTLEINは継続します。
BATTLE_RET_GAMEOVERはゲームオーバーに遷移します。
BATTLE_RET_ENDはバトル突入の前の状態に遷移して戻ります。

ゲーム全体のフレームカウントs_GameTotalFrame++;を追加。

CODE:

//----------------------------------------------------------------------
//	ゲーム本編
//----------------------------------------------------------------------
GameState_t GameMain()
{
	//	ゲーム状態の更新・フレームカウント
	STM_UpdateState(s_GameMainData.StateObj);
	
	//	ゲーム本編の状態で振り分ける。
	switch( STM_GetState(s_GameMainData.StateObj) ) {
	case GAMEMAIN_STATE_MAPLOAD:
		//マップロード処理
		GameMain_MapLoad();
		break;
	
	case GAMEMAIN_STATE_EVENT:
		//イベント中
		if( GameMain_Event() ) {
			//	エンディングに遷移
			return GAME_STATE_ENDING;		//ゲーム全体の状態を遷移。
		}
		break;
	
	case GAMEMAIN_STATE_SHOP:
		//お店で買い物中
		GameMain_Shop();
		break;
	
	case GAMEMAIN_STATE_MOVE:
		//マップ移動中
		GameMain_MapMove();
		break;
	
	case GAMEMAIN_STATE_MENU:
		//メニュー中
		GameMain_Menu();
		break;
	
	case GAMEMAIN_STATE_BATTLE:
		//	戦闘処理
		switch( GameMain_Battle() ) {
		case BATTLE_RET_CONTINUE:		//バトル継続
		case BATTLE_RET_BATTLEIN:		//バトル開始エフェクト
			break;
		
		case BATTLE_RET_GAMEOVER:		//ゲームオーバー
			//	ゲーム全体の状態を遷移。
			return GAME_STATE_GAMEOVER;
		
		case BATTLE_RET_END:			//戦闘終了
			//	次のフレームで遷移。
			STM_SetBackState(s_GameMainData.StateObj,s_GameMainData.beforeBattleInState);//バトル突入前の状態に遷移。
			break;
		}
		break;
	}
	
	//	ゲーム全体のフレームカウント
	s_GameTotalFrame++;
	
	//	ゲーム全体の状態を継続
	return g_MainData.gameState;
}
●GameMain_Event
デバッグ情報マクロの追加とイベント中のマップ移動へ遷移(EVENT_RTN_MAPMOVE)で最初のフレームで抜ける場合だけ、イベントを踏んだことがバレないように移動処理を呼びます。

これは、
フレーム0 移動処理 → イベント遷移
フレーム1 イベント処理 → 移動遷移
フレーム2 移動処理
となると1フレーム移動していないフレームが発生するためです。

強制的に移動処理を入れることで
フレーム0 移動処理 → イベント遷移
フレーム1 イベント処理 → 移動遷移(移動処理)
フレーム2 移動処理
となり移動処理のないフレームが無くなります。

マップとキャラクタ表示処理でフレーム数は連続性のあるs_GameTotalFrameを使うように変更しました。

CODE:

//----------------------------------------------------------------------
//	イベント中
//----------------------------------------------------------------------
static int GameMain_Event()
{
	int bEnding = FALSE;	//エンディングではない
	
	//	デバッグ情報マクロ
	DEBUG_INFO();
	
	//	今のフレーム
	int frame = STM_GetFrameCount(s_GameMainData.StateObj);
	
	//	イベントを実行する。
	int rtnCode = event_Main(frame,&(s_GameMainData.mapMoveData),&(s_GameMainData.playerParam)) ;
	switch( rtnCode ) {
	case EVENT_RTN_CONTINE:	//イベント継続
		//	この状態を継続します。
		break;
	
	case EVENT_RTN_MAPMOVE:	//マップ移動へ遷移。  
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_MOVE);//マップ移動へ遷移。
		//	最初のフレームで抜ける場合は、
		if( frame == 0 ) {
			//	イベントを踏むとがたつくので、マップ移動を呼び出しておく。
			GameMain_MapMove();
			//	表示をGameMain_MapMoveで行うのでこのまま抜ける。
			return FALSE;
		}
		break;
	
	case EVENT_RTN_MAPCHANGE://マップチェンジに遷移。 
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_MAPLOAD);//マップロード処理に遷移
		break;
	
	case EVENT_RTN_SHOP:	//SHOPへ遷移。
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_SHOP);//お店に遷移
		break;
	
	case EVENT_RTN_BATTLE:	//バトルに遷移。
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_BATTLE);//戦闘開始へ遷移。
		break;
	
	case EVENT_RTN_ENDING:	//エンディングへ遷移。  
		bEnding = TRUE;
		break;
	}
	
	//	マップとキャラクタ表示処理
	mapMove_Draw(&s_GameMainData.mapMoveData,s_GameTotalFrame,FALSE);
	
	//	イベントを描画する
	event_Draw(frame);
	
	//	エンディング遷移条件
	return bEnding;
}
●GameMain_Shop
マップとキャラクタ表示処理でフレーム数は連続性のあるs_GameTotalFrameを使うように変更しました。

CODE:

//----------------------------------------------------------------------
//	お店で買い物中
//----------------------------------------------------------------------
static void GameMain_Shop()
{
	//	今のフレーム
	int frame = STM_GetFrameCount(s_GameMainData.StateObj);
	
	//	実際のSHOP処理
	if( shop_Main(frame,&(s_GameMainData.playerParam)) ) {
		//	前の状態に戻す。フレームカウントはそのまま。
		STM_SetBackState(s_GameMainData.StateObj,GAMEMAIN_STATE_EVENT);//イベント中へ遷移。
	}
	
	//	マップとキャラクタ表示処理
	mapMove_Draw(&s_GameMainData.mapMoveData,s_GameTotalFrame,FALSE);
	
	//	SHOPの描画
	shop_Draw();
}
●GameMain_MapMove
デバッグ情報マクロの追加とマップとキャラクタ表示処理でフレーム数は連続性のあるs_GameTotalFrameを使うように変更しました。
それと、デバッグ用に左CTRLキーでエンカウントしなくなる裏技を追加しました。

CODE:

//----------------------------------------------------------------------
//	マップ移動中
//----------------------------------------------------------------------
static void GameMain_MapMove()
{
	//	デバッグ情報マクロ
	DEBUG_INFO();
	
	//	実際の移動処理
	mapMove_Main(&s_GameMainData.mapMoveData);
	
	//	移動の結果判定
	switch( s_GameMainData.mapMoveData.rtnCode ) {
	case MAPMOVE_RTNCODE_CONTINUE:	//移動継続
		//	何もしない。
		break;
	
	case MAPMOVE_RTNCODE_EVENT:		//イベント発生
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_EVENT);//イベント中へ遷移。
		//	イベントに関する情報の受け渡しとかもここでを予定。
		break;
	
	case MAPMOVE_RTNCODE_ENCOUNT:	//エンカウント
		if(  g_MainData.key[KEY_INPUT_LCONTROL] == 0 ) {
			STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_BATTLE);//戦闘開始へ遷移。
		}
		break;

	case MAPMOVE_RTNCODE_MENU:		//メニュー遷移
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_MENU);//メニューへ遷移。
		break;
	}
	
	//	マップとキャラクタ表示処理
	mapMove_Draw(&s_GameMainData.mapMoveData,s_GameTotalFrame,FALSE);
}
●GameMain_Menu
またまたですが、マップとキャラクタ表示処理でフレーム数は連続性のあるs_GameTotalFrameを使うように変更しました。

CODE:

//----------------------------------------------------------------------
//	メニュー中
//----------------------------------------------------------------------
static void GameMain_Menu()
{
	//	今のフレーム
	int frame = STM_GetFrameCount(s_GameMainData.StateObj);
	
	//	実際のメニュー処理
	if( menu_Main(frame,&(s_GameMainData.playerParam)) ) {
		//	メニューが終了ならば全ウィンドウを消去。
		winmng_DelAllWin();
		//	次のフレームで遷移。
		STM_ChangeState(s_GameMainData.StateObj,GAMEMAIN_STATE_MOVE);//マップ移動へ遷移。
	}
	
	//	マップとキャラクタ表示処理
	mapMove_Draw(&s_GameMainData.mapMoveData,s_GameTotalFrame,FALSE);
	
	//	有効なウィンドウの描画
	winmng_Draw();
}
●GameMain_Battle
ここは、ほぼ新作です。
最初のフレームならバトル開始前の状態を保存して戦闘開始処理を(battle_In)呼び出します。
battle_Inのパラメータはエンカウントした敵の情報とボスかどうかのフラグです。

バトル処理(battle_Main)は無条件に呼び出します。
戻り値がBATTLE_RET_BATTLEINならバトル開始エフェクト中なのでエフェクトの下にマップを描画します。
そのあとでバトルに関する描画を行います。

バトル処理(battle_Main)の戻り値は遷移の制御に使います。

CODE:

//----------------------------------------------------------------------
//	戦闘処理
//----------------------------------------------------------------------
static int GameMain_Battle()
{
	//	今のフレーム
	int frame = STM_GetFrameCount(s_GameMainData.StateObj);
	
	//	最初のフレーム?
	if( frame == 0 ) {
		//	バトル開始前の状態を保存する。
		s_GameMainData.beforeBattleInState = STM_GetBackState(s_GameMainData.StateObj);
		//	戦闘開始処理
		battle_In(s_GameMainData.mapMoveData.encountEnemyType,s_GameMainData.mapMoveData.bBossEnemy);
	}
	
	//	バトルの制御処理
	int rtn = battle_Main(&(s_GameMainData.playerParam),frame);
	
	//	バトル開始エフェクト中?
	if( rtn == BATTLE_RET_BATTLEIN ) {
		//	マップとキャラクタ表示処理
		mapMove_Draw(&s_GameMainData.mapMoveData,s_GameTotalFrame,FALSE);
	}
		
	//	バトルの描画処理
	battle_Draw();
	
	//	バトルの戻り値を持ち帰る。
	return rtn;
}
●GameMain_DebugInfo
イベント遷移を確認するためのデバッグ関数です。
経過時間とフレームとキャラクタの座標を表示して移動に支障がないか確認しています。

CODE:

//----------------------------------------------------------------------
//	デバッグ情報処理
//----------------------------------------------------------------------
static void GameMain_DebugInfo(const char*func)
{
#if DEBUG_INFO_SW
	static int count = 0;
	int ElapsedTime = 0;
	//	経過時間を求める。
	if( count!=0 ) {
		ElapsedTime = GetNowCount() - count;
	}
	//	経過時間他を表示。
	printf( "%-16s:%d %d py:%d\n", func, s_GameTotalFrame, ElapsedTime, s_GameMainData.mapMoveData.player_py );
	//	時間を記録。
	count = GetNowCount();
#endif
}
以上ゲームメイン部分でした。
いやぁ、コードが長いですね。
でも、まだまだありますので続きます。

バトル本編は、もう少しお待ちくだい。

コメントはまだありません。