C言語 入門 STGの作り方

十五日目 敵ショットを追加

初心者向けSTG作成入門

HOME/STGの作り方 目次/十五日目 敵ショットを追加/

広告

↓2016年02月29日発売↓

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

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

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

目次へ戻る

敵ショットを追加

今回は敵ショットの種類を増やしていきます。

連射ショット

直線ショットを5発連続で発射してみます。

画像(s-15-1)

まずはプログラムを見てみましょう!

「my_move_enemy_shot()」の「case 1」になります。

初期化部分です。

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

連射ショットの作り方は簡単に言うと

「決まった時間ごとに最大弾数の「max_bullet」を増やしてその「max_bullet」の分だけショットを移動する」

みたいな手順になるのですが、その決まった時間を決めるのに「gamecount_point」をいつもより余分にもう一つ使います。

あとは今回5連射なのでショット5発分の座標を設定すれば初期化は完了です。

続いて移動部分です。

移動部分

for (int j = 0; j < enemy_shot[i].max_bullet; j++){
 enemy_shot[i].y[j] = enemy_shot[i].y[j] - 2;
}

さきほども言いましたが基本の移動は「max_bullet」分ショットを動かすだけです。

次にこのショットの重要な部分である決まった時間に「max_bullet」を増やす処理です。

if (enemy_shot[i].max_bullet < 5){
 if (gamecount == enemy_shot[i].gamecount_point[1] + 5){
  enemy_shot[i].max_bullet++;
  enemy_shot[i].gamecount_point[1] += 5;
 }
}

「max_bullet」が5発未満の場合は

if (gamecount == enemy_shot[i].gamecount_point[1] + 5){
 enemy_shot[i].max_bullet++;
 enemy_shot[i].gamecount_point[1] += 5;
}

さきほど余分に用意した「gamecount_point[1]」を使って「gamecount」が5カウント経過したら「max_bullet」を増やしてさらに基準点である「gamecount_point[1]」も5カウント分増加させます。

こうする事によって決まった時間に段階的に「max_bullet」を増やす事ができます。

これで連射ショット完成です。

プレイヤー狙い

さて次はいよいよ好きな角度にショットできる魔法のような関数を紹介します。

これを使ってプレイヤーに向かって飛んでいくショットを作りたいと思います。

この関数を使えるようになるといろいろな場面に使えるのでぜひ覚えましょう。

使う時はヘッダファイルに

<math.h>

こちらを加えてください。

ちょっと難しい数学関数を使う時なんかにこれを加えます。

sin,cos

「sin」サイン、「cos」コサインと言います。

数学嫌いにはたまらない言葉ですよね。

知ってる方も知らない方も今回は手っ取り早く使い方だけ説明したいと思います。

画像(s-15-2)

まずは角度ですが画像のように右側が0度から始まって反時計回りに90度、180度、270度と増えていきます。

では予行演習として画像のように30度で矢印の方向に向かっていくショットを考えてみます。

度数法と弧度法

この角度を指定するのには弧度法(ラジアン)というのが使われます。

度数法っていうのは普通に使っている一回転を360度とする方法です。

弧度法は一回転を2πとする方法です。180度はπ、90度はπ/2になります。

って言われても知らない人にとっては意味不明ですよね。なのでこの2つを変換する計算式が

度数法から弧度法

弧度(ラジアン) = 度 × (円周率(π) / 180)

になります。この「度」の部分に好きな度数を入れればいいワケです。

いちよう逆は

度 = ラジアン × (180 / 円周率(π))

になります。

それではさきほどの30度をラジアンに変換すると

angle = 30 * (DX_PI / 180);

こんな感じになります。「angle」がラジアンに変換された30度になります。

「DX_PI」っていうのはDXライブラリで使える円周率の事です。

これで角度が用意できたのであとは「sin,cos」の出番です。

さきほどの「angle」を使って30度方向に1ピクセルずつショットを飛ばしたい場合は

x = x + cos(angle) * 1;
y = y + sin(angle) * 1;		

こうなります。

思った以上にとてもシンプルです。

最後の数字を「5」にすれば5ピクセルずつ進みます。

もしよろしければ関数表示プログラムの内容に

double angle;

