画像(logo)

HOME/[C言語DXライブラリ]STGの作り方 目次/五日目 プレイヤーのショット

広告

[C言語 DXライブラリ STGの作り方]
五日目 プレイヤーのショット

広告

↓2016年02月29日発売↓

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

目次へ戻る

プレイヤーのショット

今回はプレイヤーにショットを打たせてみましょう。

まずは中間ソース4の「my_draw_score_board()」を

void my_draw_score_board(){
	int y_point = 20;
	DrawBox(440, 0, 640, 480, Color_Gray, true);
	SetFontSize(14);
	for (int i = 0; i < 30; i++){
		DrawFormatString(450,y_point, Color_White, "ps1[%d].move_flag = %d",i, ps1[i].move_flag);
		y_point += 14;
	}
	SetFontSize(16);
}

こちらの内容に丸ごと置き換えて実行、Zキーでショットが打てるので試し打ちして右側の「ps1[].move_flag」と表示されている部分を眺めてみてください。

画像(cdxs_5_1)

これは「プレイヤーショットの入れ物が使用中かどうかを表すフラグ」の内容を表示したものになります。

「1」が使用中、「0」が未使用です。

ショットが発射されると使用中になり、一定時間たつと再び未使用になるのが確認できるかと思います。

こんな感じでプレイヤーショットのみならず敵や敵のショットなども限られた数の入れ物を使いまわしていくような感じになります。

ではあらためましてプレイヤーのショットの構造体になります。

#define PLAYER_SHOT_MAX 30

struct PLAYER_SHOT{
	double x[5];
	double y[5];
	double draw_x[5];
	double draw_y[5];
	int init_flag;
	int move_flag;
	int move_type;
	int flag[5];
	double angle[5];
	int max_bullet;
	int gamecount_point;
	double range;
};

struct PLAYER_SHOT ps1[PLAYER_SHOT_MAX];

軽く説明します。

double x[5];
double y[5];
double draw_x[5];
double draw_y[5];

まずこちらはプレイヤーの時と同じく計算する為の座標と画面に表示する為の座標になります。

「5」個座標がありますが、後ほど2way、5wayショットを放つために「5」個用意しております。

最初は正面一方向のみのショットを作っていくので座標は一つだけ使用します。

int move_type;
double angle[5];
int max_bullet;
double range;

少し順番は前後しますが「move_type」はショットの動き方を表す変数(ここで後ほど2way、5wayを切り替えるようになります)、「angle」はショットの飛ぶ角度、「max_bullet」は一度に飛ばすショットの最大弾数、「range」は当たり判定の範囲になります。

続けて

int move_flag;

これは「プレイヤーショットの入れ物」が使用中かどうかのフラグです。

「move_flag = 1」使用中

「move_flag = 0」未使用

になります。

int init_flag;

これはショットを動かす時の初期化用のフラグです。

「init_flag = 0」初期化前

「init_flag = 1」初期化済み

になります。

int gamecount_point;

これはその時の基準となる「gamecount」を記録します。

int flag[5];

これは表示するかどうかのフラグです。

「flag[] = 0」表示しない

「flag[] = 1」表示する

になります。

この「flag」含めちょっと役割がわかりづらいものもあるかと思いますので後ほど順を追って説明していきます。

そしてこの構造体一つが「プレイヤーショットの入れ物」一つ分になるのでひとまずこれを30個用意します。

#define PLAYER_SHOT_MAX 30
struct PLAYER_SHOT ps1[PLAYER_SHOT_MAX];

これを使いまわしていきます。

初期化

プレイヤーの時と同じようにプレイヤーショットも初期化します。

void my_init_player_shot(){
	for(int i = 0;i < PLAYER_SHOT_MAX;i++){
		ps1[i].init_flag = 0;
		ps1[i].move_flag = 0;
		ps1[i].move_type = 0;
		ps1[i].max_bullet = 0;
		ps1[i].gamecount_point = 0;
		ps1[i].range = 10;
		for(int j = 0;j < 5;j++){
			ps1[i].x[j] = 0;
			ps1[i].y[j] = 0;
			ps1[i].draw_x[j] = 0;
			ps1[i].draw_y[j] = 0;
			ps1[i].flag[j] = 0;
			ps1[i].angle[j] = 0;
		}
	}
}

