画像(logo)

HOME/[C言語DXライブラリ]STGの作り方 目次/十三日目 ステージ遷移

広告

[C言語 DXライブラリ STGの作り方]
十三日目 ステージ遷移

広告

↓2016年02月29日発売↓

12歳からはじめる ゼロからのC言語 ゲームプログラミング教室

目次へ戻る

ステージ遷移

今回はステージを進めたりゲームオーバーになったりと、そのへんの処理を作りたいと思います。

「swicth」文で場面分け

ステージ遷移の基本は「swicth」文で各場面を分けます。

どういう事かと言いますと最初にやった最低限のプログラムを思い出してください。

#include "DxLib.h"

int my_get_key(void);

int key[256];

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){
	ChangeWindowMode(TRUE);
	DxLib_Init();
	SetDrawScreen(DX_SCREEN_BACK);

	/*ここは最初の一度だけ実行されるので*/
	/*変数の初期化などを書く*/

	int gamecount = 0;
	int Color_White = GetColor(255,255,255);

	while (ScreenFlip()==0 && ProcessMessage()==0
		 && ClearDrawScreen()==0 && my_get_key()==0){

		/*ここにプログラムを書く*/
		
		DrawFormatString(0,0,Color_White, "%d",gamecount);
		gamecount++;
		
	}
	DxLib_End();
	return 0;
}
	
int my_get_key(){
	char keys[256];
	GetHitKeyStateAll(keys);
	for (int i = 0; i < 256; i++){
		if (keys[i] != 0){
			key[i]++;
		}
		else{
			key[i] = 0;
		}
	}
	return 0;
}

この中の「while」ループの中に、コメントにもあるようにメインプログラムを書いている状態ですよね。

ここの部分を「switch」文によって分けてしまおうというワケです。

具体的にはこんな感じになります。

#include "DxLib.h"

int my_get_key(void);

int key[256];

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){
	ChangeWindowMode(TRUE);
	DxLib_Init();
	SetDrawScreen(DX_SCREEN_BACK);

	/*ここは最初の一度だけ実行されるので*/
	/*変数の初期化などを書く*/

	int gamecount = 0;
	int game_state = 0;
	int Color_White = GetColor(255,255,255);

	while (ScreenFlip()==0 && ProcessMessage()==0
		 && ClearDrawScreen()==0 && my_get_key()==0){

		/*ここにプログラムを書く*/

		switch(game_state){
		case 0:
			/*オープニングシーン*/
			break;
		case 1:
			/*ゲームシーン*/
			break;
		case 2:
			/*エンディングシーン*/
			break;
		default:
			break;
		}

		gamecount++;
		
	}
	DxLib_End();
	return 0;
}
	
int my_get_key(){
	char keys[256];
	GetHitKeyStateAll(keys);
	for (int i = 0; i < 256; i++){
		if (keys[i] != 0){
			key[i]++;
		}
		else{
			key[i] = 0;
		}
	}
	return 0;
}

それぞれの場面を表す変数、今回の場合では「game_state」という変数を用意してその値を変化させる事によって場面を切り替えていきます。

簡単な例

このへんはやった方が早いと思いますので簡単な例を試してみましょう。

前回のソースにごくごく簡単な場面遷移を入れたものになります。

ステージ遷移確認プログラム

画像(cdxs_13_1)

オープニング画面でエンターを押すと・・・。

画像(cdxs_13_2)

ゲームのメインループに場面が切り替わります。

そしてプレイヤーの体力がなくなった場合、またはゲーム内のカウントが「5000」に達した時に

画像(cdxs_13_3)

エンディング!!

なんとなくお分かりいただけたでしょうか?

これをもっと複雑にしていろんな場面に切り替えていきます。

大事な初期化

言われなくても初期化は大事ですが・・・。

プログラムが複雑になればなるほどこの初期化的な作業がものすごく重要になってきます。

たった一つの変数の初期化を忘れただけでも悲しい事に・・・。

まあ余談ですが、先ほどの例ではオープニング→ゲーム→エンディングと場面が先に進むだけでしたのでそこに触れる事もありませんでしたが実際はこれらの場面をいったりきたりする事になると思います。

そこで大事になるのが先ほども言いましたそれぞれの変数の初期化作業になります。

大きく分けて最初に行う全初期化と場面が戻る時に行う中間初期化みたいな部分に分かれます。

全ての初期化

これは今までやっていた最初に行う全ての初期化になります。

メインの「while」ループに入る前に書いてあった処理ですね。

中間の初期化

次に中間の初期化ですが当STG入門ではステージを移動する時、プレイヤーの体力が「0」になった時、ゲームオーバー時などは「gamecount」を「0」にリセットして場面をいったん元に戻す作業を行います。

つまりそこで出現した敵やショットなどを全てクリアするという事ですね。