とラジアン値を入れる為の「double」型の変数を加えて

void my_init_point(){
	x = 0;
	y = 0;
	draw_x = 0;
	draw_y = 0;
	angle = 30 * (DX_PI / 180);
}

初期座標を変更して、「angle」に好きな角度をセットして

void my_move_point(){
	x = x + cos(angle) * 1;
	y = y + sin(angle) * 1;		
}

「sin,cos」式を組み込んで実行してみてください。

2点間の角度「atan2」

好きな角度にショットを飛ばす方法はわかりました。

しかし最初の目的はプレイヤー狙いのショットですよね。

といってもその時その時で変わるプレイヤー方向への角度ってもんがわかりません。

そんな声にお応えするような大変便利な関数があるんです。

それが

「atan2」

(アークタンジェントツー)

になります。

使い方は簡単!

度 = atan2((プレイヤーy座標 - 敵y座標),(プレイヤーx座標 - 敵x座標))

これだけです。仮にプレイヤーを「p1」、敵を「e1」とすると

angle = atan2((float)(p1.y - e1.y), (float)(p1.x - e1.x));

これだけでプレイヤー方向への角度がわかります。

しかもここでわかる角度はラジアン値になるのでさきほどの「度数法→弧度法」の計算は必要ないという大変便利な関数になるのです。

ちなみに関数「atan2」では「float」値を使うので座標に「double」値などを使っている場合は「float」値に型キャストする必要があります。

簡単に言うと変数の型を合わせなきゃいけません。

「よくわからないよー」という方は頭に「(float)」を付ければ大丈夫です。

ではこれらを踏まえた上で

「プレイヤー狙い」

のプログラムを見てみましょう!

「my_move_enemy_shot()」の「case 2」になります。

初期化部分です。

if (enemy_shot[i].init_flag == 0){
 enemy_shot[i].gamecount_point[0] = gamecount;
 enemy_shot[i].max_bullet = 1;
 enemy_shot[i].x[0] = enemy_shot[i].first_x;
 enemy_shot[i].y[0] = enemy_shot[i].first_y;
 enemy_shot[i].angle[0] = 
  atan2(p1.y - enemy_shot[i].first_y,
   p1.x - enemy_shot[i].first_x);
 enemy_shot[i].init_flag = 1;
}
else{
	/*移動部分*/
}

スペースの都合上、折り返しているので大変見づらいですが

enemy_shot[i].angle[0] = 
  atan2(p1.y - enemy_shot[i].first_y,
   p1.x - enemy_shot[i].first_x);

ここで先ほど説明した「atan2」を使ってプレイヤーへの角度を設定しております。

あとは「sin,cos」を使ってその方向へ飛ばすだけです。

移動部分

enemy_shot[i].x[0] += cos(enemy_shot[i].angle[0]) * 2;
enemy_shot[i].y[0] += sin(enemy_shot[i].angle[0]) * 2;

「sin,cos」を使って移動させます。

x方向は「cos」y方向は「sin」を使うトコロに注意です。

画像(s-15-3)

プレイヤー狙いショット完成です!

プレイヤー狙い連射ショット

次は先ほどの連射ショットとプレイヤー狙いショットを組み合わせてプレイヤー狙い連射ショットを作りたいと思います。

少し加えるだけなのでさっそくプログラムから見ていきたいと思います。

「my_move_enemy_shot()」の「case 3」になります。

if (enemy_shot[i].init_flag == 0){
 enemy_shot[i].gamecount_point[0] = gamecount;
 enemy_shot[i].gamecount_point[1] = gamecount;
 enemy_shot[i].max_bullet = 1;
 for (int j = 0; j < 10; j++){
  enemy_shot[i].x[j] = enemy_shot[i].first_x;
  enemy_shot[i].y[j] = enemy_shot[i].first_y;
 }
 enemy_shot[i].angle[0] = 
  atan2(p1.y - enemy_shot[i].first_y,
   p1.x - enemy_shot[i].first_x);
 enemy_shot[i].init_flag = 1;
}
else{
	/*移動部分*/
}

さきほどやったばかりなので大丈夫ですね。

「atan2」で角度を取得して連射ショットの為の「gamecount_point」を取得して今回は10連射にしたいので10発分座標を設定しているだけです。

