C言語 入門 STGの作り方

五日目 プレイヤーのショット

初心者向けSTG作成入門

HOME/STGの作り方 目次/五日目 プレイヤーのショット/

広告

↓2016年02月29日発売↓

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

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

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

目次へ戻る

プレイヤーのショット

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

まずは中間ソース4の「my_draw_variable()」に

for (int i = 0; i < 20; i++){
 DrawFormatString(420, 40 + i * 20, Color_White, 
  "ps1[%d].move_flag = %d",i, ps1[i].move_flag);
}

こちらの一文を加えて実行してみてください。

画像(s-5-1)

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

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

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

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

#define PLAYER_SHOT_MAX 20

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」含めちょっと役割がわかりづらいものもあるかと思いますので後ほど順を追って説明していきます。

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

#define PLAYER_SHOT_MAX 20
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] % 8 == 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] % 8 == 1」となってますね。

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

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

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

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

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

意味不明です!という方は

めんどくさいですが、紙に

1 ÷ 8 = 0 余り 1

2 ÷ 8 = 0 余り 2

3 ÷ 8 = 0 余り 3

4 ÷ 8 = 0 余り 4

5 ÷ 8 = 0 余り 5

6 ÷ 8 = 1 余り 6

7 ÷ 8 = 1 余り 7

8 ÷ 8 = 1 余り 0

9 ÷ 8 = 1 余り 1

10 ÷ 8 = 2 余り 2

11 ÷ 8 = 2 余り 3

12 ÷ 8 = 2 余り 4

13 ÷ 8 = 2 余り 5

14 ÷ 8 = 2 余り 6

15 ÷ 8 = 3 余り 7

16 ÷ 8 = 3 余り 0

とひたすら書いていってください。

そして余りに注目するとずっと規則正しく変化してるのがわかると思います。

さきほどの場面に当てはめると定期的に余りが「1」になっているのがわかると思います。

この時に何か処理をしようって事です。

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

「my_set_player_shot(int)」になります。

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」、移動部分を見てみましょう。

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;
}

今回は一度に「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] + 320;
   ps1[i].draw_y[j] = ((-1) * ps1[i].y[j]) + 240; 
  }
 }
}

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

ショットの表示する

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

「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ショット」の時にこれを存分に使いますので今はそっとしておいてください。

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

画像(s-5-2)

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

中間ソース4

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

次回

六日目 敵を表示

□ページの先頭へ□

□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□