グローバル域で宣言しているので何もしなければ「0」で初期化されますがいちよう全ての要素を初期化しておきます。

当たり判定範囲の「range」だけとりあえず「10」としております。

プレイヤーのショットの流れ

最初にも説明しましたが再度プレイヤーショットの流れを確認しましょう。

1・ショット発射ボタンを押す

2・現在未使用の「プレイヤーショットの入れ物」を調べて使用中に

3・その時のプレイヤーの「ショットの動き方」を登録

4・その「ショットの動き方」に応じて発射・計算・表示

5・終わったら再び「プレイヤーショットの入れ物」を未使用に

こんな感じでしたね。

ではさっそく発射ボタンから作っていきましょう。

発射ボタン

「my_move_player()」に発射ボタンを追加します。

if (key[KEY_INPUT_Z] % 5 == 1){
	my_set_player_shot(p1.shot_type);
}

とりあえず「z」ボタンで発射です。

「my_set_player_shot(p1.shot_type);」っていうのが現在未使用の「プレイヤーショットの入れ物」を調べる為の関数になります。

前回触れなかった「p1.shot_type」をここで引数に渡します。

これがそのまま先ほどのショットの切り替えの変数「shot_type」になります。

もう少し詳しく見ていきましょう。

key[KEY_INPUT_Z] % 5 == 1

「5」で割った余りが「1」ならって事です。

まずキー入力の仕組みのおさらいですが、入力のあったキーのトコロがものすごい速さでインクリメントされるのは覚えていますでしょうか?

それに対してその後の処理を加えるワケですが移動なんかと同じようにしてしまうと1秒間に60発ショットを打つことになってしまうので、それを少し抑えるためにさきほどの「% 5 == 1」としているワケです。

わかりやすく言うと「gamecount「5」カウントに一回弾を発射する」という感じで考えてもらえれば大丈夫です。

この割った余りというのはいろんな場面で使えるので覚えておきましょう。

意味不明です!という方はゲームテクニック集の「割った余り法」こちらを参考にしてみてください。

広告

未使用の「プレイヤーショットの入れ物」を調べる

「my_set_player_shot()」になります。

void my_set_player_shot(int shot_type){
	for(int i = 0;i < PLAYER_SHOT_MAX;i++){
		if(ps1[i].move_flag == 0){
			ps1[i].move_type = shot_type;
			ps1[i].move_flag = 1;
			break;
		}
	}
}

ここで使うのが先ほどの「move_flag」になります。

これが使用中かどうかを表すフラグになりますので未使用「move_flag == 0」のものを探して使用中「move_flag = 1」にします。

そしてショットの動き方を登録

ps1[i].move_type = shot_type;

実際の動きに入ります。

「ショットの動き方」に応じて発射・計算

ショットの動き方を計算する「my_move_player_shot()」です。

少し長いので分けて説明します。

まずは全てのプレイヤーショットの入れ物を調べてそれが使用中かどうか調べます。

for(int i = 0;i < PLAYER_SHOT_MAX;i++){
	if(ps1[i].move_flag == 1){
	}
}

ここで使用中「ps1[i].move_flag == 1」の状態のものがあった場合、その弾は動いていなきゃいけないので動かします。

「switch」文で動き方を振り分けます。

switch(ps1[i].move_type){
case 0:
	break;
default:
	break;
}

今回は「case 0」正面一方向に飛んでいくショットだけ作っていきます。

ではその中身を見ていきましょう。

初期化部分と移動部分に分かれます。

初期化部分を見てみます。

if(ps1[i].init_flag == 0){
	ps1[i].max_bullet = 1;
	for(int j = 0;j < ps1[i].max_bullet;j++){
		ps1[i].x[j] = p1.x;
		ps1[i].y[j] = p1.y;
		ps1[i].flag[j] = 1;
	}
	ps1[i].gamecount_point = gamecount;
	ps1[i].init_flag = 1;
}
else{
	/*移動部分*/
}

初期化用のフラグ「ps1[i].init_flag == 0」の時は初期化されていないので初期化をします。

