C言語 入門 STGの作り方

十八日目 敵の動きを追加2

初心者向けSTG作成入門

HOME/STGの作り方 目次/十八日目 敵の動きを追加2/

広告

↓2016年02月29日発売↓

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

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

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

目次へ戻る

Sin波を利用する

敵の動きを追加する前に何かと便利な「Sin波」というものについて見ていきたいと思います。

まずは「三角関数 Sin 表」などでインターネット検索してSinの表を引っ張り出してきましょう。

Sinなんてわからないよーという方はとりあえず角度によって規則的に動く数字ぐらいに考えておいてください。

ではあらためてSinの表を確認してみると

0°・・・0.0000

5°・・・0.0872

10°・・・0.1736

80°・・・0.9848

85°・・・0.9962

90°・・・1.0000

こんな感じになってますよね。

ではさらに90度より先はどうなるかを見てみましょう。

次のプログラムを実行してみてください。

Sin値表示プログラム

ひたすらSinの角度を上げていったプログラムです。

画像(s-18-1)

ここで注目が90度を超えると1.0000から折り返してそのまま180度からマイナスに入りそして270度で-1.0000に到達して再び折り返すみたいな変化をしている点です。

これをx軸を角度、y軸をsin値でグラフにすると・・・、

画像(s-18-2)

これぞ数学みたいな、いい感じになりました。

この動きというか値の変化を敵の動きなどに取り入れようというワケです。

折り返しの時に緩やかに値が変化する所もポイントです。

Sin波を使ってみよう

使う時は-1〜1の間という大変小さな値を変化しているのでこれに動かしたい幅の数字をかけてあげます。

例えば200をかければ-200〜200の間を行き来するという事です。

試しに簡単なプログラムを作ってみます。

Sin波利用プログラム

画像(s-18-3)

画像じゃほぼ伝わりませんが、なんというか滑らかな動きになりました。

そういえばこんな動きゲームで見た事あるような気がしますよね。

プログラムを見てみると

x = sin(angle2) * 200;

「Sin値」に「200」をかけて値を「-200〜200」の間に変化

x = sin(angle2) * 200 + 320;

さらにその値に「320」を加える事によって値を「120〜520」にして画面を中心に折り返すような動きを実現しております。

最初は難しそうですが使い方さえ覚えればけっこう簡単そうですね!

そんなワケでこのいい感じの動きをするSin波も仲間に加えて新たな敵の動きを追加していきたいと思います。

敵の動きを追加2

それでは敵の動きを追加していきます。

「my_move_enemy()」の「case 6」です。

画像(s-18-4)

軌道を点にして描いてみました。

実際の動きは中間ソース17を確認してみてください。

カーブを描きながら下降して途中で折り返すような動きです。

最初の下降部分でさっそくさきほどのSin波を取り入れております。

初期化部分です。

