C言語 入門 STGの作り方

十三日目 ステージ遷移

初心者向けSTG作成入門

HOME/STGの作り方 目次/十三日目 ステージ遷移/

広告

↓2016年02月29日発売↓

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

新品価格
¥2,462から
(2016/5/10 22:16時点)

もっと!C、C++言語本

目次へ戻る

ステージ遷移

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

「swicth」文で場面分け

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

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

#include "DxLib.h"

int my_get_key(void);

int key[256];
int gamecount;

int Color_White;

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

	/*ここは最初の一度だけ実行されるので*/
	/*変数の初期化などを書く*/
	gamecount = 0;
	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 gamecount;

int Color_White;
int game_state;

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

	/*ここは最初の一度だけ実行されるので*/
	/*変数の初期化などを書く*/
	game_state = 0;
	gamecount = 0;
	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」という変数を用意してその値を変化させる事によって場面を切り替えていきます。

簡単な例

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

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

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

画像(s-13-1)

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

画像(s-13-2)

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

そして書いてある条件を満たすと、

画像(s-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 < DISP_ENEMY_MAX * 2; 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,-250,260,0,0,1,10,1,
	1,300,-200,260,0,0,1,10,0,
	2,500,-150,260,0,0,1,10,1,
	3,700,-100,260,0,0,1,10,0,
	4,900,-50,260,0,0,1,10,1,
	5,1800,50,260,0,0,1,10,0,
	6,2000,100,260,0,0,1,10,1,
	7,2200,150,260,0,0,1,10,0,
	8,2400,200,260,0,0,1,10,1,
	9,2600,250,260,0,0,1,10,0
	},
	{
	10,100,-250,260,0,0,1,10,1,
	11,300,-200,260,0,0,1,10,0,
	12,500,-150,260,0,0,1,10,1,
	13,700,-100,260,0,0,1,10,0,
	14,900,-50,260,0,0,1,10,1,
	15,1800,50,260,0,0,1,10,0,
	16,2000,100,260,0,0,1,10,1,
	17,2200,150,260,0,0,1,10,0,
	18,2400,200,260,0,0,1,10,1,
	19,2600,250,260,0,0,1,10,0
	},
	{
	20,100,-250,260,0,0,1,10,1,
	21,300,-200,260,0,0,1,10,0,
	22,500,-150,260,0,0,1,10,1,
	23,700,-100,260,0,0,1,10,0,
	24,900,-50,260,0,0,1,10,1,
	25,1800,50,260,0,0,1,10,0,
	26,2000,100,260,0,0,1,10,1,
	27,2200,150,260,0,0,1,10,0,
	28,2400,200,260,0,0,1,10,1,
	29,2600,250,260,0,0,1,10,0
	}
};

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

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

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

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

void my_set_boss(){
 for (int i = 0; i < STAGE_BOSS_MAX; i++){
  if (ary_boss_data[stage_no][(i * BOSS_DATA_MAX) + 1] == gamecount){
   for (int j = 0; j < DISP_BOSS_MAX; j++){
    if (boss[j].move_flag == 0){
     boss[j].move_flag = 1;
     boss[j].serial_no = ary_boss_data[stage_no][i * BOSS_DATA_MAX];
    break;
   }
  }
 }
 }
}

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

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

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

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

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

void my_gameover(){
	if(gameover_flag == 1){
		gamecount3++;
		if(gamecount3 == 200){
			if(p1.life == 1){
				stage_no = 0;
				p1.life = 3;
				p1.shot_type = 0;
				score = 0;
				gamecount = 0;
				game_state = 0;
			}
			else{
				p1.life--;
				p1.shot_type = 0;
				gamecount = 0;
				game_state = 5;
			}
		}
	}
	if(gameclear_flag == 1){
		stage_no++;
		gamecount = 0;
		game_state = 25;
	}
}

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

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

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

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

残機がない場合はプレイヤー残機を初期状態に戻してその他カウント等も全てもとに戻してオープニング画面に。

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

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

組み合わせ

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

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

/*最初に全初期化が入ります*/
switch(game_state){
case 0:
	/*オープニング*/
	break;
case 5:
	/*中間初期化*/
	break;
case 10:
	/*ステージ、残機表示*/
	break;
case 15:
	/*プレイヤー登場イベント*/
	break;
case 20:
	/*ゲームメインループ*/
	break;
case 25:
	/*プレイヤー退散イベント*/
	break;
case 30:
	/*エンディング*/
	break;
}

こんな感じになります。

メインループ以外の「game_state」の切り替えやプレイヤー登場、退散のイベント処理など直接記述してあってあまりキレイなソースとは言い難いのでどうにか関数にまとめるなど工夫した方が良いと思われます。

加速・減速

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

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

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入門ではこれを少し変形して速度に直接値を乗算するカタチをとっております。

画像(s-13-4)

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

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

中間ソース12

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

次回

十四日目 敵の動きを追加

□ページの先頭へ□

□HOME□

広告

↓2014年06月20日発売↓

14歳からはじめるC言語わくわくゲームプログラミング教室 Visual Studio 2013編

新品価格
¥2,500から
(2016/5/10 22:17時点)

↓2014年10月25日発売↓

超本格! サンプルで覚えるC言語 3Dゲームプログラミング教室

新品価格
¥3,110から
(2016/5/10 22:18時点)

↓2013年07月25日発売↓

小学生からはじめるわくわくプログラミング

新品価格
¥2,052から
(2016/5/10 22:21時点)

↓2016年05月13日発売↓

小学生からはじめるわくわくプログラミング2

新品価格
¥2,052から
(2016/5/10 22:22時点)

もっと!C、C++言語本

□ページの先頭へ□

□HOME□