最初は正面に1発飛ばすだけなので最大弾数は「ps1[i].max_bullet = 1」一発だけですね。

そしてプレイヤーの現在の座標をショットにコピーします。

ps1[i].x[j] = p1.x;
ps1[i].y[j] = p1.y;

ここで「ps1[i].flag[j] = 1」というのが出てきました。

これはショットを表示するかどうかのフラグでしたね。

こちらも1発分だけですね。

表示するかどうかのフラグと言われても今の時点では「?」だと思います。

当たり判定の時に再度説明いたしますので今は深く考えずそういうものだぐらいに流しておいてください。

最後に「ps1[i].gamecount_point = gamecount」と

その時の「gamecount」を記録して初期化は終了です。

あとでここを基準に一定時間ショットを移動させます。

ではさきほどの「else」、移動部分を見てみましょう。

else{
	if(gamecount < ps1[i].gamecount_point + 100){
		for(int j = 0;j < ps1[i].max_bullet;j++){
			ps1[i].y[j] += 8;
		}
	}
	else{
		ps1[i].move_flag = 0;
		ps1[i].init_flag = 0;
	}
}

さきほどの「gamecount_point」を基準にそこからgamecount「100」カウント分

if(gamecount < ps1[i].gamecount_point + 100)

移動させます。

for(int j = 0;j < ps1[i].max_bullet;j++){
	ps1[i].y[j] += 8;
}

今回は正面に飛ばすだけなのでy座標を一度に「8」ピクセル移動しております。

そして「100」カウントを超えたらフラグを落としてショットを消滅させます。

ps1[i].move_flag = 0;
ps1[i].init_flag = 0;

この時に使用中かどうかのフラグ「move_flag」と初期化用のフラグ「init_flag」も忘れずに落とす事が大事です。

なぜ「100」カウントを超えたらというのはただ単に画面外に出たらという事です。

もちろん「ps1.y[] > 240」の時はフラグを落とす、のように座標で指定もできます。

当STG入門ではカウントでフラグを落とすように統一しております。

これで移動完了です。

座標を中央に

プレイヤーの時と同じように座標を中央に移動します。

「my_to_center()」に追加します。

for(int i = 0;i < PLAYER_SHOT_MAX;i++){
	if(ps1[i].move_flag == 1 && ps1[i].init_flag == 1){
		for(int j = 0;j < ps1[i].max_bullet;j++){
			ps1[i].draw_x[j] = ps1[i].x[j] + 220;
			ps1[i].draw_y[j] = ((-1) * ps1[i].y[j]) + 240; 
		}
	}
}

座標の数だけ「for」文で回せば大丈夫ですね。

ショットの表示する

あとは表示するだけですね。

「my_draw_player_shot()」です。

void my_draw_player_shot(){
	for(int i = 0;i < PLAYER_SHOT_MAX;i++){
		if(ps1[i].move_flag == 1 && ps1[i].init_flag == 1){
			for(int j = 0;j < ps1[i].max_bullet;j++){
				if(ps1[i].flag[j] == 1){
					DrawFormatString(ps1[i].draw_x[j],ps1[i].draw_y[j],Color_White, "弾");
				}
			}
		}
	}
}

ショットの表示に関しても基本的には今までと同じような感じです。

for(int i = 0;i < PLAYER_SHOT_MAX;i++){
	if(ps1[i].move_flag == 1 && ps1[i].init_flag == 1){
	}
}

使用中かつ初期化が終わっている「プレイヤーショットの入れ物」を調べます。

そして最大弾数分表示するだけなのですが

for(int j = 0;j < ps1[i].max_bullet;j++){
	if(ps1[i].flag[j] == 1){
	}
}

中に例の「ps1[i].flag[j] == 1」の一文が入ってます。

先ほど一発分だけ設定したので当たり前ですがそのまま表示されますよね。

ここまでだと特に意味ないと思われますが後で「2way、5wayショット」の時にこれを存分に使いますので今はそっとしておいてください。

では少し長くなりましたがこれで無事にプレイヤーショットが完成しました。

画像(cdxs_5_2)

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

中間ソース4

次回は敵を表示してみましょう!

次回

六日目 敵を表示

□ページの先頭へ□

□目次へ戻る□

□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時点)