if (enemy[i].init_flag == 0){
 enemy[i].x = enemy[i].first_x;
 enemy[i].y = 260;
 enemy[i].angle = 0;
 enemy[i].gamecount_point = gamecount;
 enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

初期化は意外とすっきりしております。

Sin波に使う角度「angle」を初期化しているぐらいですね。

移動部分

if (gamecount < enemy[i].gamecount_point + 450){
 enemy[i].angle += 2;
 enemy[i].x = (sin(enemy[i].angle * (DX_PI / 180)) * 100)
	 + enemy[i].first_x;
 enemy[i].y -= 0.5f;
}

最初に下降してくる部分を見てみます。

enemy[i].angle += 2;
enemy[i].x = 
	(sin(enemy[i].angle * (DX_PI / 180)) * 100)

まずは角度を増加させながら「-100〜100」の振れ幅のSin波を取得します。

そしてそこに「+ enemy[i].first_x;」と敵の登場x座標を足せばそこを中心としてx軸と並行に左右に触れるSin波になりますね!

そのままでは下降しないので最後に「enemy[i].y -= 0.5f;」y座標を直接下げてあげます。

これでカーブしながら下降できました!

あとは

if (gamecount > enemy[i].gamecount_point + 450){
	enemy[i].y += 4;
}

タイミングを見て途中で上昇するだけですね!

これでこの動きは完了です。

次の動きにいきます。

「my_move_enemy()」の「case 7」です。

画像(s-18-5)

好きな場所に「y = ax^2」のような放物線を描きます。

y = a(x - p)^2 + q

細かい説明は省略させて頂きますが、先ほどの放物線を好きな場所に出したい時はこの関数を使います。

この「p」と「q」が放物線の先っちょの座標(p,q)になります。

画像(s-18-6)

ちょっとわかりづらいのでまずはプログラム的に書きなおしてみます。

y = a * ((x - p) * (x - p)) + q;

さらにわかりづらくなりました!

特に()カッコの中の「p」が少しややこしいので実際に座標に当てはめた例をご紹介します。

放物線の大きさ「a」はとりあえず「0.02」にしてます。

頂点座標(100,100)の場合

y = 0.02 * ((x - 100) * (x - 100)) + 100;
画像(s-18-7)

こうなります。

式を確認すると先っちょの座標を(x100,y100)と「x」がプラスのはずなのにマイナスです。なんか変な感じですよね。

次にx座標を逆にしてみます。

頂点座標(-100,100)の場合

y = 0.02 * ((x + 100) * (x + 100)) + 100;
画像(s-18-8)

予想通りプラスマイナスがこちらも逆になりましたね。

数学好きならそんなの当たり前じゃんの話なのですが、数学嫌い、またはそんなの習ってないよという場合はそういうもんだと覚えましょう。

ちなみに「a」の部分をマイナスにすると逆の放物線になったりするのでお好みに合わせていろいろ試してみてください!

y = -0.02 * ((x + 100) * (x + 100)) + 100;
画像(s-18-9)

では好きな場所に放物線を描く方法がわかったトコロでこれを利用した動きを作っていきます。

初期化部分です。

if (enemy[i].init_flag == 0){
 enemy[i].x = -320;
 enemy[i].y = 260;
 enemy[i].var[0] = rand() % 400;
 enemy[i].var[1] = rand() % 50;
 enemy[i].gamecount_point = gamecount;
 enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

まずはこの部分

enemy[i].var[0] = rand() % 400;
enemy[i].var[1] = rand() % 50;

に注目です。

変数「var」はいろいろな目的に使えるように用意した変数なのですがそこに乱数値として2つの値を取得しております。

これを何に使うのかと言いますと毎回ある程度不規則な場所に放物線が描かれるようにしたかったのでそれを実現する為にこの乱数を使う事にしました。

では移動部分を見てみます。

移動部分

enemy[i].x += 2;
enemy[i].y = 0.02 * ((enemy[i].x + (200 - enemy[i].var[0])) * 
 (enemy[i].x + (200 - enemy[i].var[0]))) - enemy[i].var[1];
y = a * ((x - p) * (x - p)) + q;

頂点座標(p,q)の「p」の部分を見てみると

(200 - enemy[i].var[0])

となってます。

「enemy[i].var[0]」はさきほども言いましたが「0〜400」の乱数が入るので結果「p」は「-200〜200」になりますね。

「q」はそのまま乱数値「0〜50」が入る事になりますね。

これで頂点座標「p,q」の位置は「-200〜200,0〜50」をランダムに指定される事になりますね。

毎回少しずれた場所に放物線を描く事ができました!

次の動きにいきます。

「my_move_enemy()」の「case 8」です。

画像(s-18-10)

同じ動きを繰り返しているのですがこれはプログラムを見た方が早いと思いますのでさっそく見てみます。

初期化部分です。

if (enemy[i].init_flag == 0){
 enemy[i].x = enemy[i].first_x;
 enemy[i].y = 260;
 enemy[i].pattern = (rand() % 4) * 2;
 enemy[i].gamecount_point = gamecount;
 enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

まず同じ動きを繰り返す方法ですが今回は一つ一つの動きを「switch」文で分けてそれを切り替える事によって同じ動きを繰り返すようにしました。

その切り替えに使う変数として「pattern」という名前の変数を用意して毎回違う場所から動きが始まるようにそこに乱数を入れております。

次に移動部分です。

移動部分

switch (enemy[i].pattern % 8){
case 0:
	enemy[i].x += 2;
	break;
case 1:
	enemy[i].x -= 2;
	break;
case 2:
	enemy[i].y += 2;
	break;
case 3:
	enemy[i].y -= 2;
	break;
default:
	break;
}
enemy[i].y -= 0.5f;
if(gamecount % 20 == 1){
	enemy[i].pattern++;
}

長いので一部分だけ抜粋です。

移動部分は意外と単純です。

下降しながら一つ一つの動きを切り替えているだけです。

このように「switch」文を使えば一つ一つが単純な動きでも組み合わせれば複雑な動きを実現する事ができますね!

次の動きにいきます。

「my_move_enemy()」の「case 9」です。

画像(s-18-11)

今回は「gamecount」の経過とさきほどの「switch」文を使って大きく3つの部分に分けて全体の動きを作っております。

初期化部分です。

if (enemy[i].init_flag == 0){
 enemy[i].x = -320;
 enemy[i].y = -260;
 enemy[i].angle = atan2(p1.y - enemy[i].y, p1.x - enemy[i].x);
 enemy[i].pattern = 0;
 enemy[i].gamecount_point = gamecount;
 enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

登場場所は画面左下(-320,-260)に固定で後はプレイヤー方向への角度を一つ取得しておきます。

移動部分を見てみます。

if (gamecount < enemy[i].gamecount_point + 50){
 enemy[i].x++;
 enemy[i].y = 0.1 * ((enemy[i].x + 320) * (enemy[i].x + 320)) - 240;
}

最初は下からせり上がるように登場する部分です。

ここは放物線を使っているのですが頂点座標(p,q)をプレイヤーの初期座標に合わせる事によって放物線の底からせり上がるようにして登場しております。

switch (enemy[i].pattern % 2){
case 0:
	enemy[i].x += cos(enemy[i].angle) * 2;
	enemy[i].y += sin(enemy[i].angle) * 2;
	break;
case 1:
	break;
default:
	break;
}
if (gamecount % 12 == 0){
 enemy[i].pattern++;
 enemy[i].angle = atan2(p1.y - enemy[i].y, p1.x - enemy[i].x);
}

次にプレイヤー方向へ少し移動、停止というような動きを繰り返す部分です。

「switch」文を使って移動、停止を繰り返し切り替えながら移動していきます。

if (gamecount > enemy[i].gamecount_point + 500){
	enemy[i].y += 4;
}

最後に上昇して今回の動きは終了です。

次の動きにいきます。

「my_move_enemy()」の「case 10」です。

画像(s-18-12)

じわりじわりとプレイヤーへ近づいていくような動きです。

短いスパンでプレイヤー方向への角度を取得しなおすだけなのでプログラムは簡単です。

初期化部分です。

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

プレイヤー方向への角度を取得しております。

移動部分

enemy[i].x += cos(enemy[i].angle) * 0.5f;
enemy[i].y += sin(enemy[i].angle) * 0.5f;
if (gamecount % 12 == 0){
 enemy[i].angle = atan2(p1.y - enemy[i].y, p1.x - enemy[i].x);
}

短いスパンでプレイヤー方向への角度を取得しながら少しずつプレイヤー方向へ移動しているだけですね!

最後の動きにいきます。

「my_move_enemy()」の「case 11」です。

画像(s-18-13)

ひし形を描きながら下降しているような動きなります。

その動きの中に最初の方にも少し説明しましたが、「Sin」波の値の変化を取り入れて緩急をつけております。

画像(s-18-14)

こちらのグラフの赤く色づけした部分、値が最初緩やかに上昇、途中は勢いよく増え、折り返しに差し掛かるトコロあたりで再び緩やかに・・・。

と、この値の変化を使います。

ではこの値の部分だけをどのように取り出して利用するか?ですがこれはやりながら覚えていった方が分かりやすいと思いますのでプログラムで順を追って説明したいと思います。

次のプログラムの「その1〜その4」までを一つずつコメントを外しながら実行してみてください。

値の変化をそのまま表示するだけのプログラムになります。

Sin値表示プログラム2

一つずつ説明していきたいと思います。

/*その1*/
x = sin(DX_PI / 720 * gamecount);

まず以前までは度数法→弧度法の変換を行いながら説明しておりましたが、手間がかかってしまうので弧度法のままでいきます。

弧度法は1周360度を2πとして扱うのでしたね。

画像(s-18-15)

さきほどの図と見比べながら確認していってみてください。

あらためまして、「x = sin(DX_PI / 720 * gamecount);」こちらの式ですが、πを「720」で割ったものに「gamecount」をかけているので「gamecount」が720になるまでの間に「0→1→0」と値が変化しているのが確認できたかと思います。

画像(s-18-16)

ちょうどこの「0〜π」までの山の部分を取り出したような形になりますね。

値が「1」に近づくに連れその変化が緩やかになっているのも確認できたかと思います。

当たり前ですがπを割った数字「720」を「100」にすれば「100」カウントで今の値の動きになります。

こんな感じで値を取り出す事はできましたが、今回必要なのはこの部分ではなくそれを少しずらした部分

画像(s-18-17)

「-0.5π〜0.5π」あたりなので単純に今の部分に「-0.5π」してあげます。

/*その2*/
x = sin(DX_PI / 720 * gamecount - (DX_PI * 0.5f));

これで「-1→0→1」という緩やかにはじまり、急になって、再び緩やかにという値の変化をする部分が取り出せたかと思います。

ここでこの式を単純に「100」倍などすれば「-100〜0〜100」という値なるのでなんとなくは使えそうですが、色々な場面で使いたい時にはいちいち値がマイナスに入ったりするのは実用的ではないですよね。

なので少し工夫して「0〜1」の値の変化になるようにしてあげます。

/*その3*/
x = (sin(DX_PI / 720 * gamecount - (DX_PI * 0.5f)) + 1) / 2;

これまで「-1→0→1」という変化だったのでそこに「+1」して「2」で割れば「0〜1」の値の変化になるというワケですね。

あとは実際使いたい値の変化にすべく任意の数をかけてあげるだけです。

/*その4*/
x = (sin(DX_PI / 720 * gamecount - (DX_PI * 0.5f)) + 1) / 2 * 100;

今回は「100」をかけているので「0〜100」までを緩やかに始まり、途中急上昇、再び緩やかに「100」の値に「720」カウントで到達するという値の変化を取得する事ができますね!

では事前説明が長引いてしまいましたが今回の動き初期化部分です。

if (enemy[i].init_flag == 0){
 enemy[i].var[0] = enemy[i].first_x;
 enemy[i].var[1] = 260;
 enemy[i].x = enemy[i].var[0];
 enemy[i].y = enemy[i].var[1];
 enemy[i].pattern = rand() % 4;
 enemy[i].var[2] = 0;
 enemy[i].gamecount_point = gamecount;
 enemy[i].init_flag = 1;
}
else{
	/*移動部分*/
}

ちょっとややこしいですが「enemy[i].var[0],enemy[i].var[1]」には敵の出現する初期座標を最初入れて途中も座標の更新用として使用、「enemy[i].var[2]」は経過カウント用として使用しております。

では移動部分を見てみます。

switch (enemy[i].pattern % 4){
case 0:
 enemy[i].x = enemy[i].var[0] + 
  ((sin(DX_PI / 60 * enemy[i].var[2] - (DX_PI * 0.5f)) + 1) / 2) * 100;
 enemy[i].y = enemy[i].var[1] + 
  ((sin(DX_PI / 60 * enemy[i].var[2] - (DX_PI * 0.5f)) + 1) / 2) * 100;
 break;

case 1:
 enemy[i].x = (enemy[i].var[0] + 100) - 
  ((sin(DX_PI / 60 * enemy[i].var[2] - (DX_PI * 0.5f)) + 1) / 2) * 100;
 enemy[i].y = (enemy[i].var[1] + 100) + 
  ((sin(DX_PI / 60 * enemy[i].var[2] - (DX_PI * 0.5f)) + 1) / 2) * 100;
 break;

default:
 break;
}

if (enemy[i].var[2] < 60){
	enemy[i].var[2]++;				
}
else{
	enemy[i].var[2] = 0;
	enemy[i].pattern++;
}
enemy[i].var[1] -= 0.5f;

今回も「switch」文を使って動きを切り替えていきますが、同じような動きの繰り返しなので一部分だけ抜粋します。

先に紹介した「Sin」波を使った滑らかな値の変化でx,y座標共に「0〜100」の値を加えております。

つまり斜めに進むというワケですね。

そして今回の場合は「60」カウントで区切って次の動きに切り替え、その「100」ずつずれた場所からまた同じように移動させてひし型を描くような動きにしているというワケです。

あとはy座標を下降させれば今回の動きは完了です。

画像(s-18-18)

いろいろな動きをします!

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

中間ソース17

次回は簡単なバリアーとボムを作りたいと思います。

次回

十九日目 バリアーとボム

□ページの先頭へ□

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