ただここでその「gamecount」だけを戻したトコロで全てがリセットされるのかと言いますと、その戻す前の「gamecount」を一時記録してそこから敵やら敵のショットやらの消滅までのカウントも計られていたので「gamecount」のみ戻すだけだととんでもない事になります。

ではどうするのか・・・?

結論から言いますとそれぞれの「move_flag」「init_flag」この2つを元に戻すだけで基本的にはそれぞれの構造体の再初期化を行ってくれます。

void my_init_player_shot2(){
	for(int i = 0;i < PLAYER_SHOT_MAX;i++){
		ps1[i].init_flag = 0;
		ps1[i].move_flag = 0;
	}
}

void my_init_enemy2(){
	for (int i = 0; i < DISP_ENEMY_MAX; i++){
		enemy[i].init_flag = 0;
		enemy[i].move_flag = 0;
	}
}

void my_init_enemy_shot2(){
	for (int i = 0; i < ENEMY_SHOT_MAX; i++){
		enemy_shot[i].init_flag = 0;
		enemy_shot[i].move_flag = 0;
	}
}

いくつか抜き出して来ましたが消滅するタイミングで落としていたこの2つの「move_flag」と「init_flag」を強制的にこちら側で落としてあげれば初期化されるというワケです。

データを2次元配列に

ではもう少し具体的に話を進めていきます。

まずステージを進めるという事でその分の敵のデータを2次元配列にして増やします。

#define STAGE_MAX 3
#define STAGE_ENEMY_MAX 10
#define ENEMY_DATA_MAX 9

int ary_enemy_data[STAGE_MAX][STAGE_ENEMY_MAX * ENEMY_DATA_MAX] = {
	{
	0,100,-180,260,0,0,10,10,1,
	1,300,-140,260,0,0,10,10,0,
	2,500,-100,260,0,0,10,10,1,
	3,700,-60,260,0,0,10,10,0,
	4,900,-20,260,0,0,10,10,1,
	5,1800,20,260,0,0,10,10,0,
	6,2000,60,260,0,0,10,10,1,
	7,2200,100,260,0,0,10,10,0,
	8,2400,140,260,0,0,10,10,1,
	9,2600,180,260,0,0,10,10,0
	},
	{
	10,100,-180,260,0,0,10,10,1,
	11,300,-140,260,0,0,10,10,0,
	12,500,-100,260,0,0,10,10,1,
	13,700,-60,260,0,0,10,10,0,
	14,900,-20,260,0,0,10,10,1,
	15,1800,20,260,0,0,10,10,0,
	16,2000,60,260,0,0,10,10,1,
	17,2200,100,260,0,0,10,10,0,
	18,2400,140,260,0,0,10,10,1,
	19,2600,180,260,0,0,10,10,0
	},
	{
	20,100,-180,260,0,0,10,10,1,
	21,300,-140,260,0,0,10,10,0,
	22,500,-100,260,0,0,10,10,1,
	23,700,-60,260,0,0,10,10,0,
	24,900,-20,260,0,0,10,10,1,
	25,1800,20,260,0,0,10,10,0,
	26,2000,60,260,0,0,10,10,1,
	27,2200,100,260,0,0,10,10,0,
	28,2400,140,260,0,0,10,10,1,
	29,2600,180,260,0,0,10,10,0
	}
};

とりあえずは3ステージ分用意しました。

中身はほぼ同じデータを並べただけです。

そして現在のステージ数を表す変数を用意してこの配列を読み込むデータ読み込みの関数も少し変更します。

今回「stage_num」という変数がステージ数を表す変数になります。

void my_set_enemy(){
	for (int i = 0; i < STAGE_ENEMY_MAX; i++){
		if (ary_enemy_data[stage_num][(i * ENEMY_DATA_MAX) + 1] == gamecount && gamecount > 0){
			for (int j = 0; j < DISP_ENEMY_MAX; j++){
				if (enemy[j].move_flag == 0){
					enemy[j].move_flag = 1;
					enemy[j].serial_num = ary_enemy_data[stage_num][i * ENEMY_DATA_MAX];
					enemy[j].appear_point = ary_enemy_data[stage_num][(i * ENEMY_DATA_MAX) + 1];
					enemy[j].first_x = ary_enemy_data[stage_num][(i * ENEMY_DATA_MAX) + 2];
					/*省略*/
					break;
				}
			}
		}
	}
}

これで各ステージ毎のデータを読み出せるようになりました。

ボスも同じようにすればデータ部分は完了です。

クリア時、死亡時、ゲームオーバー時

あとはそれぞれ状況が少し異なる場合の処理の振り分けをしていきます。

今回は「my_change_state()」という関数を作ってみました。

void my_change_state(){
	if(gameover_flag == 1){
		gamecount3++;
		if(gamecount3 == 200){
			if(p1.life == 1){
				game_state = 2;
			}
			else{
				p1.life--;
				p1.shot_type = 0;
				game_state = 5;
			}
		}
	}
	else if(gameclear_flag == 1){
		stage_num++;
		gamecount = 0;
		game_state = 25;
	}
}