続いて移動部分です。

移動部分

for (int j = 0; j < enemy_shot[i].max_bullet; j++){
 enemy_shot[i].x[j] += cos(enemy_shot[i].angle[0]) * 4;
 enemy_shot[i].y[j] += sin(enemy_shot[i].angle[0]) * 4;
}

連射なので「max_bullet」分ショットを動かします。

次に連射の為の「max_bullet」を増加させる部分です。

if (enemy_shot[i].max_bullet < 10){
 if (gamecount == enemy_shot[i].gamecount_point[1] + 5
  && enemy[enemy_shot[i].enemy_no].move_flag == 1){
  enemy_shot[i].max_bullet++;
  enemy_shot[i].gamecount_point[1] += 5;
 }
}

ここで先ほどとほとんど変わらず「5」カウントずつ「max_bullet」を増加させているのは変わらないのですが条件のトコロになにやら見慣れない一文が見えます。

enemy[enemy_shot[i].enemy_no].move_flag == 1

この部分ですね。

もう忘れかけている頃だと思いますのでもう一度説明させて頂きますとこの「enemy_no」というのは敵ショットをセットする時の

my_set_enemy_shot(enemy[i].x,enemy[i].y,enemy[i].shot_type,i);

この最後の引数「i」を受け取ったものになります。

ではこの「i」はなんなのかと言いますとその前の「for」文を辿ればわかりますがその時ショットを放っている敵の番号になります。

ではこれを使って何がしたいのかと言いますと、今回の連射ショットのようなある程度長い時間放ち続けるショットの場合、そのショットを放っている敵の体力が「0」になってその敵が消滅しているのにも関わらずその座標から連射ショットだけが放たれているという状況が起こり得るのでそれを防止する為に敵ショット側からも元の敵を監視できるようにその時の敵番号を受け取っているというワケです。

enemy[enemy_shot[i].enemy_no].move_flag == 1

あらためてこの条件を加えればその敵がまだ動いている間は連射ショットを放ち続けるという事ができますね!

画像(s-15-4)

プレイヤー狙い連射ショット完成です。

プレイヤー狙い連射ショット2

ではついでにもう一つ同じ「プレイヤー狙い連射ショット」でも一発ずつ角度を修正しながら放つ連射ショットを作ってみましょう。

初期化の部分は変わらないので移動部分から見てみます。

「my_move_enemy_shot()」の「case 4」になります。

移動部分

for (int j = 0; j < enemy_shot[i].max_bullet; j++){
 enemy_shot[i].x[j] += cos(enemy_shot[i].angle[j]) * 4;
 enemy_shot[i].y[j] += sin(enemy_shot[i].angle[j]) * 4;
}

今度は一発ずつ異なる方向に飛んでいくので「angle」もそれぞれ異なるものを使います。

そして次の部分で「max_bullet」を増加させると共に角度を再取得していきます。

if (enemy_shot[i].max_bullet < 10){
 if (gamecount == enemy_shot[i].gamecount_point[1] + 10
  && enemy[enemy_shot[i].enemy_no].move_flag == 1){
   enemy_shot[i].angle[enemy_shot[i].max_bullet] = 
    atan2(p1.y - enemy_shot[i].first_y, p1.x - enemy_shot[i].first_x);
   enemy_shot[i].max_bullet++;
   enemy_shot[i].gamecount_point[1] += 10;
 }
}

基本的にはほとんど変わらないので、角度を再取得している部分だけ見てみます。

enemy_shot[i].angle[enemy_shot[i].max_bullet] = 
    atan2(p1.y - enemy_shot[i].first_y, p1.x - enemy_shot[i].first_x);

この部分ですね。

これを見てみるとわかりますが「max_bullet」の増加に合わせて角度も再取得、というのは言葉通りその「max_bullet」をそのまま添え字に当てれば大丈夫です。

最初の一発分はすでに設定されているので「max_bullet」を増加させる前に角度を再取得するトコロに注意です。

画像(s-15-5)

プレイヤー狙い連射ショット2完成です。

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

中間ソース14

次回はプレイヤーショットの種類を追加したいと思います。

次回

十六日目 プレイヤーショットを追加

□ページの先頭へ□

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