プレイヤー死亡時の「gameover_flag」とステージクリア時の「gameclear_flag」というフラグを用意して死亡時とステージクリア時の処理の振り分けをしていきます。

「gameover_flag」「gameclear_flag」は当たり判定関数内でフラグの切り替えをしております。

まず死亡時ですが、死亡した瞬間場面が切り替わるのではさみしいのでやはりここでもカウント用の変数「gamecount3」を新たに用意して「200」カウント経過した時点で処理をします。

内容はプレイヤーの残機がまだある場合は残機を減らしてショットの種類を元に戻してそのステージの最初に。

残機がない場合は最初の初期化まで一気に遡り初期状態に戻してからオープニング画面に。

ステージクリア時はステージを進めてカウントを戻しているだけですね。

この関数をメインループに設置します。

組み合わせ

そしてこれらを組み合わせてオープニング、エンディング等好みのシーンを加えれば場面遷移は完了です。

いちよう当STG作成入門の簡単な場面遷移になります。

switch(game_state){
case 0:
	/*画像などの初期化部分*/
	/*画像や音楽などは一度読み込んでしまえば*/
	/*大丈夫です!*/
	break;
case 2:
	/*全初期化部分*/
	break;
case 4:
	/*オープニング*/
	break;
case 5:
	/*中間初期化部分*/
	break;
case 10:
	/*ステージ、残機表示*/
	break;
case 15:
	/*プレイヤー登場イベント*/
	break;
case 20:
	/*ゲームメインループ*/
	break;
case 25:
	/*プレイヤー退散イベント*/
	break;
case 30:
	/*エンディング*/
	break;
default:
	break;
}

こんな感じになります。

それぞれお好みで工夫してみてください。

画像(cdxs_13_4)

ステージ1 スタート!!!

加速・減速

ここからは余談になりますが、プレイヤー登場、退散時の背景画像の加速・減速処理ですが、これはある程度物理的な動き(正確ではないです)をさせたい時の小技のようなものです。

軽く使い方を説明します。

HAKUHIN's home pageさんのサイトを参考にさせて頂きました。

加速

/*座標*/
double x = 0;
double y = 0;

/*x方向とy方向の速度*/
double dx = 1;
double dy = 1;

/*加速度*/
double ax = 1;
double ay = 1;

いつもの座標の他に速度と加速度を用意します。

そして座標を直接動かすのではなくこの速度を加算する事によって動かしていきます。

さらにその速度に加速度を加算して加速していきます。

/*速度加算*/
x += dx;
y += dy;

/*加速度加算*/
dx += ax;
dy += ay;

これを繰り返す事によって加速していきます。

減速

/*座標*/
double x = 0;
double y = 0;

/*x方向とy方向の速度*/
double dx = 1;
double dy = 1;

/*摩擦係数*/
double friction = 0.9;

いつもの座標の他に速度と摩擦係数を用意します。

そして座標を直接動かすのではなくこの速度を加算する事によって動かしていきます。

さらにその速度に摩擦係数(0〜1)を乗算して減速していきます。

/*速度加算*/
x += dx;
y += dy;

/*摩擦係数乗算*/
dx *= friction;
dy *= friction;

これを繰り返す事によって減速していきます。

どちらもひたすら加速、減速させ続けれられてしまうのである速度に達したら一定の速度になるような工夫が必要です。

摩擦係数は「0」に近いほど急に止まり「1」に近いほど緩やかに止まります。

それっぽい動きになってなかなか良いのでいろいろ使ってみてください。

当STG入門ではこれを少し変形して速度に直接値を乗算するカタチをとっております。

ここまでの中間ソースになります。

中間ソース12

次回は敵の動きの種類を追加したいと思います。

次回

十四日目 敵の動きを追加

□ページの先頭へ□

□目次へ戻る□

□HOME□

広告

↓2017年06月16日発売↓

やさしいC 第5版 (「やさしい」シリーズ)

新品価格
¥2,700から
(2017/5/1 13:05時点)

↓2014年08月09日発売↓

新・明解C言語 入門編 (明解シリーズ)

新品価格
¥2,484から
(2017/5/1 13:08時点)

↓2016年02月20日発売↓

新・解きながら学ぶC言語

新品価格
¥2,160から
(2017/5/1 13:10時点)

↓2017年02月11日発売↓

C言語プログラミング基本例題88 88

新品価格
¥3,024から
(2017/5/1 13:12時点)

↓2016年12月15日発売↓

Cの絵本 第2版 C言語が好きになる新しい9つの扉

新品価格
¥1,490から
(2017/5/1 13:13時点)

↓2017年02月08日発売↓

新・明解C言語で学ぶアルゴリズムとデータ構造 (明解シリーズ)

新品価格
¥2,700から
(2017/5/1 13:15時点)